diff --git a/.github/workflows/deploy-relay.yml b/.github/workflows/deploy-relay.yml index 8e2e3be94ff6..391b0bc61210 100644 --- a/.github/workflows/deploy-relay.yml +++ b/.github/workflows/deploy-relay.yml @@ -4,6 +4,15 @@ on: push: branches: - main + # Alchemy does not redeploy the Worker when only a Config value read in its + # Init changes (alchemy-run/alchemy#1831), so a changed repository variable + # needs a forced deploy to reach production. + workflow_dispatch: + inputs: + force: + description: Redeploy every resource, including ones with no detected changes + type: boolean + default: true permissions: contents: read @@ -17,6 +26,8 @@ concurrency: jobs: deploy_relay: name: Deploy production relay + # A manual run from another branch would deploy that branch to production. + if: github.ref == 'refs/heads/main' runs-on: ubuntu-24.04 # blacksmith-8vcpu-ubuntu-2404 timeout-minutes: 15 environment: @@ -28,6 +39,7 @@ jobs: RELAY_DOMAIN: ${{ vars.RELAY_DOMAIN }} RELAY_API_ZONE_NAME: ${{ vars.RELAY_API_ZONE_NAME }} RELAY_TUNNEL_ZONE_NAME: ${{ vars.RELAY_TUNNEL_ZONE_NAME }} + RELAY_TUNNEL_CLEANUP_MODE: ${{ vars.RELAY_TUNNEL_CLEANUP_MODE }} CLERK_PUBLISHABLE_KEY: ${{ vars.CLERK_PUBLISHABLE_KEY }} CLERK_JWT_AUDIENCE: ${{ vars.CLERK_JWT_AUDIENCE }} APNS_ENVIRONMENT: ${{ vars.APNS_ENVIRONMENT }} @@ -70,7 +82,7 @@ jobs: - name: Deploy production relay stage if: steps.creds.outputs.configured == 'true' - run: vp run --filter t3code-relay deploy --stage prod --yes --no-input + run: vp run --filter t3code-relay deploy --stage prod --yes --no-input ${{ inputs.force && '--force' || '' }} env: # The PublishClientConfig action writes the client env here instead # of the repo-root .env; nothing on the runner reads it. diff --git a/.github/workflows/release-desktop.yml b/.github/workflows/release-desktop.yml index c374a8692db6..c743b961560b 100644 --- a/.github/workflows/release-desktop.yml +++ b/.github/workflows/release-desktop.yml @@ -510,6 +510,7 @@ jobs: "release/*.dmg" "release/*.zip" "release/*.AppImage" + "release/*.deb" "release/*.exe" ) # Preview builds have no publish config, so electron-builder writes diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9b4d7725aa39..01ed8142116e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -640,7 +640,7 @@ jobs: clerk_cli_oauth_client_id: ${{ needs.relay_public_config.outputs.clerk_cli_oauth_client_id }} relay_url: ${{ needs.relay_public_config.outputs.relay_url }} label: Linux arm64 - runner: ubuntu-24.04-arm + runner: blacksmith-16vcpu-ubuntu-2404-arm platform: linux target: AppImage arch: arm64 @@ -890,6 +890,7 @@ jobs: echo 'release-assets/*.dmg' echo 'release-assets/*.zip' echo 'release-assets/*.AppImage' + echo 'release-assets/*.deb' echo 'release-assets/*.exe' if [[ "${{ needs.preflight.outputs.release_channel }}" != "preview" ]]; then echo 'release-assets/*.blockmap' diff --git a/README.md b/README.md index 28cc78162085..175e52d9de46 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,14 @@ winget install T3Tools.T3Code brew install --cask t3-code ``` +#### Debian, Ubuntu (`.deb`) + +Download the `.deb` from [GitHub Releases](https://github.com/pingdotgg/t3code/releases), then: + +```bash +sudo apt install ./T3-Code-*.deb +``` + #### Arch Linux (AUR) Stable: diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 2e48f321dd47..daedb89a1283 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -3,7 +3,7 @@ "version": "0.0.42", "private": true, "type": "module", - "main": "dist-electron/main.cjs", + "main": "dist-electron/boot.cjs", "scripts": { "ensure:electron": "node scripts/ensure-electron-runtime.mjs", "start": "node scripts/start-electron.mjs", diff --git a/apps/desktop/scripts/main-process-bundle.test.mjs b/apps/desktop/scripts/main-process-bundle.test.mjs index 28d8e37d7a9e..678b62f58aac 100644 --- a/apps/desktop/scripts/main-process-bundle.test.mjs +++ b/apps/desktop/scripts/main-process-bundle.test.mjs @@ -1,3 +1,4 @@ +import * as NodeChildProcess from "node:child_process"; import * as NodeFSP from "node:fs/promises"; import * as NodeOS from "node:os"; import * as NodePath from "node:path"; @@ -108,3 +109,65 @@ void import("./linux.ts").then(({ result }) => process.emit("ready", result));`, await NodeFSP.rm(directory, { recursive: true, force: true }); } }); + +it("loads the emitted packaged boot entry and backend cache preload", async () => { + const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-desktop-boot-")); + try { + const entries = ["src/boot.ts", "src/compileCache.ts"]; + await NodeFSP.mkdir(NodePath.join(directory, "src")); + await Promise.all( + entries.map((entry) => + NodeFSP.copyFile(new URL(`../${entry}`, import.meta.url), NodePath.join(directory, entry)), + ), + ); + assert.ok(Array.isArray(desktopConfig.pack)); + for (const packConfig of desktopConfig.pack) { + if (!Array.isArray(packConfig.entry)) continue; + if (!packConfig.entry.some((entry) => entries.includes(entry))) continue; + await build({ + ...packConfig, + config: false, + cwd: directory, + tsconfig: false, + sourcemap: false, + onSuccess: undefined, + logLevel: "silent", + }); + } + const outputDirectory = NodePath.join(directory, "dist-electron"); + const fixture = `console.log(require('node:module').getCompileCacheDir() ? 'cached' : 'uncached');`; + await NodeFSP.writeFile(NodePath.join(outputDirectory, "main.cjs"), fixture); + await NodeFSP.writeFile( + NodePath.join(outputDirectory, "backend.mjs"), + `import { getCompileCacheDir } from 'node:module'; console.log(getCompileCacheDir() ? 'cached' : 'uncached');`, + ); + for (const disabled of [false, true]) { + for (const args of [ + [NodePath.join(outputDirectory, "boot.cjs")], + [ + "--require", + NodePath.join(outputDirectory, "compileCache.cjs"), + NodePath.join(outputDirectory, "backend.mjs"), + ], + ]) { + const child = NodeChildProcess.spawnSync(process.execPath, args, { + encoding: "utf8", + env: { + ...process.env, + APPIMAGE: "", + NODE_COMPILE_CACHE: undefined, + NODE_DISABLE_COMPILE_CACHE: disabled ? "1" : undefined, + XDG_CACHE_HOME: directory, + TMPDIR: directory, + TEMP: directory, + TMP: directory, + }, + }); + assert.equal(child.status, 0, child.stderr); + assert.equal(child.stdout.trim(), disabled ? "uncached" : "cached"); + } + } + } finally { + await NodeFSP.rm(directory, { recursive: true, force: true }); + } +}); diff --git a/apps/desktop/scripts/smoke-test.mjs b/apps/desktop/scripts/smoke-test.mjs index fea5f0a120e5..05195bb8d8cd 100644 --- a/apps/desktop/scripts/smoke-test.mjs +++ b/apps/desktop/scripts/smoke-test.mjs @@ -5,7 +5,7 @@ import { resolveElectronLaunchCommand } from "./electron-launcher.mjs"; const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)); const desktopDir = NodePath.resolve(__dirname, ".."); -const mainJs = NodePath.resolve(desktopDir, "dist-electron/main.cjs"); +const mainJs = NodePath.resolve(desktopDir, "dist-electron/boot.cjs"); console.log("\nLaunching Electron smoke test..."); diff --git a/apps/desktop/src/app/DesktopAppActivation.test.ts b/apps/desktop/src/app/DesktopAppActivation.test.ts index d6ce80322798..8ab165afce0a 100644 --- a/apps/desktop/src/app/DesktopAppActivation.test.ts +++ b/apps/desktop/src/app/DesktopAppActivation.test.ts @@ -44,6 +44,25 @@ function request(requestId: string, platform: NodeJS.Platform): DesktopAppActiva }; } +function startOkServer(target: ReturnType, userId: number | undefined) { + return startDesktopAppControlServer({ + ...target, + userId, + handle: async (input) => ({ + version: 1, + requestId: input.requestId, + ok: true, + projectId: ProjectId.make("project-1"), + threadId: ThreadId.make("thread-1"), + }), + cancel: () => undefined, + onReclaimError: () => undefined, + }).then((server) => { + openServers.push(server); + return server; + }); +} + function exchange(address: string, payload: DesktopAppActivationRequest) { return new Promise((resolve, reject) => { const socket = NodeNet.createConnection(address); @@ -84,6 +103,7 @@ describe("desktop app control server", () => { }; }, cancel: () => undefined, + onReclaimError: () => undefined, }); openServers.push(server); @@ -117,6 +137,7 @@ describe("desktop app control server", () => { userId, handle: () => new Promise(() => undefined), cancel: resolveCanceled, + onReclaimError: () => undefined, }); openServers.push(server); const socket = NodeNet.createConnection(target.address); @@ -137,4 +158,50 @@ describe("desktop app control server", () => { }); }), ); + + // Two desktop apps can share one state dir, such as nightly and a preview build. + it.effect("keeps a newer app's socket when an older app on the same state dir quits", () => + Effect.gen(function* () { + const platform = yield* HostProcessPlatform; + const userId = yield* HostProcessUserId; + if (platform === "win32") return; + yield* Effect.promise(async () => { + const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-app-takeover-test-")); + const target = makeTarget(NodePath.join(root, "userdata"), platform, userId); + const older = await startOkServer(target, userId); + await startOkServer(target, userId); + + await older.close(); + + await expect( + exchange(target.address, request("after-quit", platform)), + ).resolves.toMatchObject({ ok: true, requestId: "after-quit" }); + await NodeFSP.rm(root, { recursive: true, force: true }); + }); + }), + ); + + it.effect("binds its address again after the socket file is removed", () => + Effect.gen(function* () { + const platform = yield* HostProcessPlatform; + const userId = yield* HostProcessUserId; + if (platform === "win32") return; + yield* Effect.promise(async () => { + const root = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-app-reclaim-test-")); + const target = makeTarget(NodePath.join(root, "userdata"), platform, userId); + const server = await startOkServer(target, userId); + + await NodeFSP.unlink(target.address); + await server.reclaim(); + + await expect( + exchange(target.address, request("reclaimed", platform)), + ).resolves.toMatchObject({ + ok: true, + requestId: "reclaimed", + }); + await NodeFSP.rm(root, { recursive: true, force: true }); + }); + }), + ); }); diff --git a/apps/desktop/src/app/DesktopAppActivation.ts b/apps/desktop/src/app/DesktopAppActivation.ts index 50fc70d783e4..740799955da2 100644 --- a/apps/desktop/src/app/DesktopAppActivation.ts +++ b/apps/desktop/src/app/DesktopAppActivation.ts @@ -1,7 +1,10 @@ -// @effect-diagnostics nodeBuiltinImport:off -- Local socket ownership checks need lstat uid and an atomic stale-socket unlink at the Node adapter boundary. +// @effect-diagnostics nodeBuiltinImport:off -- Local socket ownership checks need lstat, an atomic rename, and a directory watch at the Node adapter boundary. +import * as NodeCrypto from "node:crypto"; +import * as NodeFS from "node:fs"; import * as NodeFSP from "node:fs/promises"; import * as NodeNet from "node:net"; import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; import { DESKTOP_APP_ACTIVATION_PROTOCOL_VERSION, @@ -44,6 +47,8 @@ export class DesktopAppActivationStartError extends Schema.TaggedError Promise; readonly close: () => Promise; } @@ -70,12 +75,12 @@ function requestIdFromUnknown(value: unknown): string { return "invalid-request"; } -async function prepareUnixSocket(input: { - readonly address: string; +/** Makes sure the socket directory is safe to use. Returns true when it had to create it. */ +async function prepareUnixDirectory(input: { readonly directory: string; readonly userId: number | undefined; -}): Promise { - await NodeFSP.mkdir(input.directory, { recursive: true, mode: 0o700 }); +}): Promise { + const created = await NodeFSP.mkdir(input.directory, { recursive: true, mode: 0o700 }); const stat = await NodeFSP.lstat(input.directory); if (!stat.isDirectory() || stat.isSymbolicLink()) { throw new Error(`${input.directory} is not a directory.`); @@ -84,28 +89,43 @@ async function prepareUnixSocket(input: { throw new Error(`${input.directory} is owned by another user.`); } await NodeFSP.chmod(input.directory, 0o700); - await NodeFSP.unlink(input.address).catch((error: NodeJS.ErrnoException) => { - if (error.code !== "ENOENT") throw error; - }); + return created !== undefined; +} + +async function inodeAt(path: string): Promise { + return NodeFSP.lstat(path).then( + (stat) => stat.ino, + (error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return null; + throw error; + }, + ); +} + +function closeServer(server: NodeNet.Server): Promise { + return new Promise((resolve) => server.close(() => resolve())); } +/** + * Serves `t3 app` requests on the local control address until `close`. + * + * Two desktop apps can share one state dir, for example nightly and a preview + * build. They share one socket path, so on Unix: + * - The newest app takes the path over. + * - `close` removes the socket file only while it is still this app's socket. + * - An app binds the path again when it is gone, for example after the app that + * took it over quits. + */ export async function startDesktopAppControlServer(input: { readonly address: string; readonly directory: string | null; readonly userId: number | undefined; readonly handle: (request: DesktopAppActivationRequest) => Promise; readonly cancel: (requestId: string) => void; + readonly onReclaimError: (error: unknown) => void; }): Promise { - if (input.directory !== null) { - await prepareUnixSocket({ - address: input.address, - directory: input.directory, - userId: input.userId, - }); - } - const sockets = new Set(); - const server = NodeNet.createServer((socket) => { + const handleConnection = (socket: NodeNet.Socket) => { sockets.add(socket); socket.setEncoding("utf8"); let buffer = ""; @@ -160,40 +180,116 @@ export async function startDesktopAppControlServer(input: { sockets.delete(socket); if (!responseSent && activeRequestId !== null) input.cancel(activeRequestId); }); - }); + }; - await new Promise((resolve, reject) => { - const onError = (error: Error) => { - server.removeListener("listening", onListening); - reject(error); - }; - const onListening = () => { - server.removeListener("error", onError); - resolve(); - }; - server.once("error", onError); - server.once("listening", onListening); - server.listen(input.address); - }); + const listen = (address: string) => + new Promise((resolve, reject) => { + const server = NodeNet.createServer(handleConnection); + const onError = (error: Error) => { + server.removeListener("listening", onListening); + reject(error); + }; + const onListening = () => { + server.removeListener("error", onError); + resolve(server); + }; + server.once("error", onError); + server.once("listening", onListening); + server.listen(address); + }); - try { - if (input.directory !== null) { - await NodeFSP.chmod(input.address, 0o600); + // Closing a Unix socket server unlinks the path it was bound to, even when + // another app's socket lives there now. Bind a staging path and move it onto + // the address instead, so a later close only unlinks the staging path, which + // is already gone. `rename` takes the address over in one step. `link` claims + // it only while it is free, and fails with EEXIST otherwise. + const bindUnix = async (directory: string, mode: "take-over" | "claim-free") => { + const staging = NodePath.join(directory, `${NodeCrypto.randomBytes(6).toString("hex")}.tmp`); + const server = await listen(staging); + try { + await NodeFSP.chmod(staging, 0o600); + const inode = await inodeAt(staging); + if (mode === "take-over") { + await NodeFSP.rename(staging, input.address); + } else { + await NodeFSP.link(staging, input.address); + await NodeFSP.unlink(staging); + } + return { server, inode }; + } catch (error) { + await closeServer(server); + throw error; } - } catch (error) { - await new Promise((resolve) => server.close(() => resolve())); - throw error; + }; + + let server: NodeNet.Server; + let inode: number | null = null; + if (input.directory === null) { + // Named pipes close with the app that owns them, so no other app can remove this one. + server = await listen(input.address); + } else { + await prepareUnixDirectory({ directory: input.directory, userId: input.userId }); + ({ server, inode } = await bindUnix(input.directory, "take-over")); } let closed = false; + const reclaimOnce = async () => { + // Never replace a socket that exists, so two apps cannot trade the path back and forth. + if (closed || input.directory === null || (await inodeAt(input.address)) !== null) return; + if (await prepareUnixDirectory({ directory: input.directory, userId: input.userId })) { + // A watch follows the directory's inode, so a recreated directory needs a new one. + watchDirectory(input.directory); + } + const next = await bindUnix(input.directory, "claim-free").catch( + (error: NodeJS.ErrnoException) => { + // Another app bound the address first. + if (error.code === "EEXIST") return null; + throw error; + }, + ); + if (next === null) return; + const previous = server; + ({ server, inode } = next); + previous.close(); + }; + let pendingReclaim = Promise.resolve(); + const reclaim = () => { + const run = pendingReclaim.then(reclaimOnce); + pendingReclaim = run.catch(() => undefined); + return run; + }; + let watcher: NodeFS.FSWatcher | null = null; + const watchDirectory = (directory: string) => { + watcher?.close(); + watcher = null; + try { + watcher = NodeFS.watch(directory, { persistent: false }, () => { + reclaim().catch(input.onReclaimError); + }); + watcher.on("error", input.onReclaimError); + } catch (error) { + // The socket still works without a watcher. It only cannot recover after removal. + input.onReclaimError(error); + } + }; + if (input.directory !== null) { + watchDirectory(input.directory); + // Catch a removal that happened before the watcher started. + reclaim().catch(input.onReclaimError); + } + return { + reclaim, close: async () => { if (closed) return; closed = true; + // A running reclaim can replace the watcher, so close the watcher after it. + await pendingReclaim; + watcher?.close(); for (const socket of sockets) socket.destroy(); - await new Promise((resolve) => server.close(() => resolve())); + await closeServer(server); server.removeAllListeners(); - if (input.directory !== null) { + if (inode !== null && (await inodeAt(input.address)) === inode) { await NodeFSP.unlink(input.address).catch((error: NodeJS.ErrnoException) => { if (error.code !== "ENOENT") throw error; }); @@ -258,6 +354,10 @@ export const make = Effect.gen(function* () { userId, handle: (request) => broker.request(request), cancel: (requestId) => broker.cancel(requestId), + onReclaimError: (cause) => + void runPromise( + logWarning("failed to restore the desktop app control socket", { cause }), + ), }), catch: (cause) => new DesktopAppActivationStartError({ address: address.address, cause }), }), diff --git a/apps/desktop/src/app/DesktopAppIdentity.test.ts b/apps/desktop/src/app/DesktopAppIdentity.test.ts index ce0c7d013a99..295a07355acf 100644 --- a/apps/desktop/src/app/DesktopAppIdentity.test.ts +++ b/apps/desktop/src/app/DesktopAppIdentity.test.ts @@ -76,7 +76,7 @@ const makeAssetsLayer = (png: Option.Option) => icns: Option.none(), png, }), - resolveResourcePath: () => Effect.succeed(Option.none()), + resolveResourcePath: () => Effect.succeedNone, } satisfies DesktopAssets.DesktopAssets["Service"]); const makeEnvironmentLayer = (overrides: TestEnvironmentInput = {}) => { diff --git a/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts b/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts index fdc69841343c..5cb7aafbbd20 100644 --- a/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts +++ b/apps/desktop/src/app/DesktopConnectionCatalogStore.test.ts @@ -46,7 +46,7 @@ function makeSafeStorageLayer(available: boolean, failDecrypt: Ref.Ref return decoded.slice("encrypted:".length); }); }, - selectedStorageBackend: Effect.succeed(Option.none()), + selectedStorageBackend: Effect.succeedNone, } satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]); } diff --git a/apps/desktop/src/app/DesktopConnectionCatalogStore.ts b/apps/desktop/src/app/DesktopConnectionCatalogStore.ts index e2e0cd413bd9..46b2546e681c 100644 --- a/apps/desktop/src/app/DesktopConnectionCatalogStore.ts +++ b/apps/desktop/src/app/DesktopConnectionCatalogStore.ts @@ -205,7 +205,7 @@ const readDocument = ( raw === null ? Effect.succeed(Option.none()) : decodeEncryptedConnectionCatalogDocumentJson(raw).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.mapError( (cause) => new DesktopConnectionCatalogStoreDocumentDecodeError({ diff --git a/apps/desktop/src/app/DesktopEnvironment.ts b/apps/desktop/src/app/DesktopEnvironment.ts index 0d04f0db4e2c..1ae98e1b77b9 100644 --- a/apps/desktop/src/app/DesktopEnvironment.ts +++ b/apps/desktop/src/app/DesktopEnvironment.ts @@ -67,6 +67,8 @@ export class DesktopEnvironment extends Context.Service< readonly clientAssetsDir: string; readonly backendCwd: string; readonly preloadPath: string; + // Preload that turns on the V8 compile cache for the local backend. + readonly compileCachePath: string; readonly appUpdateYmlPath: string; readonly devServerUrl: Option.Option; readonly devRemoteT3ServerEntryPath: Option.Option; @@ -230,6 +232,7 @@ const make = Effect.fn("desktop.environment.make")(function* ( clientAssetsDir: path.join(serverRoot, "apps/server/dist/client"), backendCwd: input.isPackaged ? homeDirectory : appRoot, preloadPath: path.join(input.dirname, "preload.cjs"), + compileCachePath: path.join(input.dirname, "compileCache.cjs"), appUpdateYmlPath: input.isPackaged ? path.join(resourcesPath, "app-update.yml") : path.join(input.appPath, "dev-app-update.yml"), diff --git a/apps/desktop/src/app/DesktopObservability.test.ts b/apps/desktop/src/app/DesktopObservability.test.ts index c91127241968..f65cc6bd5959 100644 --- a/apps/desktop/src/app/DesktopObservability.test.ts +++ b/apps/desktop/src/app/DesktopObservability.test.ts @@ -427,6 +427,102 @@ describe("DesktopObservability", () => { ); }); + it.effect("exports to an OTEL endpoint over Settings, with its own headers and protocol", () => { + const requests: Array = []; + return Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const baseDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-desktop-observability-test-", + }); + const environmentLayer = makeEnvironmentLayer(baseDir, true, { + T3CODE_OTLP_HEADERS: "x-scope=desktop", + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + OTEL_EXPORTER_OTLP_HEADERS: "x-otel=desktop", + OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: "http/json", + }); + yield* writeObservabilitySettings(environmentLayer, { + otlpLogsUrl: "https://settings.example.com/v1/logs", + }); + + yield* Effect.scoped( + Effect.logInfo("desktop otel export").pipe( + Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), + ), + ); + + assert.lengthOf(requests, 1); + const [request] = requests; + assert.strictEqual(request?.url, "https://collector.example.com/v1/logs"); + assert.strictEqual(request?.headers["x-otel"], "desktop"); + assert.strictEqual(request?.headers["x-scope"], undefined); + assert.strictEqual(request?.headers["content-type"], "application/json"); + }).pipe( + Effect.scoped, + Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))), + ); + }); + + it.effect("keeps its service name while OTEL resource attributes add dimensions", () => { + const requests: Array = []; + return Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const baseDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-desktop-observability-test-", + }); + const environmentLayer = makeEnvironmentLayer(baseDir, true, { + T3CODE_OTLP_LOGS_URL: "https://collector.example.com/v1/logs", + OTEL_SERVICE_NAME: "renamed", + OTEL_RESOURCE_ATTRIBUTES: + "service.name=renamed,service.namespace=renamed,deployment.environment.name=development", + }); + + yield* Effect.scoped( + Effect.logInfo("desktop service name").pipe( + Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), + ), + ); + + assert.lengthOf(requests, 1); + const body = requests[0]?.body ?? ""; + assert.include(body, '"stringValue":"t3code-desktop"'); + assert.include(body, "deployment.environment.name"); + assert.include(body, '"key":"service.namespace","value":{"stringValue":"t3code"}'); + assert.notInclude(body, "renamed"); + }).pipe( + Effect.scoped, + Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))), + ); + }); + + it.effect("exports nothing to Settings for logs an unusable OTEL endpoint claimed", () => { + const requests: Array = []; + return Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const baseDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-desktop-observability-test-", + }); + const environmentLayer = makeEnvironmentLayer(baseDir, true, { + T3CODE_OTLP_HEADERS: "x-scope=desktop", + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", + OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: "grpc", + }); + yield* writeObservabilitySettings(environmentLayer, { + otlpLogsUrl: "https://settings.example.com/v1/logs", + }); + + yield* Effect.scoped( + Effect.logInfo("desktop otel off").pipe( + Effect.provide(DesktopObservability.layer.pipe(Layer.provideMerge(environmentLayer))), + ), + ); + + assert.lengthOf(requests, 0); + }).pipe( + Effect.scoped, + Effect.provide(Layer.mergeAll(NodeServices.layer, collectorLayer(requests))), + ); + }); + it.effect("exports kill switch warnings through the configured logger", () => { const requests: Array = []; return Effect.gen(function* () { diff --git a/apps/desktop/src/app/DesktopObservability.ts b/apps/desktop/src/app/DesktopObservability.ts index bb807ab95684..2d492afa7ac5 100644 --- a/apps/desktop/src/app/DesktopObservability.ts +++ b/apps/desktop/src/app/DesktopObservability.ts @@ -38,8 +38,8 @@ const DESKTOP_LOG_FILE_MAX_BYTES = 10 * 1024 * 1024; const DESKTOP_LOG_FILE_MAX_FILES = 10; const DESKTOP_BACKEND_CHILD_LOG_FIBER_ID = "#backend-child"; const DESKTOP_TRACE_BATCH_WINDOW_MS = 1_000; -/** What the main process calls itself, in the family with `t3-server` and `t3-web`. */ -const DESKTOP_SERVICE_NAME = "t3-desktop"; +/** What the main process calls itself, in the family with `t3code-server` and `t3code-web`. */ +const DESKTOP_SERVICE_NAME = "t3code-desktop"; const DESKTOP_BACKEND_OUTPUT_BUFFER_MAX_BYTES = 1024 * 1024; const DESKTOP_BACKEND_OUTPUT_BUFFER_MAX_CHUNKS = 256; @@ -380,6 +380,7 @@ const resolveOtlpExport = Effect.gen(function* () { namedProtocol: Option.getOrUndefined(environment.otlpProtocol), serviceName: DESKTOP_SERVICE_NAME, runtimeAttributes: { + "service.namespace": "t3code", "service.runtime": "desktop", "service.mode": environment.isDevelopment ? "development" : "packaged", }, diff --git a/apps/desktop/src/app/DesktopOtlpExport.test.ts b/apps/desktop/src/app/DesktopOtlpExport.test.ts index 34bbfea059a6..285b30030906 100644 --- a/apps/desktop/src/app/DesktopOtlpExport.test.ts +++ b/apps/desktop/src/app/DesktopOtlpExport.test.ts @@ -36,7 +36,7 @@ const resolve = ( namedExportIntervalMs: overrides.namedExportIntervalMs, namedHeaders: overrides.namedHeaders, namedProtocol: overrides.namedProtocol, - serviceName: "t3-desktop", + serviceName: "t3code-desktop", runtimeAttributes: { "service.runtime": "desktop", "service.mode": "development" }, }), ), @@ -280,7 +280,7 @@ describe("resolveDesktopOtlpExport", () => { OTEL_SERVICE_VERSION: "1.2.3", OTEL_RESOURCE_ATTRIBUTES: "deployment.environment=lab,service.runtime=t3-server", }); - assert.strictEqual(resolved.resource.serviceName, "t3-desktop"); + assert.strictEqual(resolved.resource.serviceName, "t3code-desktop"); assert.strictEqual(resolved.resource.serviceVersion, "1.2.3"); assert.strictEqual(resolved.resource.attributes["deployment.environment"], "lab"); assert.strictEqual(resolved.resource.attributes["service.runtime"], "desktop"); @@ -293,7 +293,7 @@ describe("resolveDesktopOtlpExport", () => { OTEL_SERVICE_NAME: "some-other-app", OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com", }); - assert.strictEqual(resolved.resource.serviceName, "t3-desktop"); + assert.strictEqual(resolved.resource.serviceName, "t3code-desktop"); assert.lengthOf(resolved.warnings, 1); assert.include(resolved.warnings[0] ?? "", "OTEL_SERVICE_NAME was ignored"); }), @@ -304,7 +304,7 @@ describe("resolveDesktopOtlpExport", () => { const resolved = yield* resolve({ OTEL_RESOURCE_ATTRIBUTES: "service.name=some-other-app,host.name=lab-01", }); - assert.strictEqual(resolved.resource.serviceName, "t3-desktop"); + assert.strictEqual(resolved.resource.serviceName, "t3code-desktop"); assert.strictEqual(resolved.resource.attributes["service.name"], undefined); assert.strictEqual(resolved.resource.attributes["host.name"], "lab-01"); assert.include(resolved.warnings[0] ?? "", "service.name was ignored"); @@ -314,7 +314,7 @@ describe("resolveDesktopOtlpExport", () => { it.effect("names itself even when the environment says nothing", () => Effect.gen(function* () { const resolved = yield* resolve({}); - assert.strictEqual(resolved.resource.serviceName, "t3-desktop"); + assert.strictEqual(resolved.resource.serviceName, "t3code-desktop"); assert.strictEqual(resolved.resource.serviceVersion, undefined); }), ); diff --git a/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts b/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts index cf9eb6b07064..9ecada2b5af3 100644 --- a/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts +++ b/apps/desktop/src/backend/DesktopBackendConfiguration.test.ts @@ -235,6 +235,11 @@ describe("DesktopBackendConfiguration", () => { const second = yield* configuration.resolvePrimary; assert.equal(first.executablePath, process.execPath); + assert.deepEqual(first.args.slice(0, 3), [ + "--require", + environment.compileCachePath, + environment.backendEntryPath, + ]); assert.equal(first.entryPath, environment.backendEntryPath); assert.equal(first.cwd, environment.backendCwd); assert.equal(first.captureOutput, true); @@ -775,12 +780,11 @@ describe("DesktopBackendConfiguration", () => { const config = yield* configuration.resolveWsl({ port: 5050, distro: null }); // No settings.json exists here: the endpoints come from the desktop - // process's env, which a WSL child cannot inherit, so the bootstrap - // has to carry them or log export stays off inside the distro. + // environment, and the bootstrap carries them for a WSL child that + // lacks the variables. assert.equal(config.bootstrap.otlpTracesUrl, "http://127.0.0.1:4318/v1/traces"); assert.equal(config.bootstrap.otlpMetricsUrl, "http://127.0.0.1:4318/v1/metrics"); assert.equal(config.bootstrap.otlpLogsUrl, "http://127.0.0.1:4318/v1/logs"); - assert.notInclude(config.env.WSLENV ?? "", "T3CODE_OTLP_LOGS_URL"); }).pipe( Effect.provide( DesktopBackendConfiguration.layer.pipe( @@ -927,6 +931,8 @@ describe("DesktopBackendConfiguration", () => { const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration; const config = yield* configuration.resolvePrimary; assert.equal(config.captureOutput, true); + // Dev never shares the prod compile cache. + assert.notInclude(config.args, "--require"); }).pipe( Effect.provide( DesktopBackendConfiguration.layer.pipe( @@ -1099,6 +1105,73 @@ describe("DesktopBackendConfiguration", () => { }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), ); + it.effect( + "resolveWsl carries the standard OTLP endpoint, headers, and protocol into the distro", + () => + Effect.gen(function* () { + const fileSystem = yield* FileSystem.FileSystem; + const baseDir = yield* fileSystem.makeTempDirectoryScoped({ + prefix: "t3-desktop-backend-config-test-", + }); + + const standard = { + OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com:4318/base?api_key=secret", + OTEL_EXPORTER_OTLP_LOGS_HEADERS: "authorization=Bearer%20token", + T3CODE_OTLP_TRACES_URL: "http://t3.example.com:4318/v1/traces", + }; + const previousWslEnv = process.env.WSLENV; + // A developer's own OTLP variables would be forwarded too. + const ambientOtel = Object.entries(process.env).filter( + ([name]) => name.startsWith("OTEL_") || name.startsWith("T3CODE_OTLP_"), + ); + try { + for (const [name] of ambientOtel) delete process.env[name]; + delete process.env.WSLENV; + Object.assign(process.env, standard); + + yield* Effect.gen(function* () { + const configuration = yield* DesktopBackendConfiguration.DesktopBackendConfiguration; + const config = yield* configuration.resolveWsl({ port: 5050, distro: null }); + + assert.equal( + config.env.OTEL_EXPORTER_OTLP_ENDPOINT, + "https://collector.example.com:4318/base?api_key=secret", + ); + assert.equal( + config.env.OTEL_EXPORTER_OTLP_LOGS_HEADERS, + "authorization=Bearer%20token", + ); + // Without a flag, WSL passes the values through untranslated. + const wslEnv = (config.env.WSLENV ?? "").split(":"); + assert.include(wslEnv, "OTEL_EXPORTER_OTLP_ENDPOINT"); + assert.include(wslEnv, "OTEL_EXPORTER_OTLP_LOGS_HEADERS"); + assert.equal(config.env.T3CODE_OTLP_TRACES_URL, "http://t3.example.com:4318/v1/traces"); + assert.include(wslEnv, "T3CODE_OTLP_TRACES_URL"); + }).pipe( + Effect.provide( + DesktopBackendConfiguration.layer.pipe( + Layer.provideMerge(serverExposureLayer), + Layer.provideMerge(DesktopAppSettings.layerTest()), + Layer.provideMerge(DesktopWslServerTree.layerTest()), + Layer.provideMerge( + DesktopWslEnvironment.layerTest({ + isAvailable: true, + windowsToWslPath: () => Option.some("/mnt/c/repo/apps/server/src/index.ts"), + getDistroIp: () => Option.some("172.27.0.99"), + }), + ), + Layer.provideMerge(makeEnvironmentLayer(baseDir, { platform: "win32" })), + ), + ), + ); + } finally { + for (const name of Object.keys(standard)) delete process.env[name]; + restoreEnv("WSLENV", previousWslEnv); + for (const [name, value] of ambientOtel) restoreEnv(name, value); + } + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), + ); + it.effect("resolveWsl preserves existing WSLENV entries when forwarding backend secrets", () => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; @@ -1111,7 +1184,10 @@ describe("DesktopBackendConfiguration", () => { const previousAnthropicKey = process.env.ANTHROPIC_API_KEY; const previousOtlpHeaders = process.env.T3CODE_OTLP_HEADERS; const previousOtlpProtocol = process.env.T3CODE_OTLP_PROTOCOL; + // A developer's own OTEL_* variables would be forwarded too. + const ambientOtel = Object.entries(process.env).filter(([name]) => name.startsWith("OTEL_")); try { + for (const [name] of ambientOtel) delete process.env[name]; process.env.WSLENV = "GOPATH/p:OPENAI_API_KEY/u:EMPTY::AZURE_DEVOPS_EXT_PAT/u"; process.env.OPENAI_API_KEY = "openai-key"; process.env.ANTHROPIC_API_KEY = "anthropic-key"; @@ -1168,6 +1244,7 @@ describe("DesktopBackendConfiguration", () => { restoreEnv("ANTHROPIC_API_KEY", previousAnthropicKey); restoreEnv("T3CODE_OTLP_HEADERS", previousOtlpHeaders); restoreEnv("T3CODE_OTLP_PROTOCOL", previousOtlpProtocol); + for (const [name, value] of ambientOtel) restoreEnv(name, value); } }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), ); diff --git a/apps/desktop/src/backend/DesktopBackendConfiguration.ts b/apps/desktop/src/backend/DesktopBackendConfiguration.ts index bc926e399ab9..50fb49e43173 100644 --- a/apps/desktop/src/backend/DesktopBackendConfiguration.ts +++ b/apps/desktop/src/backend/DesktopBackendConfiguration.ts @@ -90,8 +90,9 @@ const DESKTOP_BACKEND_ENV_NAMES = [ ] as const; // Env vars that the WSL backend needs but Windows process.env won't forward -// across the wsl.exe boundary without WSLENV. The dev-server URL is handled -// separately via a `--dev-url` CLI flag. +// across the wsl.exe boundary without WSLENV. The dev-server URL travels as +// the `--dev-url` CLI flag instead. +// // Every name the server reads to decide what it exports and where. These cross // without a WSLENV flag, so their values arrive verbatim; only a `/p`, `/l`, // `/u`, or `/w` entry is path-translated, which is what makes URL-shaped names @@ -113,7 +114,6 @@ const OBSERVABILITY_FORWARDED_ENV_NAMES = [ "T3CODE_OTLP_HEADERS", "T3CODE_OTLP_PROTOCOL", "T3CODE_OTLP_EXPORT_INTERVAL_MS", - "T3CODE_OTLP_SERVICE_NAME", "OTEL_SDK_DISABLED", "OTEL_EXPORTER_OTLP_ENDPOINT", "OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", @@ -243,11 +243,11 @@ const readPersistedBackendObservabilitySettings = Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const environment = yield* DesktopEnvironment.DesktopEnvironment; const raw = yield* fileSystem.readFileString(environment.serverSettingsPath).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ PlatformError: (cause) => cause.reason._tag === "NotFound" - ? Effect.succeed(Option.none()) + ? Effect.succeedNone : logBackendObservabilitySettingsReadFailure(environment.serverSettingsPath, cause).pipe( Effect.as(Option.none()), ), @@ -265,12 +265,11 @@ const readPersistedBackendObservabilitySettings = Effect.gen(function* () { }; }); -// The bootstrap is the only channel that carries an OTLP endpoint to every -// backend. A Windows-native child inherits the desktop process's env, but a -// WSL child gets nothing across wsl.exe that WSLENV does not declare, and -// WSLENV translation of URL-shaped values is unreliable, so the endpoints are -// deliberately not forwarded that way. Env beats the persisted settings file, -// matching the precedence resolveServerConfig and DesktopObservability apply. +// The bootstrap carries the OTLP endpoints to every backend, including a WSL +// child that lacks the variables. The T3 URLs also travel as variables in +// WSL_FORWARDED_ENV_NAMES so they outrank a forwarded OTEL endpoint. Env beats +// the persisted settings file, matching the precedence resolveServerConfig and +// DesktopObservability apply. const readBackendObservabilitySettings = Effect.gen(function* () { const environment = yield* DesktopEnvironment.DesktopEnvironment; const persisted = yield* readPersistedBackendObservabilitySettings; @@ -594,7 +593,16 @@ const resolvePrimaryStartConfig = Effect.fn("desktop.backendConfiguration.resolv return { executablePath: process.execPath, - args: [environment.backendEntryPath, "--bootstrap-fd", "3"], + // Packaged builds only, so a dev instance never shares the cache with the + // prod app it is often run from. `--require` rather than NODE_COMPILE_CACHE, + // so the setting does not leak into the provider and terminal processes + // the backend starts. + args: [ + ...(environment.isPackaged ? ["--require", environment.compileCachePath] : []), + environment.backendEntryPath, + "--bootstrap-fd", + "3", + ], entryPath: environment.backendEntryPath, cwd: environment.backendCwd, env: { @@ -777,10 +785,8 @@ const resolveWslStartConfig = Effect.fn("desktop.backendConfiguration.resolveWsl }; // Forward the dev-server URL as an explicit CLI flag so the WSL backend's - // config resolution lands in dev/ instead of userdata/. Inheriting through - // WSLENV is unreliable in practice (URL-shaped values with colons / - // slashes get translated unpredictably depending on flags), and the - // packaged build leaves devServerUrl as None anyway. + // config resolution lands in dev/ instead of userdata/. The packaged build + // leaves devServerUrl as None. const devUrlArgs = Option.match(environment.devServerUrl, { onNone: () => [] as ReadonlyArray, onSome: (url) => ["--dev-url", url.href], diff --git a/apps/desktop/src/backend/DesktopBackendManager.test.ts b/apps/desktop/src/backend/DesktopBackendManager.test.ts index df2001f1ac20..901d9f4a2708 100644 --- a/apps/desktop/src/backend/DesktopBackendManager.test.ts +++ b/apps/desktop/src/backend/DesktopBackendManager.test.ts @@ -162,7 +162,7 @@ function makeTestInstance(input: MakeInstanceInput) { forInstance: () => Effect.succeed(stubLog), } satisfies DesktopObservability.DesktopBackendOutputLogFactory["Service"]), Layer.succeed(DesktopTelemetryPublisher.DesktopTelemetryPublisher, { - latest: Effect.succeed(Option.none()), + latest: Effect.succeedNone, changes: Stream.empty, encoded: input.desktopTelemetryStream ?? Stream.empty, handleControlForSource: () => Effect.void, @@ -1545,7 +1545,7 @@ describe("DesktopBackendManager", () => { const mockPool = Layer.succeed(DesktopBackendPool.DesktopBackendPool, { list: Effect.succeed([instance1, instance2]), - get: () => Effect.succeed(Option.none()), + get: () => Effect.succeedNone, primary: Effect.die(new Error("primary not implemented")), register: () => Effect.die(new Error("register not implemented")), unregister: () => Effect.die(new Error("unregister not implemented")), diff --git a/apps/desktop/src/backend/DesktopBackendPool.test.ts b/apps/desktop/src/backend/DesktopBackendPool.test.ts index 7859223161b7..8373437ae312 100644 --- a/apps/desktop/src/backend/DesktopBackendPool.test.ts +++ b/apps/desktop/src/backend/DesktopBackendPool.test.ts @@ -67,7 +67,7 @@ function makePoolLayer( } satisfies DesktopObservability.DesktopBackendOutputLogShape), } satisfies DesktopObservability.DesktopBackendOutputLogFactory["Service"]), Layer.succeed(DesktopTelemetryPublisher.DesktopTelemetryPublisher, { - latest: Effect.succeed(Option.none()), + latest: Effect.succeedNone, changes: Stream.empty, encoded: Stream.empty, handleControlForSource: () => Effect.void, @@ -135,9 +135,7 @@ describe("DesktopBackendPool", () => { it.effect("layerTest dies when no instances are supplied", () => Effect.exit( - Effect.gen(function* () { - yield* DesktopBackendPool.DesktopBackendPool; - }).pipe(Effect.provide(DesktopBackendPool.layerTest([]))), + DesktopBackendPool.DesktopBackendPool.pipe(Effect.provide(DesktopBackendPool.layerTest([]))), ).pipe(Effect.map((exit) => assert.equal(exit._tag, "Failure"))), ); diff --git a/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.test.ts b/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.test.ts index e7a58baef140..ed71bd332d24 100644 --- a/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.test.ts +++ b/apps/desktop/src/backend/DesktopLocalEnvironmentAuth.test.ts @@ -1,7 +1,6 @@ import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; import * as HttpClient from "effect/unstable/http/HttpClient"; import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse"; @@ -60,7 +59,7 @@ describe("DesktopLocalEnvironmentAuth", () => { { id: PRIMARY_LOCAL_ENVIRONMENT_ID, label: Effect.succeed("Windows"), - currentConfig: Effect.succeed(Option.some(config)), + currentConfig: Effect.succeedSome(config), }, ]), } as unknown as DesktopBackendPool.DesktopBackendPool["Service"]); diff --git a/apps/desktop/src/boot.ts b/apps/desktop/src/boot.ts new file mode 100644 index 000000000000..c9eb35b24e5f --- /dev/null +++ b/apps/desktop/src/boot.ts @@ -0,0 +1,4 @@ +// Packaged app entry. Enables the compile cache before the main bundle loads, +// so the cache also covers main.cjs itself. +require("./compileCache.cjs"); +require("./main.cjs"); diff --git a/apps/desktop/src/compileCache.ts b/apps/desktop/src/compileCache.ts new file mode 100644 index 000000000000..20f942db7db9 --- /dev/null +++ b/apps/desktop/src/compileCache.ts @@ -0,0 +1,28 @@ +// @effect-diagnostics nodeBuiltinImport:off +// Runs before any Effect runtime exists, so it stays on Node built-ins. +import * as NodeModule from "node:module"; +import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; + +// Turns on Node's on-disk V8 code cache for every module loaded after this one, +// so later launches skip recompiling the large main and server bundles. +// Packaged builds only: boot.ts loads it for the main process, and the local +// backend gets it with `--require`. Dev launches main.cjs directly and skips it. +// Linux uses the user's cache dir because /tmp is shared between users; the +// macOS and Windows temp dirs are already per user. +// +// Skipped under AppImage: it mounts the app at a new /tmp/.mount_* path each +// launch, and Node keys entries by path, so every launch would miss and leave +// another copy behind. The backend inherits APPIMAGE, so this covers it too. +try { + if (!process.env.APPIMAGE) { + const cacheRoot = + // oxlint-disable-next-line t3code/no-global-process-runtime -- Loads before any Effect runtime. + process.platform === "linux" + ? process.env.XDG_CACHE_HOME || NodePath.join(NodeOS.homedir(), ".cache") + : NodeOS.tmpdir(); + NodeModule.enableCompileCache(NodePath.join(cacheRoot, "t3code", "compile-cache")); + } +} catch { + // The cache is only a speedup. Never let it stop the app from starting. +} diff --git a/apps/desktop/src/electron/ElectronMenu.ts b/apps/desktop/src/electron/ElectronMenu.ts index ce9e0fb48979..401e0c27ccd8 100644 --- a/apps/desktop/src/electron/ElectronMenu.ts +++ b/apps/desktop/src/electron/ElectronMenu.ts @@ -80,6 +80,7 @@ function normalizeContextMenuItems(source: readonly ContextMenuItem[]): ContextM destructive: sourceItem.destructive === true, disabled: sourceItem.disabled === true, ...(sourceItem.separatorBefore === true ? { separatorBefore: true } : {}), + ...(typeof sourceItem.checked === "boolean" ? { checked: sourceItem.checked } : {}), }; if (sourceItem.children) { @@ -168,6 +169,7 @@ export const make = Effect.gen(function* () { const itemOption: Electron.MenuItemConstructorOptions = { label: item.label, enabled: !item.disabled, + ...(typeof item.checked === "boolean" ? { type: "checkbox", checked: item.checked } : {}), }; if (item.children && item.children.length > 0) { itemOption.submenu = buildTemplate(item.children, complete); @@ -224,7 +226,7 @@ export const make = Effect.gen(function* () { Effect.callback>((resume) => { const normalizedItems = normalizeContextMenuItems(input.items); if (normalizedItems.length === 0) { - resume(Effect.succeed(Option.none())); + resume(Effect.succeedNone); return; } diff --git a/apps/desktop/src/electron/ElectronProtocol.ts b/apps/desktop/src/electron/ElectronProtocol.ts index c3cecf88b2e4..30feb85043f8 100644 --- a/apps/desktop/src/electron/ElectronProtocol.ts +++ b/apps/desktop/src/electron/ElectronProtocol.ts @@ -126,6 +126,9 @@ function registerDesktopSchemePrivilegesSync(): void { supportFetchAPI: true, corsEnabled: true, stream: true, + // Custom schemes skip Chromium's V8 code cache unless they opt in. + // Dev stays off: Vite serves changing code at stable URLs. + codeCache: true, }, }, { diff --git a/apps/desktop/src/ipc/methods/snapShot.test.ts b/apps/desktop/src/ipc/methods/snapShot.test.ts index 7e20890ef554..138bcbc36d30 100644 --- a/apps/desktop/src/ipc/methods/snapShot.test.ts +++ b/apps/desktop/src/ipc/methods/snapShot.test.ts @@ -51,7 +51,7 @@ describe("window capture IPC", () => { Effect.provide( Layer.mergeAll( Layer.succeed(ElectronWindow.ElectronWindow, { - main: Effect.succeed(Option.some({ webContents: { id: 7 } })), + main: Effect.succeedSome({ webContents: { id: 7 } }), } as ElectronWindow.ElectronWindow["Service"]), Layer.succeed(DesktopSnapShot.DesktopSnapShot, { previewConfig: () => @@ -87,7 +87,7 @@ describe("window capture IPC", () => { Effect.provide( Layer.mergeAll( Layer.succeed(ElectronWindow.ElectronWindow, { - main: Effect.succeed(Option.some({ webContents: { id: 7 } })), + main: Effect.succeedSome({ webContents: { id: 7 } }), } as ElectronWindow.ElectronWindow["Service"]), Layer.succeed(DesktopSnapShot.DesktopSnapShot, { state: Effect.succeed({ @@ -120,7 +120,7 @@ describe("window capture IPC", () => { Effect.provide( Layer.mergeAll( Layer.succeed(ElectronWindow.ElectronWindow, { - main: Effect.succeed(Option.some({ webContents: { id: 7 } })), + main: Effect.succeedSome({ webContents: { id: 7 } }), } as ElectronWindow.ElectronWindow["Service"]), Layer.succeed(DesktopSnapShot.DesktopSnapShot, { state: Effect.succeed({ linuxBackend: "niri" }), @@ -156,13 +156,11 @@ describe("window capture IPC", () => { Layer.succeed( ElectronWindow.ElectronWindow, ElectronWindow.ElectronWindow.of({ - main: Effect.succeed( - Option.some({ - getBounds: () => ({ x: 100, y: 80, width: 1_000, height: 700 }), - getContentBounds: () => ({ x: 100, y: 118, width: 1_000, height: 662 }), - webContents, - }), - ), + main: Effect.succeedSome({ + getBounds: () => ({ x: 100, y: 80, width: 1_000, height: 700 }), + getContentBounds: () => ({ x: 100, y: 118, width: 1_000, height: 662 }), + webContents, + }), } as ElectronWindow.ElectronWindow["Service"]), ), Layer.succeed( @@ -220,7 +218,7 @@ describe("window capture IPC", () => { Layer.succeed( ElectronWindow.ElectronWindow, ElectronWindow.ElectronWindow.of({ - main: Effect.succeed(Option.some({ webContents })), + main: Effect.succeedSome({ webContents }), } as ElectronWindow.ElectronWindow["Service"]), ), Layer.succeed( @@ -256,7 +254,7 @@ describe("window capture IPC", () => { Effect.provideService( ElectronWindow.ElectronWindow, ElectronWindow.ElectronWindow.of({ - main: Effect.succeed(Option.some({ webContents: { id: 7 } })), + main: Effect.succeedSome({ webContents: { id: 7 } }), } as ElectronWindow.ElectronWindow["Service"]), ), Effect.provideService(DesktopSnapShot.DesktopSnapShot, null as never), @@ -277,7 +275,7 @@ describe("window capture IPC", () => { Effect.provide( Layer.mergeAll( Layer.succeed(ElectronWindow.ElectronWindow, { - main: Effect.succeed(Option.some({ webContents: { id: 7 } })), + main: Effect.succeedSome({ webContents: { id: 7 } }), } as ElectronWindow.ElectronWindow["Service"]), Layer.succeed(DesktopSnapShot.DesktopSnapShot, { setup: (action: string) => @@ -295,7 +293,7 @@ describe("window capture IPC", () => { Layer.succeed( ElectronWindow.ElectronWindow, ElectronWindow.ElectronWindow.of({ - main: Effect.succeed(Option.some({ webContents: { id: 7 } })), + main: Effect.succeedSome({ webContents: { id: 7 } }), } as ElectronWindow.ElectronWindow["Service"]), ), Layer.succeed( @@ -320,7 +318,7 @@ describe("window capture IPC", () => { Layer.succeed( ElectronWindow.ElectronWindow, ElectronWindow.ElectronWindow.of({ - main: Effect.succeed(Option.some({ webContents: { id: 7 } })), + main: Effect.succeedSome({ webContents: { id: 7 } }), } as ElectronWindow.ElectronWindow["Service"]), ), Layer.succeed( diff --git a/apps/desktop/src/ipc/methods/sshEnvironment.ts b/apps/desktop/src/ipc/methods/sshEnvironment.ts index cfb993d35cfa..e9535300104d 100644 --- a/apps/desktop/src/ipc/methods/sshEnvironment.ts +++ b/apps/desktop/src/ipc/methods/sshEnvironment.ts @@ -137,13 +137,11 @@ export const ensureSshEnvironment = DesktopIpc.makeIpcMethod({ }) { const sshEnvironment = yield* DesktopSshEnvironment.DesktopSshEnvironment; return yield* sshEnvironment.ensureEnvironment(target, options).pipe( - Effect.catch((error) => - DesktopSshEnvironment.isDesktopSshPasswordPromptCancellation(error) - ? Effect.succeed({ - type: DesktopSshPasswordPromptCancelledType, - message: error.message, - }) - : Effect.fail(error), + Effect.catchIf(DesktopSshEnvironment.isDesktopSshPasswordPromptCancellation, (error) => + Effect.succeed({ + type: DesktopSshPasswordPromptCancelledType, + message: error.message, + }), ), ); }), diff --git a/apps/desktop/src/ipc/methods/window.test.ts b/apps/desktop/src/ipc/methods/window.test.ts index 764056742372..0b43fa9e6ef9 100644 --- a/apps/desktop/src/ipc/methods/window.test.ts +++ b/apps/desktop/src/ipc/methods/window.test.ts @@ -61,7 +61,7 @@ const defaultWslInstance: DesktopBackendManager.DesktopBackendInstance = { label: Effect.succeed("WSL (default distro)"), start: Effect.void, stop: () => Effect.void, - currentConfig: Effect.succeed(Option.some(readyWslConfig)), + currentConfig: Effect.succeedSome(readyWslConfig), snapshot: Effect.succeed({ desiredRunning: true, ready: true, @@ -101,7 +101,7 @@ describe("getLocalEnvironmentBootstraps", () => { }; const retryingInstance: DesktopBackendManager.DesktopBackendInstance = { ...defaultWslInstance, - currentConfig: Effect.succeed(Option.some(retryingConfig)), + currentConfig: Effect.succeedSome(retryingConfig), snapshot: Effect.succeed({ desiredRunning: true, ready: false, @@ -128,16 +128,14 @@ describe("getLocalEnvironmentBootstraps", () => { it.effect("omits a bounded transient bootstrap after retries stop", () => { const stoppedInstance: DesktopBackendManager.DesktopBackendInstance = { ...defaultWslInstance, - currentConfig: Effect.succeed( - Option.some({ - ...readyWslConfig, - preflightFailure: Option.some({ - reason: "WSL probe timed out", - fatal: false, - retryLimit: 12, - }), + currentConfig: Effect.succeedSome({ + ...readyWslConfig, + preflightFailure: Option.some({ + reason: "WSL probe timed out", + fatal: false, + retryLimit: 12, }), - ), + }), snapshot: Effect.succeed({ desiredRunning: false, ready: false, @@ -163,7 +161,7 @@ describe("getWindowFullscreenState", () => { }).pipe( Effect.provide( Layer.mock(ElectronWindow.ElectronWindow)({ - currentMainOrFirst: Effect.succeed(Option.some(window)), + currentMainOrFirst: Effect.succeedSome(window), }), ), ); @@ -206,7 +204,7 @@ describe("pasteAsText", () => { }).pipe( Effect.provide( Layer.mock(ElectronWindow.ElectronWindow)({ - main: Effect.succeed(Option.some(window)), + main: Effect.succeedSome(window), }), ), ); @@ -219,7 +217,7 @@ describe("pickProjectFavicon", () => { Layer.mergeAll( Layer.mock(ElectronDialog.ElectronDialog)({ pickFiles }), Layer.mock(ElectronWindow.ElectronWindow)({ - focusedMainOrFirst: Effect.succeed(Option.none()), + focusedMainOrFirst: Effect.succeedNone, }), DesktopAppSettings.layerTest(settings), ); diff --git a/apps/desktop/src/ipc/methods/wsl.test.ts b/apps/desktop/src/ipc/methods/wsl.test.ts index bfd1a6e679d9..65137cbbd895 100644 --- a/apps/desktop/src/ipc/methods/wsl.test.ts +++ b/apps/desktop/src/ipc/methods/wsl.test.ts @@ -2,7 +2,6 @@ import { DesktopWslStateSchema } from "@t3tools/contracts"; import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as DesktopEnvironment from "../../app/DesktopEnvironment.ts"; @@ -34,7 +33,7 @@ function makeWslBackendLayer(input: { readonly onReconcile?: Effect.Effect DesktopWslBackend.DesktopWslBackend, DesktopWslBackend.DesktopWslBackend.of({ reconcile: input.onReconcile ?? Effect.void, - lastPreflightError: Effect.succeed(Option.none()), + lastPreflightError: Effect.succeedNone, }), ); } diff --git a/apps/desktop/src/preview/BrowserImport/ChromiumKeys.ts b/apps/desktop/src/preview/BrowserImport/ChromiumKeys.ts index 9310fd1c92f7..9c03523191a3 100644 --- a/apps/desktop/src/preview/BrowserImport/ChromiumKeys.ts +++ b/apps/desktop/src/preview/BrowserImport/ChromiumKeys.ts @@ -315,8 +315,9 @@ export const resolveChromiumKeys = Effect.fn("ChromiumKeys.resolveChromiumKeys") // v10 remains importable when Secret Service is absent or does not // contain a key. An explicit denial/lock/cancel remains a consent // failure rather than being silently downgraded. - Effect.catch((error) => - error.reason === "needsKeychainApproval" ? Effect.fail(error) : Effect.succeed(error), + Effect.catchIf( + (error) => error.reason !== "needsKeychainApproval", + (error) => Effect.succeed(error), ), ) : undefined; diff --git a/apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts b/apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts index 96d27129b78a..0db4fc9096ec 100644 --- a/apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts +++ b/apps/desktop/src/preview/BrowserImport/FirefoxCookies.ts @@ -114,56 +114,55 @@ const expiryToSeconds = (expiry: number, schemaVersion: number): number | undefi return schemaVersion >= FIREFOX_EXPIRY_MILLISECONDS_SCHEMA ? Math.floor(expiry / 1000) : expiry; }; -export const readFirefoxCookies = Effect.fn("FirefoxCookies.readFirefoxCookies")(function* ( - cookieDatabasePath: string, -) { - const snapshotPath = yield* snapshotCookieDatabase(cookieDatabasePath).pipe( - Effect.mapError((cause) => new FirefoxCookieReadError({ cookieDatabasePath, cause })), - ); +export const readFirefoxCookies = Effect.fn("FirefoxCookies.readFirefoxCookies")( + function* (cookieDatabasePath: string) { + const snapshotPath = yield* snapshotCookieDatabase(cookieDatabasePath); - const { rows, schemaVersion } = yield* Effect.gen(function* () { - const sql = yield* SqlClient.SqlClient; - const [versionRow] = yield* decodeUserVersion(yield* sql`pragma user_version`); - const schemaVersion = versionRow?.user_version ?? 0; - const hasRawSameSite = - schemaVersion >= FIREFOX_RAW_SAMESITE_FIRST_SCHEMA && - schemaVersion <= FIREFOX_RAW_SAMESITE_LAST_SCHEMA; - // Only the default container. Firefox isolates cookies per container and - // per private window via `originAttributes` (`^userContextId=2`, - // `^privateBrowsingId=1`); Electron has no equivalent, so importing them - // all would collapse several identities onto one host/name/path and hand - // the profile an arbitrary container's session. - const raw = hasRawSameSite - ? yield* sql` + const { rows, schemaVersion } = yield* Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + const [versionRow] = yield* decodeUserVersion(yield* sql`pragma user_version`); + const schemaVersion = versionRow?.user_version ?? 0; + const hasRawSameSite = + schemaVersion >= FIREFOX_RAW_SAMESITE_FIRST_SCHEMA && + schemaVersion <= FIREFOX_RAW_SAMESITE_LAST_SCHEMA; + // Only the default container. Firefox isolates cookies per container and + // per private window via `originAttributes` (`^userContextId=2`, + // `^privateBrowsingId=1`); Electron has no equivalent, so importing them + // all would collapse several identities onto one host/name/path and hand + // the profile an arbitrary container's session. + const raw = hasRawSameSite + ? yield* sql` select host, name, value, path, expiry, isSecure, isHttpOnly, sameSite, rawSameSite from moz_cookies where originAttributes = '' ` - : yield* sql` + : yield* sql` select host, name, value, path, expiry, isSecure, isHttpOnly, sameSite, null as rawSameSite from moz_cookies where originAttributes = '' `; - return { rows: yield* decodeCookieRows(raw), schemaVersion }; - }).pipe( - Effect.provide(NodeSqliteClient.layer({ filename: snapshotPath, readonly: true })), - Effect.mapError((cause) => new FirefoxCookieReadError({ cookieDatabasePath, cause })), - ); + return { rows: yield* decodeCookieRows(raw), schemaVersion }; + }).pipe(Effect.provide(NodeSqliteClient.layer({ filename: snapshotPath, readonly: true }))); - return rows.map((row) => { - const secure = row.isSecure === 1; - const scope = cookieScope(row.host, row.path, secure); - return { - url: scope.url, - name: row.name, - value: row.value, - domain: scope.domain, - path: row.path, - secure, - httpOnly: row.isHttpOnly === 1, - expirationDate: expiryToSeconds(row.expiry, schemaVersion), - sameSite: sameSiteFromColumn(row.sameSite, row.rawSameSite), - } satisfies ImportedCookie; - }); -}); + return rows.map((row) => { + const secure = row.isSecure === 1; + const scope = cookieScope(row.host, row.path, secure); + return { + url: scope.url, + name: row.name, + value: row.value, + domain: scope.domain, + path: row.path, + secure, + httpOnly: row.isHttpOnly === 1, + expirationDate: expiryToSeconds(row.expiry, schemaVersion), + sameSite: sameSiteFromColumn(row.sameSite, row.rawSameSite), + } satisfies ImportedCookie; + }); + }, + (effect, cookieDatabasePath) => + effect.pipe( + Effect.mapError((cause) => new FirefoxCookieReadError({ cookieDatabasePath, cause })), + ), +); diff --git a/apps/desktop/src/preview/BrowserSession.ts b/apps/desktop/src/preview/BrowserSession.ts index 930c13990d51..664302d7f83b 100644 --- a/apps/desktop/src/preview/BrowserSession.ts +++ b/apps/desktop/src/preview/BrowserSession.ts @@ -231,8 +231,9 @@ export const make = Effect.gen(function* BrowserSessionMake() { getSession, clearCookies: Effect.fn("BrowserSession.clearCookies")(function* (partitions?) { const sessions = yield* SynchronizedRef.get(sessionsRef); - yield* Effect.all( - selectSessions(sessions, partitions).map(([partition, browserSession]) => + yield* Effect.forEach( + selectSessions(sessions, partitions), + ([partition, browserSession]) => Effect.tryPromise({ try: () => browserSession.clearStorageData({ @@ -244,14 +245,14 @@ export const make = Effect.gen(function* BrowserSessionMake() { cause, }), }), - ), { concurrency: "unbounded", discard: true }, ); }), clearCache: Effect.fn("BrowserSession.clearCache")(function* (partitions?) { const sessions = yield* SynchronizedRef.get(sessionsRef); - yield* Effect.all( - selectSessions(sessions, partitions).map(([partition, browserSession]) => + yield* Effect.forEach( + selectSessions(sessions, partitions), + ([partition, browserSession]) => Effect.tryPromise({ try: () => browserSession.clearCache(), catch: (cause) => @@ -260,7 +261,6 @@ export const make = Effect.gen(function* BrowserSessionMake() { cause, }), }), - ), { concurrency: "unbounded", discard: true }, ); }), diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index 468c47065315..b97af6f05204 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -942,14 +942,14 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function delivery: () => Effect.Effect, ) => Effect.suspend(delivery).pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("Desktop preview event listener failed.", { - eventKind, - tabId, - cause, - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => + Effect.logWarning("Desktop preview event listener failed.", { + eventKind, + tabId, + cause, + }), ), ); @@ -1115,15 +1115,13 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function } return resolvedPath; }).pipe( - Effect.flatMap((resolvedPath) => - resolvedPath === null - ? Effect.fail( - new PreviewArtifactPathOutsideDirectoryError({ - artifactPath, - artifactDirectory: resolvedArtifactDirectory, - }), - ) - : Effect.succeed(resolvedPath), + Effect.filterOrFail( + (resolvedPath) => resolvedPath !== null, + () => + new PreviewArtifactPathOutsideDirectoryError({ + artifactPath, + artifactDirectory: resolvedArtifactDirectory, + }), ), ); @@ -1404,14 +1402,13 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function wcDebugger.on("message", onMessage); wcDebugger.attach("1.3"); }); - yield* Effect.all( - ["Runtime.enable", "Accessibility.enable", "Network.enable", "Log.enable"].map( - (method) => - attemptPromise( - { operation: `initializeDebugger.${method}`, webContentsId: wc.id }, - () => wcDebugger.sendCommand(method), - ), - ), + yield* Effect.forEach( + ["Runtime.enable", "Accessibility.enable", "Network.enable", "Log.enable"], + (method) => + attemptPromise( + { operation: `initializeDebugger.${method}`, webContentsId: wc.id }, + () => wcDebugger.sendCommand(method), + ), { concurrency: "unbounded", discard: true }, ); return [ @@ -4056,13 +4053,13 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function receiptKey: JSON.stringify(`__t3NativeKey_${NodeCrypto.randomUUID()}`), })), ({ frames, receiptKey }) => - Effect.all( - frames.map((frame) => + Effect.forEach( + frames, + (frame) => evaluate(frame, `globalThis[${receiptKey}]?.dispose()`).pipe( Effect.timeoutOption(1_000), Effect.ignore, ), - ), { concurrency: "unbounded", discard: true }, ), ); diff --git a/apps/desktop/src/settings/DesktopClientSettings.ts b/apps/desktop/src/settings/DesktopClientSettings.ts index e199f2b2f32e..cd7fea6ca116 100644 --- a/apps/desktop/src/settings/DesktopClientSettings.ts +++ b/apps/desktop/src/settings/DesktopClientSettings.ts @@ -75,7 +75,7 @@ const readClientSettings = ( settingsPath: string, ): Effect.Effect, DesktopClientSettingsReadError> => fileSystem.readFileString(settingsPath).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ PlatformError: (cause) => cause.reason._tag === "NotFound" @@ -98,7 +98,7 @@ const readClientSettings = ( onNone: () => Effect.succeed(Option.none()), onSome: (raw) => decodeClientSettingsJson(raw).pipe( - Effect.map((settings) => Option.some(settings)), + Effect.asSome, Effect.catchTags({ SchemaError: (cause) => Effect.logWarning("Could not decode desktop client settings.", cause).pipe( diff --git a/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts b/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts index dad53815f3a6..b1e467869e8e 100644 --- a/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts +++ b/apps/desktop/src/settings/DesktopSavedEnvironments.test.ts @@ -94,7 +94,7 @@ function makeSafeStorageLayer(input: { } return Effect.succeed(decoded.slice("enc:".length)); }, - selectedStorageBackend: Effect.succeed(Option.none()), + selectedStorageBackend: Effect.succeedNone, } satisfies ElectronSafeStorage.ElectronSafeStorage["Service"]); } diff --git a/apps/desktop/src/snapShot/DesktopSnapShot.ts b/apps/desktop/src/snapShot/DesktopSnapShot.ts index d7634f980e25..7ee455756930 100644 --- a/apps/desktop/src/snapShot/DesktopSnapShot.ts +++ b/apps/desktop/src/snapShot/DesktopSnapShot.ts @@ -820,7 +820,7 @@ export const make = Effect.gen(function* () { }; const emit = (event: DesktopSnapShotEvent) => - desktopWindow.dispatchSnapShotEvent(event).pipe(Effect.catchCause(() => Effect.void)); + desktopWindow.dispatchSnapShotEvent(event).pipe(Effect.ignoreCause); const setFailure = (message: string, captureId?: string) => Ref.update(stateRef, (state) => ({ ...state, message })).pipe( Effect.andThen( @@ -846,10 +846,9 @@ export const make = Effect.gen(function* () { const discardCapture = Effect.fn("desktop.snapShot.discardCapture")(function* (id: string) { closeLinuxFeedback(id); transition.dismiss(id); - yield* Effect.all( - [`${id}.png`, `${id}.tmp.png`, `${id}.json`, `${id}.json.tmp`].map((name) => - fileSystem.remove(path.join(captureDirectory, name), { force: true }), - ), + yield* Effect.forEach( + [`${id}.png`, `${id}.tmp.png`, `${id}.json`, `${id}.json.tmp`], + (name) => fileSystem.remove(path.join(captureDirectory, name), { force: true }), { concurrency: "unbounded", discard: true }, ).pipe(Effect.ignore); }); @@ -904,7 +903,7 @@ export const make = Effect.gen(function* () { if (snapshot.animationStarted) { yield* emit({ type: "started", id: id as DesktopSnapShotId }); } else { - yield* desktopWindow.activate.pipe(Effect.catchCause(() => Effect.void)); + yield* desktopWindow.activate.pipe(Effect.ignoreCause); } return { id, capturedAt, ...snapshot }; }).pipe(Effect.mapError((cause) => captureFailure(cause, id))); @@ -1511,7 +1510,7 @@ export const make = Effect.gen(function* () { null, ), ), - Effect.catch(() => Effect.void), + Effect.ignore, ), ), configure, diff --git a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts index aa3b7c0b4d99..27ab73d2e5e2 100644 --- a/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts +++ b/apps/desktop/src/ssh/DesktopSshPasswordPrompts.test.ts @@ -4,7 +4,6 @@ import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import * as TestClock from "effect/testing/TestClock"; import type * as Electron from "electron"; @@ -92,9 +91,9 @@ function makeElectronWindowLayer(window: ReturnType["wind ElectronWindow.ElectronWindow, ElectronWindow.ElectronWindow.of({ create: () => Effect.die("unexpected BrowserWindow creation"), - main: Effect.succeed(Option.some(window as Electron.BrowserWindow)), - currentMainOrFirst: Effect.succeed(Option.some(window as Electron.BrowserWindow)), - focusedMainOrFirst: Effect.succeed(Option.some(window as Electron.BrowserWindow)), + main: Effect.succeedSome(window as Electron.BrowserWindow), + currentMainOrFirst: Effect.succeedSome(window as Electron.BrowserWindow), + focusedMainOrFirst: Effect.succeedSome(window as Electron.BrowserWindow), setMain: () => Effect.void, clearMain: () => Effect.void, prepareReveal: () => Effect.succeed(false), diff --git a/apps/desktop/src/telemetry/DesktopTelemetryPublisher.ts b/apps/desktop/src/telemetry/DesktopTelemetryPublisher.ts index 18ea0a8380a3..a191a0d586ae 100644 --- a/apps/desktop/src/telemetry/DesktopTelemetryPublisher.ts +++ b/apps/desktop/src/telemetry/DesktopTelemetryPublisher.ts @@ -289,12 +289,12 @@ export const make = Effect.fn("desktop.telemetryPublisher.make")(function* () { yield* Ref.set(latest, Option.some(snapshot)); yield* PubSub.publish(changes, snapshot); }).pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("Failed to sample Electron telemetry", { - cause: String(cause), - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => + Effect.logWarning("Failed to sample Electron telemetry", { + cause: String(cause), + }), ), ); @@ -306,6 +306,7 @@ export const make = Effect.fn("desktop.telemetryPublisher.make")(function* () { Ref.get(diagnosticsDemandSources).pipe(Effect.map((sources) => sources.size > 0)), Ref.get(hostPowerIntervals), ]); + // @effect-diagnostics-next-line raceFirstWithSleepToTimeout:off - races a trigger queue against the interval; both arms are real outcomes, not a timeout const allowSuspendRecovery = yield* Effect.raceFirst( Queue.take(sampleTriggers).pipe(Effect.as(false)), Effect.sleep(sampleInterval(currentPower, demand, intervals)).pipe(Effect.as(true)), diff --git a/apps/desktop/src/updates/DesktopUpdates.test.ts b/apps/desktop/src/updates/DesktopUpdates.test.ts index 509778521511..9745662c1209 100644 --- a/apps/desktop/src/updates/DesktopUpdates.test.ts +++ b/apps/desktop/src/updates/DesktopUpdates.test.ts @@ -1,4 +1,5 @@ import { assert, describe, it } from "@effect/vitest"; +import { DESKTOP_UPDATE_RESTART_MARKER_FILE } from "@t3tools/contracts"; import * as Cause from "effect/Cause"; import * as Deferred from "effect/Deferred"; import * as Duration from "effect/Duration"; @@ -14,6 +15,7 @@ import * as TestClock from "effect/testing/TestClock"; import * as ElectronUpdater from "../electron/ElectronUpdater.ts"; import * as DesktopAppSettings from "../settings/DesktopAppSettings.ts"; +import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; import * as DesktopState from "../app/DesktopState.ts"; import * as DesktopUpdates from "./DesktopUpdates.ts"; import { flushCallbacks, makeHarness } from "./updatesTestHarness.ts"; @@ -85,6 +87,25 @@ describe("DesktopUpdates", () => { }).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer))); }); + it.effect("updates Linux .deb installs and leaves other non-AppImage installs off", () => + Effect.gen(function* () { + const linuxState = (packageType: string | undefined) => + Effect.scoped( + Effect.gen(function* () { + const updates = yield* DesktopUpdates.DesktopUpdates; + yield* updates.configure; + return yield* updates.getState; + }), + ).pipe(Effect.provide(makeHarness({ platform: "linux", packageType }).layer)); + + const deb = yield* linuxState("deb\n"); + assert.equal(deb.status, "idle"); + + const unmarked = yield* linuxState(undefined); + assert.equal(unmarked.status, "disabled"); + }), + ); + it.effect("subscribe delivers the latest state plus subsequent changes", () => { const harness = makeHarness(); @@ -559,6 +580,55 @@ describe("DesktopUpdates", () => { ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer))); }); + it.effect("marks the backend stop for an install as an update restart", () => { + let markersAtStop: ReadonlyArray = []; + const harness = makeHarness({ + stopBackend: Effect.sync(() => { + markersAtStop = [...harness.updateRestartMarkers]; + }), + }); + + return Effect.scoped( + Effect.gen(function* () { + const environment = yield* DesktopEnvironment.DesktopEnvironment; + const updates = yield* DesktopUpdates.DesktopUpdates; + yield* updates.configure; + harness.emit("update-downloaded", { version: "1.2.4" }); + yield* flushCallbacks; + + assert.isTrue((yield* updates.install).accepted); + assert.deepEqual(markersAtStop, [ + environment.path.join(environment.baseDir, "runtime", DESKTOP_UPDATE_RESTART_MARKER_FILE), + ]); + }), + ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer))); + }); + + it.effect("drops the update restart marker when an install is interrupted", () => + Effect.gen(function* () { + const stopping = yield* Deferred.make(); + const harness = makeHarness({ + stopBackend: Deferred.succeed(stopping, undefined).pipe(Effect.andThen(Effect.never)), + }); + + yield* Effect.scoped( + Effect.gen(function* () { + const updates = yield* DesktopUpdates.DesktopUpdates; + yield* updates.configure; + harness.emit("update-downloaded", { version: "1.2.4" }); + yield* flushCallbacks; + + const installFiber = yield* updates.install.pipe(Effect.forkScoped); + yield* Deferred.await(stopping); + assert.equal(harness.updateRestartMarkers.size, 1); + + yield* Fiber.interrupt(installFiber); + assert.equal(harness.updateRestartMarkers.size, 0); + }), + ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer))); + }), + ); + it.effect("keeps windows and restarts backends when quitAndInstall fails", () => { const harness = makeHarness({ quitAndInstall: Effect.fail( @@ -583,6 +653,8 @@ describe("DesktopUpdates", () => { assert.isTrue(result.accepted); assert.isFalse(yield* Ref.get(desktopState.quitting)); assert.deepEqual(harness.installSteps, ["quitAndInstall", "startBackend"]); + // The restarted old backend must release its tunnel on a later quit. + assert.equal(harness.updateRestartMarkers.size, 0); }), ).pipe(Effect.provide(Layer.merge(TestClock.layer(), harness.layer))); }); diff --git a/apps/desktop/src/updates/DesktopUpdates.ts b/apps/desktop/src/updates/DesktopUpdates.ts index c35b52e8343d..245925a6e731 100644 --- a/apps/desktop/src/updates/DesktopUpdates.ts +++ b/apps/desktop/src/updates/DesktopUpdates.ts @@ -1,4 +1,5 @@ import { + DESKTOP_UPDATE_RESTART_MARKER_FILE, DesktopUpdateChannelSchema, type DesktopRuntimeInfo, type DesktopUpdateActionResult, @@ -249,6 +250,7 @@ function getAutoUpdateDisabledReason(args: { isPackaged: boolean; platform: NodeJS.Platform; appImage?: string | undefined; + isDebPackage: boolean; disabledByEnv: boolean; hasUpdateFeedConfig: boolean; }): string | null { @@ -261,8 +263,8 @@ function getAutoUpdateDisabledReason(args: { if (args.disabledByEnv) { return "Automatic updates are disabled by the T3CODE_DISABLE_AUTO_UPDATE setting."; } - if (args.platform === "linux" && !args.appImage) { - return "Automatic updates on Linux require running the AppImage build."; + if (args.platform === "linux" && !args.appImage && !args.isDebPackage) { + return "Automatic updates on Linux require the AppImage or the .deb package."; } return null; } @@ -331,6 +333,18 @@ export const make = Effect.gen(function* () { ), ); + // The .deb carries electron-builder's resources/package-type marker. + // electron-updater reads the same file and installs updates with dpkg. + const isDebPackage = + environment.platform === "linux" && environment.isPackaged + ? yield* fileSystem + .readFileString(environment.path.join(environment.resourcesPath, "package-type")) + .pipe( + Effect.map((packageType) => packageType.trim() === "deb"), + Effect.orElseSucceed(() => false), + ) + : false; + const hasUpdateFeedConfig = Ref.get(appUpdateYmlConfigRef).pipe( Effect.map((appUpdateYmlConfig) => Option.isSome(appUpdateYmlConfig) || config.mockUpdates), ); @@ -343,6 +357,7 @@ export const make = Effect.gen(function* () { isPackaged: environment.isPackaged, platform: environment.platform, appImage: Option.getOrUndefined(config.appImagePath), + isDebPackage, disabledByEnv: config.disableAutoUpdate, hasUpdateFeedConfig: hasFeedConfig, }), @@ -501,8 +516,35 @@ export const make = Effect.gen(function* () { ); }).pipe(Effect.withSpan("desktop.updates.downloadAvailableUpdate")); + // Tells the primary backend that the coming stop is an update restart, so it + // keeps its managed tunnel for the backend the updated app starts. Best + // effort: without the marker the backend only re-provisions its tunnel. + const updateRestartMarkerDir = environment.path.join(environment.baseDir, "runtime"); + const updateRestartMarkerPath = environment.path.join( + updateRestartMarkerDir, + DESKTOP_UPDATE_RESTART_MARKER_FILE, + ); + const writeUpdateRestartMarker = fileSystem + .makeDirectory(updateRestartMarkerDir, { recursive: true }) + .pipe( + Effect.andThen(fileSystem.writeFileString(updateRestartMarkerPath, "")), + Effect.catch((error) => + logUpdaterWarning("Could not write the update restart marker.", { errorTag: error._tag }), + ), + ); + + // A failed or interrupted install brings no updated backend, so a later + // quit must release the tunnel. + const removeUpdateRestartMarker = fileSystem + .remove(updateRestartMarkerPath, { force: true }) + .pipe(Effect.ignore); + const resetInstallAction = Effect.all( - [finishUpdateAction("install"), Ref.set(desktopState.quitting, false)], + [ + finishUpdateAction("install"), + Ref.set(desktopState.quitting, false), + removeUpdateRestartMarker, + ], { discard: true }, ); @@ -517,6 +559,7 @@ export const make = Effect.gen(function* () { if (!ownsRecovery) return; yield* Ref.set(desktopState.quitting, false); + yield* removeUpdateRestartMarker; yield* Effect.gen(function* () { const instances = yield* pool.list; const restartExit = yield* Effect.forEach(instances, (instance) => instance.start, { @@ -586,6 +629,7 @@ export const make = Effect.gen(function* () { yield* Ref.set(desktopState.quitting, true); return yield* Effect.gen(function* () { + yield* writeUpdateRestartMarker; // Stop every backend in the pool, not just the primary. With // parallel WSL + Windows backends, leaving the WSL instance up // means quitAndInstall's app.quit() exits before the pool's @@ -638,24 +682,25 @@ export const make = Effect.gen(function* () { }), ).pipe(Effect.withSpan("desktop.updates.installDownloadedUpdate")); - const installWithExpectedVersion = (expectedVersion?: string) => - Effect.gen(function* () { - if (yield* Ref.get(desktopState.quitting)) { - return { - accepted: false, - completed: false, - failed: false, - state: yield* Ref.get(updateStateRef), - }; - } - const result = yield* installDownloadedUpdate(expectedVersion); + const installWithExpectedVersion = Effect.fn("desktop.updates.install")(function* ( + expectedVersion?: string, + ) { + if (yield* Ref.get(desktopState.quitting)) { return { - accepted: result.accepted, - completed: result.completed, - failed: result.failed, + accepted: false, + completed: false, + failed: false, state: yield* Ref.get(updateStateRef), }; - }).pipe(Effect.withSpan("desktop.updates.install")); + } + const result = yield* installDownloadedUpdate(expectedVersion); + return { + accepted: result.accepted, + completed: result.completed, + failed: result.failed, + state: yield* Ref.get(updateStateRef), + }; + }); const startUpdatePollers: Effect.Effect = Effect.gen(function* () { yield* Effect.sleep(AUTO_UPDATE_STARTUP_DELAY).pipe( diff --git a/apps/desktop/src/updates/updatesTestHarness.ts b/apps/desktop/src/updates/updatesTestHarness.ts index fbcbb349f9e7..5455f1602855 100644 --- a/apps/desktop/src/updates/updatesTestHarness.ts +++ b/apps/desktop/src/updates/updatesTestHarness.ts @@ -1,6 +1,8 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import type { DesktopUpdateState } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as PlatformError from "effect/PlatformError"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; @@ -32,6 +34,9 @@ export interface UpdatesHarnessOptions { readonly stopBackend?: Effect.Effect; readonly startBackend?: Effect.Effect; readonly env?: Record; + readonly platform?: NodeJS.Platform; + /** Contents of the resources/package-type marker a Linux package ships. */ + readonly packageType?: string | undefined; } export function makeHarness(options: UpdatesHarnessOptions = {}) { @@ -106,9 +111,9 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { const windowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { create: () => Effect.die("unexpected BrowserWindow creation"), - main: Effect.succeed(Option.none()), - currentMainOrFirst: Effect.succeed(Option.none()), - focusedMainOrFirst: Effect.succeed(Option.none()), + main: Effect.succeedNone, + currentMainOrFirst: Effect.succeedNone, + focusedMainOrFirst: Effect.succeedNone, setMain: () => Effect.void, clearMain: () => Effect.void, prepareReveal: () => Effect.succeed(false), @@ -130,7 +135,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { installSteps.push("startBackend"); }).pipe(Effect.andThen(options.startBackend ?? Effect.void)), stop: () => options.stopBackend ?? Effect.void, - currentConfig: Effect.succeed(Option.none()), + currentConfig: Effect.succeedNone, snapshot: Effect.succeed({ desiredRunning: false, ready: false, @@ -145,7 +150,7 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { const environmentLayer = DesktopEnvironment.layer({ dirname: "/repo/apps/desktop/src", homeDirectory: `/tmp/t3-desktop-updates-home-${process.pid}`, - platform: "darwin", + platform: options.platform ?? "darwin", processArch: "x64", appVersion: "1.2.3", appPath: "/repo", @@ -203,7 +208,34 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { } satisfies DesktopAppSettings.DesktopAppSettings["Service"]) : DesktopAppSettings.layer; + // Tracks the restart markers installs leave, so installs stay free of real + // disk I/O that would outrun the tests' settle loops. + const updateRestartMarkers = new Set(); + const fileSystemLayer = FileSystem.layerNoop({ + readFileString: (path) => + path === "/missing/resources/package-type" && options.packageType !== undefined + ? Effect.succeed(options.packageType) + : Effect.fail( + PlatformError.systemError({ + module: "FileSystem", + method: "readFileString", + _tag: "NotFound", + pathOrDescriptor: path, + }), + ), + makeDirectory: () => Effect.void, + writeFileString: (path) => + Effect.sync(() => { + updateRestartMarkers.add(path); + }), + remove: (path) => + Effect.sync(() => { + updateRestartMarkers.delete(path); + }), + }); + const layer = DesktopUpdates.layer.pipe( + Layer.provide(fileSystemLayer), Layer.provideMerge(updaterLayer), Layer.provideMerge(windowLayer), Layer.provideMerge(backendLayer), @@ -226,8 +258,9 @@ export function makeHarness(options: UpdatesHarnessOptions = {}) { checkCount: () => checkCount, quitAndInstalls: () => quitAndInstallCount, installSteps, + updateRestartMarkers, downloadCount: () => downloadCount, - feedUrls: () => feedUrls, + feedUrls: (): ElectronUpdater.ElectronUpdaterFeedUrl[] => feedUrls, fullChangelog: () => fullChangelog, listenerCount: () => Array.from(listeners.values()).reduce( diff --git a/apps/desktop/src/window/DesktopApplicationMenu.test.ts b/apps/desktop/src/window/DesktopApplicationMenu.test.ts index 5f5cbaa1d7fc..d24ffdcba48f 100644 --- a/apps/desktop/src/window/DesktopApplicationMenu.test.ts +++ b/apps/desktop/src/window/DesktopApplicationMenu.test.ts @@ -3,7 +3,6 @@ import { assert, describe, it } from "@effect/vitest"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import type * as Electron from "electron"; @@ -51,7 +50,7 @@ const electronAppLayer = Layer.succeed(ElectronApp.ElectronApp, { } satisfies ElectronApp.ElectronApp["Service"]); const electronDialogLayer = Layer.succeed(ElectronDialog.ElectronDialog, { - pickFolder: () => Effect.succeed(Option.none()), + pickFolder: () => Effect.succeedNone, pickFiles: () => Effect.succeed([]), showMessageBox: () => Effect.succeed({ response: 0, checkboxChecked: false }), showErrorBox: () => Effect.void, @@ -63,7 +62,7 @@ const desktopUpdatesLayer = Layer.succeed(DesktopUpdates.DesktopUpdates, { isInstallActive: Effect.succeed(false), subscribe: Effect.die("unexpected subscribe"), emitState: Effect.void, - disabledReason: Effect.succeed(Option.none()), + disabledReason: Effect.succeedNone, configure: Effect.void, setChannel: () => Effect.die("unexpected setChannel"), check: () => Effect.die("unexpected check"), @@ -98,7 +97,7 @@ const makeElectronMenuLayer = ( setApplicationMenu: (template) => Deferred.succeed(applicationMenuTemplate, template).pipe(Effect.asVoid), popupTemplate: () => Effect.void, - showContextMenu: () => Effect.succeed(Option.none()), + showContextMenu: () => Effect.succeedNone, } satisfies ElectronMenu.ElectronMenu["Service"]); const configureMenu = ( diff --git a/apps/desktop/src/window/DesktopWindow.test.ts b/apps/desktop/src/window/DesktopWindow.test.ts index ab3405b34aa8..5df400b90c44 100644 --- a/apps/desktop/src/window/DesktopWindow.test.ts +++ b/apps/desktop/src/window/DesktopWindow.test.ts @@ -151,7 +151,7 @@ function makeFakeBrowserWindow() { } const desktopClientSettingsLayer = Layer.mock(DesktopClientSettings.DesktopClientSettings)({ - get: Effect.succeed(Option.none()), + get: Effect.succeedNone, }); const electronAppLayer = Layer.mock(ElectronApp.ElectronApp)({ @@ -185,7 +185,7 @@ const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopSe const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: () => Effect.void, popupTemplate: () => Effect.void, - showContextMenu: () => Effect.succeed(Option.none()), + showContextMenu: () => Effect.succeedNone, } satisfies ElectronMenu.ElectronMenu["Service"]); const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { @@ -294,7 +294,7 @@ function makeTestLayer(input: { electronAppLayer, Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: () => Effect.void, - showContextMenu: () => Effect.succeed(Option.none()), + showContextMenu: () => Effect.succeedNone, popupTemplate: input.onPopupTemplate ?? (() => Effect.void), }), Layer.succeed(ElectronShell.ElectronShell, { diff --git a/apps/desktop/src/wsl/DesktopWslBackend.ts b/apps/desktop/src/wsl/DesktopWslBackend.ts index 3f20e58aa680..72bad30c06cc 100644 --- a/apps/desktop/src/wsl/DesktopWslBackend.ts +++ b/apps/desktop/src/wsl/DesktopWslBackend.ts @@ -138,7 +138,7 @@ export const layer = Layer.effect( const primaryConfig = yield* serverExposure.backendConfig; const port = yield* scanForWslPort(primaryConfig.port + 1).pipe( Effect.provideService(NetService.NetService, net), - Effect.map((value) => Option.some(value)), + Effect.asSome, Effect.catch((error) => logWslBackendWarning("could not allocate port for WSL backend", { error: error.message, @@ -171,7 +171,7 @@ export const layer = Layer.effect( onReady: () => Ref.set(preflightErrorRef, Option.none()), }) .pipe( - Effect.map((registered) => Option.some(registered)), + Effect.asSome, Effect.catch((error) => logWslBackendWarning("WSL backend already registered, skipping start", { id: targetId, diff --git a/apps/desktop/src/wsl/DesktopWslEnvironment.ts b/apps/desktop/src/wsl/DesktopWslEnvironment.ts index 95b217c622b1..2f922a48f4d8 100644 --- a/apps/desktop/src/wsl/DesktopWslEnvironment.ts +++ b/apps/desktop/src/wsl/DesktopWslEnvironment.ts @@ -1040,11 +1040,7 @@ const preWarmImpl = ( const handle = yield* spawner.spawn(command); yield* handle.exitCode; }), - ).pipe( - Effect.timeoutOption(PRE_WARM_TIMEOUT), - Effect.asVoid, - Effect.catch(() => Effect.void), - ); + ).pipe(Effect.timeoutOption(PRE_WARM_TIMEOUT), Effect.ignore); const windowsToWslPathImpl = ( distro: string | null, @@ -1272,15 +1268,14 @@ export const layer = Layer.effect( // distro. Negative results aren't cached so a transient wsl.exe failure // doesn't permanently disable tilde expansion. const userHomeCache = new Map(); - const getUserHome = (distro: string | null) => - Effect.gen(function* () { - const key = distro ?? "__default__"; - const cached = userHomeCache.get(key); - if (cached !== undefined) return Option.some(cached); - const resolved = yield* provideSpawner(getUserHomeImpl(distro)); - if (Option.isSome(resolved)) userHomeCache.set(key, resolved.value); - return resolved; - }).pipe(Effect.withSpan("desktop.wsl.getUserHome")); + const getUserHome = Effect.fn("desktop.wsl.getUserHome")(function* (distro: string | null) { + const key = distro ?? "__default__"; + const cached = userHomeCache.get(key); + if (cached !== undefined) return Option.some(cached); + const resolved = yield* provideSpawner(getUserHomeImpl(distro)); + if (Option.isSome(resolved)) userHomeCache.set(key, resolved.value); + return resolved; + }); const getDistroIp = (distro: string | null) => provideSpawner(getDistroIpImpl(distro)).pipe(Effect.withSpan("desktop.wsl.getDistroIp")); diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index c451a89b5767..17ce805ffe16 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -85,6 +85,19 @@ export default defineConfig({ onlyBundle: false, }, }, + { + // boot.cjs requires the other two at runtime, so all three stay separate files. + format: "cjs", + outDir: "dist-electron", + dts: false, + sourcemap: true, + outExtensions: () => ({ js: ".cjs" }), + entry: ["src/boot.ts", "src/compileCache.ts"], + clean: false, + deps: { + neverBundle: (id) => id === "./main.cjs" || id === "./compileCache.cjs", + }, + }, { format: "cjs", outDir: "dist-electron", diff --git a/apps/marketing/public/harnesses/openai_dark.svg b/apps/marketing/public/harnesses/openai_dark.svg index b78a51db7bc6..956f87c99f60 100644 --- a/apps/marketing/public/harnesses/openai_dark.svg +++ b/apps/marketing/public/harnesses/openai_dark.svg @@ -1 +1,3 @@ - \ No newline at end of file + + + diff --git a/apps/marketing/src/pages/download.astro b/apps/marketing/src/pages/download.astro index f3212d1e38ae..7fb22e52f481 100644 --- a/apps/marketing/src/pages/download.astro +++ b/apps/marketing/src/pages/download.astro @@ -91,6 +91,16 @@ const imageProps = {

Linux

+

+ On ARM? Download the arm64 .deb or + AppImage. +

diff --git a/apps/mobile/modules/t3-subscription-widget/android/src/main/AndroidManifest.xml b/apps/mobile/modules/t3-subscription-widget/android/src/main/AndroidManifest.xml index e66f03cffae5..e98c1ad6b312 100644 --- a/apps/mobile/modules/t3-subscription-widget/android/src/main/AndroidManifest.xml +++ b/apps/mobile/modules/t3-subscription-widget/android/src/main/AndroidManifest.xml @@ -1,6 +1,6 @@ - + diff --git a/apps/mobile/modules/t3-subscription-widget/android/src/main/java/expo/modules/t3subscriptionwidget/SubscriptionUsageWidget.kt b/apps/mobile/modules/t3-subscription-widget/android/src/main/java/expo/modules/t3subscriptionwidget/SubscriptionUsageWidget.kt index ef33a5d7c25d..b8a916129ea7 100644 --- a/apps/mobile/modules/t3-subscription-widget/android/src/main/java/expo/modules/t3subscriptionwidget/SubscriptionUsageWidget.kt +++ b/apps/mobile/modules/t3-subscription-widget/android/src/main/java/expo/modules/t3subscriptionwidget/SubscriptionUsageWidget.kt @@ -8,7 +8,7 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.net.Uri -import android.os.Bundle +import android.os.Build import android.view.View import android.widget.RemoteViews import org.json.JSONObject @@ -29,15 +29,6 @@ class SubscriptionUsageWidget : AppWidgetProvider() { context.getSystemService(AlarmManager::class.java).cancel(expiryIntent(context)) } - override fun onAppWidgetOptionsChanged( - context: Context, - manager: AppWidgetManager, - id: Int, - options: Bundle - ) { - update(context, manager, id) - } - companion object { const val PREFERENCES = "t3_subscription_widget" private const val EXPIRE = "expo.modules.t3subscriptionwidget.EXPIRE" @@ -56,59 +47,68 @@ class SubscriptionUsageWidget : AppWidgetProvider() { } private fun update(context: Context, manager: AppWidgetManager, id: Int) { + // The receiver is disabled below 12L (values-v32/bools.xml), but the module still calls in. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S_V2) return val saved = context.getSharedPreferences(PREFERENCES, 0).getString("snapshot", null) val snapshot = runCatching { JSONObject(saved.orEmpty()) }.getOrNull() - val views = RemoteViews(context.packageName, R.layout.t3_subscription_widget) - openAppIntent(context, id, snapshot)?.let { - views.setOnClickPendingIntent(R.id.t3_widget_root, it) - } + val openApp = openAppIntent(context, id, snapshot) val providers = snapshot?.optJSONArray("providers") val now = System.currentTimeMillis() var nextExpiry = Long.MAX_VALUE - var totalRows = 0 val groups = (0 until (providers?.length() ?: 0)).mapNotNull { index -> val provider = providers?.optJSONObject(index) ?: return@mapNotNull null val windows = provider.optJSONArray("windows") val expiresAt = provider.optLong("expiresAt") if (expiresAt > now && windows != null && windows.length() > 0) { nextExpiry = minOf(nextExpiry, expiresAt) - totalRows += provider.optInt("totalWindows", windows.length()) (0 until windows.length()).map { provider to windows.optJSONObject(it) } } else { - totalRows++ listOf(provider to null) } } - // Show each provider before filling spare space with its other windows. + // Keep the first quota from each provider near the top of the list. val rows = (0 until (groups.maxOfOrNull { it.size } ?: 0)).flatMap { index -> groups.mapNotNull { it.getOrNull(index) } } - if (rows.isNotEmpty()) { - views.removeAllViews(R.id.t3_widget_rows) - val options = manager.getAppWidgetOptions(id) - val height = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 180) - val count = ((height - 64) / 66).coerceIn(1, 12).coerceAtMost(rows.size) - for ((provider, window) in rows.take(count)) { - views.addView(R.id.t3_widget_rows, rowView(context, provider, window)) - } - val remaining = totalRows - count - val checkedAt = snapshot?.optLong("checkedAt") ?: 0 + val views = RemoteViews(context.packageName, R.layout.t3_subscription_widget) + // Count limits only; "Open app to refresh" placeholders are not entries. + val limits = rows.count { (_, window) -> window != null } + // Without limits the layout's plain title stays. + if (limits > 0) { + views.setTextViewText( + R.id.t3_widget_title, + context.getString(R.string.t3_subscription_widget_title_count, limits) + ) + views.setContentDescription( + R.id.t3_widget_title, + context.resources.getQuantityString( + R.plurals.t3_subscription_widget_title_description, + limits, + limits + ) + ) + } + openApp?.let { views.setOnClickPendingIntent(R.id.t3_widget_root, it) } + openAppIntent(context, id, snapshot, forCollection = true)?.let { + views.setPendingIntentTemplate(R.id.t3_widget_rows, it) + } + val items = RemoteViews.RemoteCollectionItems.Builder() + rows.forEachIndexed { index, (provider, window) -> + items.addItem(index.toLong(), rowView(context, provider, window)) + } + views.setRemoteAdapter(R.id.t3_widget_rows, items.build()) + views.setEmptyView(R.id.t3_widget_rows, R.id.t3_widget_empty) + val checkedAt = snapshot?.optLong("checkedAt") ?: 0 + val checked = if (checkedAt > 0) { val formatted = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT ).format(Date(checkedAt)) - val more = if (remaining > 0) { - context.getString(R.string.t3_subscription_widget_more, remaining) - } else { - "" - } - val checked = if (checkedAt > 0) { - context.getString(R.string.t3_subscription_widget_as_of, formatted) - } else { - context.getString(R.string.t3_subscription_widget_unknown_check) - } - views.setTextViewText(R.id.t3_widget_footer, checked + more) + context.getString(R.string.t3_subscription_widget_last_checked, formatted) + } else { + context.getString(R.string.t3_subscription_widget_unknown_check) } + views.setTextViewText(R.id.t3_widget_footer, checked) val alarms = context.getSystemService(AlarmManager::class.java) alarms.cancel(expiryIntent(context)) // Inexact and non-wakeup: the timestamp remains visible if Android delays expiry. @@ -118,7 +118,12 @@ class SubscriptionUsageWidget : AppWidgetProvider() { manager.updateAppWidget(id, views) } - private fun openAppIntent(context: Context, id: Int, snapshot: JSONObject?): PendingIntent? { + private fun openAppIntent( + context: Context, + id: Int, + snapshot: JSONObject?, + forCollection: Boolean = false + ): PendingIntent? { // Target this variant's launcher so co-installed builds cannot steal the tap. val intent = context.packageManager.getLaunchIntentForPackage(context.packageName) ?: return null @@ -129,9 +134,14 @@ class SubscriptionUsageWidget : AppWidgetProvider() { intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP return PendingIntent.getActivity( context, - id, + id * 2 + if (forCollection) 1 else 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_UPDATE_CURRENT or if (forCollection) { + // Collection rows use fill-in intents with an explicit app target. + PendingIntent.FLAG_MUTABLE + } else { + PendingIntent.FLAG_IMMUTABLE + } ) } @@ -153,6 +163,7 @@ class SubscriptionUsageWidget : AppWidgetProvider() { val reset = window?.optString("reset") ?: context.getString(R.string.t3_subscription_widget_refresh) child.setTextViewText(R.id.t3_widget_reset, reset) + child.setOnClickFillInIntent(R.id.t3_widget_row, Intent()) child.setContentDescription( R.id.t3_widget_row, "$label. $windowLabel. $percent. $reset. $detail" diff --git a/apps/mobile/modules/t3-subscription-widget/android/src/main/res/layout/t3_subscription_widget.xml b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/layout/t3_subscription_widget.xml index 58e55bc2c81e..bf800d249fe0 100644 --- a/apps/mobile/modules/t3-subscription-widget/android/src/main/res/layout/t3_subscription_widget.xml +++ b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/layout/t3_subscription_widget.xml @@ -1,9 +1,10 @@ - - - - + + + + + diff --git a/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values-v32/bools.xml b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values-v32/bools.xml new file mode 100644 index 000000000000..dc1751bcc4f0 --- /dev/null +++ b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values-v32/bools.xml @@ -0,0 +1,4 @@ + + + true + diff --git a/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values/bools.xml b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values/bools.xml new file mode 100644 index 000000000000..8d74a0fd7d17 --- /dev/null +++ b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values/bools.xml @@ -0,0 +1,3 @@ + + false + diff --git a/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values/strings.xml b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values/strings.xml index 7ef70aa44a46..586b82595345 100644 --- a/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values/strings.xml +++ b/apps/mobile/modules/t3-subscription-widget/android/src/main/res/values/strings.xml @@ -1,11 +1,15 @@ Last checked unavailable Subscription usage + Subscription usage (%1$d) + + Subscription usage, %1$d entry + Subscription usage, %1$d entries + Saved subscription quotas from your T3 Code environments. Tap to refresh in the app. - Open T3 Code and connect an environment to see limits. + No subscription limits available. Open T3 Code to connect. Tap to open Usage Open app to refresh %1$d%% remaining - As of %1$s - · +%1$d more + Last checked %1$s diff --git a/apps/mobile/src/components/AndroidAnchoredMenu.tsx b/apps/mobile/src/components/AndroidAnchoredMenu.tsx index dfa0dea8d05a..4c465a9f9ef4 100644 --- a/apps/mobile/src/components/AndroidAnchoredMenu.tsx +++ b/apps/mobile/src/components/AndroidAnchoredMenu.tsx @@ -7,9 +7,9 @@ import { useKeyboardState } from "react-native-keyboard-controller"; import Animated, { FadeIn } from "react-native-reanimated"; import { OverlayPortal } from "./OverlayPortal"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; import { MaterialMenuPopup } from "./MaterialMenuPopup"; -const MENU_WIDTH = 250; const SCREEN_MARGIN = 12; const ANCHOR_GAP = 6; @@ -55,6 +55,7 @@ export type AndroidAnchoredMenuProps = { * menus use the native popup for placement, animation and dismissal. */ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { + const { scale, menuWidth: desiredMenuWidth } = useAndroidControlSizing(); const [anchor, setAnchor] = useState(null); const [path, setPath] = useState([]); // Height of the modal's root view, in the modal's own coordinate space. @@ -68,6 +69,10 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { // are converted into this frame, so the menu lands correctly no matter // where the portal host sits (status bar, keyboard resize, etc.). const [overlay, setOverlay] = useState(null); + const menuWidth = + overlay === null + ? desiredMenuWidth + : Math.min(desiredMenuWidth, Math.max(0, overlay.width - 2 * SCREEN_MARGIN)); const anchorRef = useRef(null); const overlayRef = useRef(null); @@ -131,14 +136,11 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { ? 0 : local.x + local.width / 2 <= overlay.width / 2 ? local.x - : local.x + local.width - MENU_WIDTH; + : local.x + local.width - menuWidth; const left = overlay === null ? 0 - : Math.min( - Math.max(preferredLeft, SCREEN_MARGIN), - overlay.width - MENU_WIDTH - SCREEN_MARGIN, - ); + : Math.min(Math.max(preferredLeft, SCREEN_MARGIN), overlay.width - menuWidth - SCREEN_MARGIN); // The keyboard stays up while the menu is open (in-window overlay, no // focus change), so the space it covers is not usable — without this the // composer-pill menus "open down" into the IME and can't be tapped. @@ -201,6 +203,7 @@ export function AndroidAnchoredMenu(props: AndroidAnchoredMenuProps) { {!placeable || local === null ? null : !anchor.keyboardWasVisible ? ( 2 ? (headerWidth >= 600 ? 3 : 1) : actions.length; @@ -51,16 +51,13 @@ export function AndroidScreenHeader(props: { return ( setHeaderWidth(event.nativeEvent.layout.width)} - className="border-b border-header-border bg-header px-2 pb-2" + className="border-b border-header-border bg-header px-2" style={{ - paddingTop: props.embedded ? 8 : Math.max(insets.top, 12), + ...headerPadding, borderBottomWidth: props.hideBottomBorder ? 0 : undefined, }} > - + {props.onBack ? ( - + Code - + {stageLabel} diff --git a/apps/mobile/src/components/ComposerAttachmentButton.tsx b/apps/mobile/src/components/ComposerAttachmentButton.tsx index 8ea70a45c020..b6b3d6e78a49 100644 --- a/apps/mobile/src/components/ComposerAttachmentButton.tsx +++ b/apps/mobile/src/components/ComposerAttachmentButton.tsx @@ -1,6 +1,7 @@ import type { MenuAction } from "@react-native-menu/menu"; import { Pressable } from "react-native"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; import { SymbolView } from "./AppSymbol"; import { ControlPillMenu } from "./ControlPill"; @@ -15,6 +16,7 @@ export function ComposerAttachmentButton(props: { readonly onPickMedia: () => Promise; readonly onPickFiles: () => Promise; }) { + const { scale } = useAndroidControlSizing(); const button = ( ["name"]; - readonly iconNode?: ReactNode; + readonly renderIcon?: (size: number) => ReactNode; readonly label: string; readonly maxWidth?: ViewStyle["maxWidth"]; readonly onPress?: () => void; @@ -39,6 +41,7 @@ export function ComposerInlineControl(props: { readonly chevronDirection?: "down" | "right"; readonly showChevron?: boolean; }) { + const { scale, smallIconSize } = useAndroidControlSizing(); return ( - {props.iconNode ? ( - {props.iconNode} + {props.renderIcon ? ( + + {props.renderIcon(smallIconSize)} + ) : props.icon ? ( void; readonly variant?: "primary" | "danger"; }) { + const { scale, smallIconSize } = useAndroidControlSizing(); + const circleSize = Math.round(30 * scale); return ( { @@ -133,7 +135,7 @@ export function ControlPill(props: { ) : props.icon ? ( diff --git a/apps/mobile/src/components/MaterialButton.android.tsx b/apps/mobile/src/components/MaterialButton.android.tsx index f2df6464f103..f7a62f1ecb0b 100644 --- a/apps/mobile/src/components/MaterialButton.android.tsx +++ b/apps/mobile/src/components/MaterialButton.android.tsx @@ -14,10 +14,12 @@ import { View } from "react-native"; import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; import type { MaterialButtonProps } from "./MaterialButton"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; export function MaterialButton(props: MaterialButtonProps) { const { themeAppearance, themeVariables: colors } = useAppearancePreferences(); const typography = useScaledTextRole("footnote"); + const { scale, mediumIconSize } = useAndroidControlSizing(); const tone = props.tone ?? "secondary"; const Component = tone === "text" ? TextButton : tone === "secondary" ? FilledTonalButton : Button; @@ -77,11 +79,11 @@ export function MaterialButton(props: MaterialButtonProps) { {props.loading ? ( <> - + ) : null} {props.label} diff --git a/apps/mobile/src/components/MaterialFloatingActionButton.android.tsx b/apps/mobile/src/components/MaterialFloatingActionButton.android.tsx index 2882d0d91ec0..29f396fc9d6d 100644 --- a/apps/mobile/src/components/MaterialFloatingActionButton.android.tsx +++ b/apps/mobile/src/components/MaterialFloatingActionButton.android.tsx @@ -6,8 +6,9 @@ import { LargeFloatingActionButton, Text, } from "@expo/ui/jetpack-compose"; -import { size } from "@expo/ui/jetpack-compose/modifiers"; +import { defaultMinSize, height, size, width } from "@expo/ui/jetpack-compose/modifiers"; import { View, type StyleProp, type ViewStyle } from "react-native"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; import { SymbolView, type AppSymbolName } from "./AppSymbol"; @@ -24,6 +25,8 @@ export function MaterialFloatingActionButton(props: { }) { const { themeAppearance, themeVariables: colors } = useAppearancePreferences(); const typography = useScaledTextRole("footnote"); + const { scale, iconSize: standardIconSize, fabSize, largeFabSize } = useAndroidControlSizing(); + const buttonSize = props.variant === "large" ? largeFabSize : fabSize; const primary = props.tone === "primary"; const containerColor = colors[primary ? "--color-primary" : "--color-secondary"]; const contentColor = @@ -34,7 +37,7 @@ export function MaterialFloatingActionButton(props: { : props.variant === "large" ? LargeFloatingActionButton : FloatingActionButton; - const iconSize = props.variant === "large" ? 36 : 24; + const iconSize = props.variant === "large" ? Math.round(36 * scale) : standardIconSize; return ( { if (!props.disabled) props.onPress?.(); }} - style={{ width: 48, height: 48 }} + style={{ width: buttonSize, height: buttonSize }} > diff --git a/apps/mobile/src/components/MaterialListRow.tsx b/apps/mobile/src/components/MaterialListRow.tsx index 539472e4bd96..cef104a02f50 100644 --- a/apps/mobile/src/components/MaterialListRow.tsx +++ b/apps/mobile/src/components/MaterialListRow.tsx @@ -5,6 +5,7 @@ import { useAppearancePreferences } from "../features/settings/appearance/Appear import { cn } from "../lib/cn"; import { AppText } from "./AppText"; import { SymbolView } from "./AppSymbol"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; /** Shared geometry for Material navigation and selection lists. Group rows in one card. */ export function MaterialListRow({ @@ -23,6 +24,7 @@ export function MaterialListRow({ readonly trailing?: ReactNode; }) { const { themeVariables } = useAppearancePreferences(); + const { smallIconSize } = useAndroidControlSizing(); return ( + ) : null} ); diff --git a/apps/mobile/src/components/MaterialMenuPopup.android.tsx b/apps/mobile/src/components/MaterialMenuPopup.android.tsx index c49db1815039..4f9fe3d1ada4 100644 --- a/apps/mobile/src/components/MaterialMenuPopup.android.tsx +++ b/apps/mobile/src/components/MaterialMenuPopup.android.tsx @@ -7,9 +7,11 @@ import { RNHostView, Text, } from "@expo/ui/jetpack-compose"; -import { padding, size, width } from "@expo/ui/jetpack-compose/modifiers"; +import { defaultMinSize, padding, size, width } from "@expo/ui/jetpack-compose/modifiers"; import { View } from "react-native"; +import { resolveScaledTextRole } from "../lib/appearancePreferences"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; import type { MaterialMenuPopupProps } from "./MaterialMenuPopup"; import { isAppSymbolName, SymbolView, type AppSymbolName } from "./AppSymbol"; @@ -19,12 +21,16 @@ function MenuIcon(props: { readonly destructive?: boolean; readonly disabled?: boolean; }) { + const { iconSize } = useAndroidControlSizing(); return ( - - + + {props.parent ? ( - + - + {props.parent.title} ) : props.title ? ( - + {props.title} ) : null} @@ -66,19 +81,22 @@ export function MaterialMenuPopup(props: MaterialMenuPopupProps) { props.onPress(action)} > + {action.image && isAppSymbolName(action.image) ? ( + + + + ) : null} {action.subtitle ? ( - + {action.subtitle} ) : null} - {action.image && isAppSymbolName(action.image) ? ( - - - - ) : null} {(action.subactions?.length ?? 0) > 0 ? ( @@ -124,7 +133,7 @@ export function MaterialMenuPopup(props: MaterialMenuPopupProps) { colorScheme={themeAppearance} ignoreSafeAreaKeyboardInsets matchContents - style={{ width: 250 }} + style={{ width: props.menuWidth }} > {items} diff --git a/apps/mobile/src/components/MaterialMenuPopup.tsx b/apps/mobile/src/components/MaterialMenuPopup.tsx index 3a23e5de7ab4..06019a0e1512 100644 --- a/apps/mobile/src/components/MaterialMenuPopup.tsx +++ b/apps/mobile/src/components/MaterialMenuPopup.tsx @@ -1,6 +1,7 @@ import type { MenuAction } from "@react-native-menu/menu"; export interface MaterialMenuPopupProps { + readonly menuWidth: number; readonly anchor: { readonly x: number; readonly y: number; diff --git a/apps/mobile/src/components/MaterialScrollComposeButton.android.tsx b/apps/mobile/src/components/MaterialScrollComposeButton.android.tsx index 018dbdc19b42..8c653e67ee9a 100644 --- a/apps/mobile/src/components/MaterialScrollComposeButton.android.tsx +++ b/apps/mobile/src/components/MaterialScrollComposeButton.android.tsx @@ -1,9 +1,17 @@ import { Box, ExtendedFloatingActionButton, Host, Icon, Text } from "@expo/ui/jetpack-compose"; -import { fillMaxWidth, onSizeChanged, size } from "@expo/ui/jetpack-compose/modifiers"; +import { + defaultMinSize, + fillMaxWidth, + graphicsLayer, + height, + onSizeChanged, + size, +} from "@expo/ui/jetpack-compose/modifiers"; import { useCallback, useState } from "react"; import { Pressable, View, type StyleProp, type ViewStyle } from "react-native"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; -import { useScaledTextRole } from "../features/settings/appearance/useScaledTextRole"; +import { resolveScaledTextRole } from "../lib/appearancePreferences"; /** Keep the animated width and icon positioning entirely inside Compose, not Yoga. */ export function MaterialScrollComposeButton(props: { @@ -12,14 +20,23 @@ export function MaterialScrollComposeButton(props: { readonly className?: string; readonly style?: StyleProp; }) { - const { themeAppearance, themeVariables: colors } = useAppearancePreferences(); - const typography = useScaledTextRole("footnote"); - const [expandedWidth, setExpandedWidth] = useState(56); + const { appearance, themeAppearance, themeVariables: colors } = useAppearancePreferences(); + const typography = resolveScaledTextRole("footnote", appearance.baseFontSize); + const { iconSize, fabSize } = useAndroidControlSizing(); + // Scale the native 56dp minimum; keep text and icons at their requested sizes. + const nativeSize = Math.max(56, fabSize); + const scale = fabSize / nativeSize; + const nativeIconSize = Math.round(iconSize / scale); + const [buttonWidth, setButtonWidth] = useState(nativeSize); const rememberWidth = useCallback(({ width }: { width: number }) => { - setExpandedWidth((previous) => Math.max(previous, width)); + setButtonWidth((previous) => Math.max(previous, width)); }, []); return ( - + - + @@ -45,7 +72,11 @@ export function MaterialScrollComposeButton(props: { New thread @@ -65,8 +96,9 @@ export function MaterialScrollComposeButton(props: { right: 0, top: 0, bottom: 0, - width: props.expanded ? expandedWidth : 56, - borderRadius: 16, + // Release the label area as soon as collapse starts, before native measurements arrive. + width: props.expanded ? buttonWidth * scale : fabSize, + borderRadius: 16 * scale, overflow: "hidden", }} /> diff --git a/apps/mobile/src/components/MaterialSearchField.tsx b/apps/mobile/src/components/MaterialSearchField.tsx index ab96440f5f09..3609430a6e1a 100644 --- a/apps/mobile/src/components/MaterialSearchField.tsx +++ b/apps/mobile/src/components/MaterialSearchField.tsx @@ -2,6 +2,7 @@ import type { RefObject } from "react"; import { Pressable, TextInput, View } from "react-native"; import { SymbolView } from "./AppSymbol"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; export function MaterialSearchField({ inputRef, @@ -18,9 +19,21 @@ export function MaterialSearchField({ readonly value: string; readonly onChangeText: (value: string) => void; }) { + const { scale, mediumIconSize } = useAndroidControlSizing(); return ( - - + + @@ -49,7 +63,7 @@ export function MaterialSearchField({ > diff --git a/apps/mobile/src/components/ProjectFavicon.tsx b/apps/mobile/src/components/ProjectFavicon.tsx index e1d883a01026..83505730d808 100644 --- a/apps/mobile/src/components/ProjectFavicon.tsx +++ b/apps/mobile/src/components/ProjectFavicon.tsx @@ -1,8 +1,9 @@ import { SymbolView } from "./AppSymbol"; +import { AppText } from "./AppText"; import { Image } from "expo-image"; import { useLayoutEffect, useMemo, useState } from "react"; import { View } from "react-native"; -import type { EnvironmentId } from "@t3tools/contracts"; +import type { EnvironmentId, ProjectIconOverride } from "@t3tools/contracts"; import { getProjectFaviconCacheKey, getProjectFaviconResourceKey, @@ -11,6 +12,12 @@ import { import { useAtomValue } from "@effect/atom-react"; import { Atom } from "effect/unstable/reactivity"; import { projectFaviconUrlAtom } from "../state/assets"; +import { + countGlyphs, + projectIconColorClassNames, + resolveProjectIconGlyph, + type ProjectIconGlyph, +} from "../lib/projectIcon"; import { beginProjectFaviconRequest, @@ -30,10 +37,12 @@ export function ProjectFavicon(props: { readonly projectTitle: string; readonly workspaceRoot?: string | null; readonly faviconPath?: string | null; + readonly projectIcon?: ProjectIconOverride | null; }) { const size = props.size ?? 42; + const glyph = resolveProjectIconGlyph(props.projectIcon, props.projectTitle); const faviconUrl = useAtomValue( - props.workspaceRoot == null + props.workspaceRoot == null || glyph !== null ? EMPTY_FAVICON_URL : projectFaviconUrlAtom({ environmentId: props.environmentId, @@ -51,6 +60,10 @@ export function ProjectFavicon(props: { : getProjectFaviconCacheKey(props.environmentId, props.workspaceRoot, renderableFaviconUrl) : null; + if (glyph !== null) { + return ; + } + return ( + + {glyph.emoji} + + + ); + } + + const colors = projectIconColorClassNames(glyph.color); + return ( + + + {glyph.text} + + + ); +} + function ProjectFaviconImage(props: { readonly cacheKey: string | null; readonly faviconUrl: string | null; @@ -105,7 +168,7 @@ function ProjectFaviconImage(props: { {!showImage ? ( diff --git a/apps/mobile/src/components/ProviderIcon.tsx b/apps/mobile/src/components/ProviderIcon.tsx index 374738d0aeca..49de96a8d74c 100644 --- a/apps/mobile/src/components/ProviderIcon.tsx +++ b/apps/mobile/src/components/ProviderIcon.tsx @@ -75,10 +75,12 @@ export function ProviderIcon(props: ProviderIconProps) { // codex (and unknown drivers) return ( - + ); diff --git a/apps/mobile/src/components/ScreenHeader.android.tsx b/apps/mobile/src/components/ScreenHeader.android.tsx index 61a38c3a00c2..1d3232fd1641 100644 --- a/apps/mobile/src/components/ScreenHeader.android.tsx +++ b/apps/mobile/src/components/ScreenHeader.android.tsx @@ -1,6 +1,6 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { BackHandler, Keyboard, Pressable, TextInput, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { useMaterialToolbarLayout } from "./useMaterialToolbarLayout"; import { NativeStackScreenOptions } from "../native/StackHeader"; import { AndroidWorkspaceSidebarButton } from "../features/layout/workspace-sidebar-toolbar"; import { useAppearancePreferences } from "../features/settings/appearance/AppearancePreferencesProvider"; @@ -11,10 +11,12 @@ import { ControlPillMenu } from "./ControlPill"; import { MaterialSearchField } from "./MaterialSearchField"; import { androidHeaderMenuActions, findHeaderMenuAction } from "./headerMenu.android"; import type { ScreenHeaderProps } from "./ScreenHeader.types"; +import { useAndroidControlSizing } from "./useAndroidControlSizing"; export function ScreenHeader(props: ScreenHeaderProps) { const { search } = props; - const insets = useSafeAreaInsets(); + const { paddingTop, paddingBottom } = useMaterialToolbarLayout(); + const { scale, buttonSize, iconSize, smallIconSize } = useAndroidControlSizing(); const { themeVariables } = useAppearancePreferences(); const inputRef = useRef(null); const [searchOpen, setSearchOpen] = useState(false); @@ -48,11 +50,12 @@ export function ScreenHeader(props: ScreenHeaderProps) { @@ -80,30 +83,43 @@ export function ScreenHeader(props: ScreenHeaderProps) { <> {options} - + {props.onBack ? ( ) : null} - + @@ -112,9 +128,14 @@ export function ScreenHeader(props: ScreenHeaderProps) { autoCapitalize="none" onChangeText={search.onChangeText} value={search.value} - placeholder={search.placeholder} + placeholder={ + search.compactToolbar + ? (search.compactPlaceholder ?? search.placeholder) + : search.placeholder + } placeholderTextColorClassName="accent-placeholder" - className="flex-1 py-2 text-base font-sans text-header-foreground" + className="flex-1 text-base font-sans text-header-foreground" + style={{ paddingVertical: 7 * scale }} /> {menuView} @@ -173,11 +194,8 @@ export function ScreenHeader(props: ScreenHeaderProps) { {header} {searching ? ( - - + + void; readonly leading?: ReactNode; }) { - const insets = useSafeAreaInsets(); + const { paddingTop, paddingBottom } = useMaterialToolbarLayout(); const searchRef = useRef(null); const [searchOpen, setSearchOpen] = useState(false); const searching = searchOpen || props.searchQuery.length > 0; @@ -77,11 +77,8 @@ export function MaterialFilesHeader(props: { /> {searching ? ( - - + + ) { const insets = useSafeAreaInsets(); + const { appearance } = useAppearancePreferences(); + const { fontScale } = useWindowDimensions(); + const [layoutWidth, setLayoutWidth] = useState(null); const { state } = useWorkspaceState(); const [expanded, setExpanded] = useState(true); const scrollState = useRef({ anchor: 0, expanded: true }); @@ -23,11 +32,13 @@ export function AndroidHomeFabLayout(props: ComponentProps + setLayoutWidth(event.nativeEvent.layout.width)}> {props.children} - {state.hasConnections ? ( + {state.hasConnections && layoutWidth !== null ? ( void; readonly onPinThread: (thread: EnvironmentThreadShell) => Promise; readonly onUnpinThread: (thread: EnvironmentThreadShell) => Promise; + readonly onSetThreadAutoSettle: ( + thread: EnvironmentThreadShell, + enabled: boolean, + ) => Promise; readonly onMoveThread: ( thread: EnvironmentThreadShell, direction: ThreadMoveDestination, @@ -125,6 +138,10 @@ interface HomeScreenProps { // measured-height pool expansion. The old tallest-card estimate (~92) fired // that warning on every ordinary shelf expand, so the average wins. const ESTIMATED_THREAD_LIST_V2_ROW_HEIGHT = 72; +// Rows away from the viewport are cheap dormant frames (see +// swipe-row-activation), so render further ahead: a fast fling then reaches +// rows that are already built instead of rows still being rebuilt. +const THREAD_LIST_V2_DRAW_DISTANCE = 1_000; const PRE_LIQUID_GLASS_BOTTOM_TOOLBAR_HEIGHT = 44; /** * Top spacing between the list and the Android custom header. The Android @@ -210,6 +227,7 @@ export function HomeScreen(props: HomeScreenProps) { const queuedThreadKeys = useQueuedThreadKeys(); const openSwipeableRef = useRef(null); const insets = useSafeAreaInsets(); + const { fabClearance } = useAndroidControlSizing(); const iosBottomToolbarClearance = Platform.OS === "ios" && !NATIVE_LIQUID_GLASS_SUPPORTED ? PRE_LIQUID_GLASS_BOTTOM_TOOLBAR_HEIGHT @@ -260,8 +278,45 @@ export function HomeScreen(props: HomeScreenProps) { openSwipeableRef.current?.close(); }, []); const onMaterialFabScroll = useMaterialFabScroll(); + const listRef = useRef(null); + const swipeRowActivation = useMemo(() => createSwipeRowActivation(), []); + const activateVisibleRows = useCallback( + (rows: ReadonlyArray) => { + const state = listRef.current?.getState(); + if (state === undefined || !(state.end >= 0)) return; + swipeRowActivation.activate( + rows.slice(Math.max(0, state.start - 2), state.end + 3).map((row) => row.key), + ); + }, + [swipeRowActivation], + ); + // Status-bar, accessibility and programmatic scrolls never arm the scroll + // gate, so every scroll also activates the visible rows once it settles. + const activationTimerRef = useRef | undefined>(undefined); + useEffect(() => () => clearTimeout(activationTimerRef.current), []); + const handleListScroll = useCallback( + (event: NativeSyntheticEvent) => { + onMaterialFabScroll?.(event); + clearTimeout(activationTimerRef.current); + activationTimerRef.current = setTimeout( + () => activateVisibleRows(listRef.current?.getState().data ?? []), + 200, + ); + }, + [activateVisibleRows, onMaterialFabScroll], + ); + const trackListTouches = useCallback( + (event: GestureResponderEvent, started: boolean) => { + const { changedTouches, touches } = event.nativeEvent; + swipeRowActivation.trackTouches( + started ? changedTouches.map((touch) => touch.identifier) : [], + touches.map((touch) => touch.identifier), + ); + }, + [swipeRowActivation], + ); const { swipeEnabled, scrollGateHandlers } = useSwipeableScrollGate({ - onScroll: onMaterialFabScroll, + onScroll: handleListScroll, onScrollBeginDrag: handleScrollBeginDrag, }); @@ -378,6 +433,12 @@ export function HomeScreen(props: HomeScreenProps) { }, [props.onUnpinThread], ); + const handleSetThreadAutoSettle = useCallback( + (thread: EnvironmentThreadShell, enabled: boolean) => { + void props.onSetThreadAutoSettle(thread, enabled); + }, + [props.onSetThreadAutoSettle], + ); const handleRegenerateThreadTitle = useCallback( (thread: EnvironmentThreadShell) => { void props.onRegenerateThreadTitle(thread); @@ -456,6 +517,15 @@ export function HomeScreen(props: HomeScreenProps) { } return supported; }, [serverConfigs]); + const autoSettleOptOutEnvironmentIds = useMemo(() => { + const supported = new Set(); + for (const [environmentId, config] of serverConfigs) { + if (config.environment.capabilities.threadAutoSettleOptOut === true) { + supported.add(environmentId); + } + } + return supported; + }, [serverConfigs]); const pinReorderEnvironmentIds = useMemo(() => { const supported = new Set(); for (const [environmentId, config] of serverConfigs) { @@ -636,6 +706,9 @@ export function HomeScreen(props: HomeScreenProps) { ); useThreadJumpShortcuts(threadListV2Items, props.onSelectThread); + useEffect(() => { + if (swipeEnabled) activateVisibleRows(threadListV2Items); + }, [activateVisibleRows, swipeEnabled, threadListV2Items]); const renderV2Item = useCallback( ({ item }: { readonly item: ThreadListV2ListItem }) => { @@ -726,6 +799,7 @@ export function HomeScreen(props: HomeScreenProps) { onSettleThread={handleSettleThread} snoozeSupported={snoozeEnvironmentIds.has(thread.environmentId)} pinningSupported={pinningEnvironmentIds.has(thread.environmentId)} + autoSettleOptOutSupported={autoSettleOptOutEnvironmentIds.has(thread.environmentId)} reorderSupported={ item.item.pinned ? pinReorderEnvironmentIds.has(thread.environmentId) @@ -738,9 +812,11 @@ export function HomeScreen(props: HomeScreenProps) { onUnsettleThread={handleUnsettleThread} onPinThread={handlePinThread} onUnpinThread={handleUnpinThread} + onSetThreadAutoSettle={handleSetThreadAutoSettle} onMoveThread={handleMoveThread} onSwipeableClose={handleSwipeableClose} onSwipeableWillOpen={handleSwipeableWillOpen} + activationKey={item.key} /> ); }, @@ -758,6 +834,8 @@ export function HomeScreen(props: HomeScreenProps) { handleSwipeableClose, handleSwipeableWillOpen, handleUnsettleThread, + handleSetThreadAutoSettle, + autoSettleOptOutEnvironmentIds, pinningEnvironmentIds, machineByEnvironmentId, pinReorderEnvironmentIds, @@ -927,15 +1005,20 @@ export function HomeScreen(props: HomeScreenProps) { {/* Shared with the iPad sidebar: cells are reused across data rebuilds and `itemsAreEqual` keeps a minute tick (or an unrelated shell update) from re-rendering untouched rows. */} - + activateVisibleRows(threadListV2Items)} + onTouchStart={(event) => trackListTouches(event, true)} + onTouchEnd={(event) => trackListTouches(event, false)} + onTouchCancel={(event) => trackListTouches(event, false)} data={threadListV2Items} renderItem={renderV2Item} keyExtractor={v2KeyExtractor} getItemType={(item) => item.type} itemsAreEqual={threadListV2ListItemsAreEqual} estimatedItemSize={ESTIMATED_THREAD_LIST_V2_ROW_HEIGHT} - drawDistance={500} + drawDistance={THREAD_LIST_V2_DRAW_DISTANCE} recycleItems extraData={v2ExtraData} ListHeaderComponent={v2ListHeader} @@ -960,7 +1043,7 @@ export function HomeScreen(props: HomeScreenProps) { paddingBottom: Platform.OS === "ios" ? Math.max(insets.bottom, 24) + 96 + iosBottomToolbarClearance - : Math.max(insets.bottom, 16) + (Platform.OS === "android" ? 148 : 88), + : Math.max(insets.bottom, 16) + (Platform.OS === "android" ? fabClearance : 88), }} /> diff --git a/apps/mobile/src/features/home/MaterialThreadListToolbar.tsx b/apps/mobile/src/features/home/MaterialThreadListToolbar.tsx index 2d852f8f7be2..6cea625aced9 100644 --- a/apps/mobile/src/features/home/MaterialThreadListToolbar.tsx +++ b/apps/mobile/src/features/home/MaterialThreadListToolbar.tsx @@ -12,7 +12,8 @@ import { MaterialSearchField } from "../../components/MaterialSearchField"; import { useHardwareKeyboardCommand } from "../keyboard/hardwareKeyboardCommands"; import { WorkspaceConnectionTitle } from "./WorkspaceConnectionTitle"; import { useWorkspaceState } from "../../state/workspace"; -import { useMaterialToolbarHeight } from "../../components/useMaterialToolbarHeight"; +import { useAndroidControlSizing } from "../../components/useAndroidControlSizing"; +import { useMaterialToolbarLayout } from "../../components/useMaterialToolbarLayout"; /** One toolbar height for the compact list and expanded sidebar, including search. */ export function MaterialThreadListToolbar(props: { @@ -28,7 +29,8 @@ export function MaterialThreadListToolbar(props: { readonly onRequestVisibility?: () => void; }) { const insets = useSafeAreaInsets(); - const toolbarHeight = useMaterialToolbarHeight(); + const { fabSize } = useAndroidControlSizing(); + const { height: toolbarHeight, ...headerPadding } = useMaterialToolbarLayout(); const { state } = useWorkspaceState(); const { onRequestVisibility, onSearchQueryChange } = props; const searchRef = useRef(null); @@ -76,11 +78,9 @@ export function MaterialThreadListToolbar(props: { {searching ? ( @@ -116,14 +116,14 @@ export function MaterialThreadListToolbar(props: { )} - {/* Sit 8dp above the 56dp extended New thread FAB. */} + {/* Keep the filter above the New thread FAB at every text size. */} {state.hasConnections ? ( diff --git a/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx b/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx index 9b9333b46c7f..d2b068a07c59 100644 --- a/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx +++ b/apps/mobile/src/features/home/WorkspaceConnectionTitle.tsx @@ -1,9 +1,10 @@ import type { NativeStackNavigationOptions } from "@react-navigation/native-stack"; import { useEffect, useRef, useState, type ReactNode } from "react"; -import { ActivityIndicator, Animated, Pressable, View } from "react-native"; +import { ActivityIndicator, Animated, Platform, Pressable, View } from "react-native"; import { SymbolView } from "../../components/AppSymbol"; import { AppText as Text } from "../../components/AppText"; +import { useAndroidControlSizing } from "../../components/useAndroidControlSizing"; import { brandTitleOffset, CompactBrandTitle, @@ -105,6 +106,7 @@ export function WorkspaceConnectionTitle(props: { }) { const status = useDelayedConnectionStatus(); const size = props.size ?? "navbar"; + const { scale } = useAndroidControlSizing(); if (status === null) { return props.grow ? ( @@ -126,26 +128,28 @@ export function WorkspaceConnectionTitle(props: { hitSlop={8} onPress={props.onPress} className="flex-row items-center gap-2" - style={{ flexShrink: 1, marginLeft: props.statusOffset ?? 0 }} + style={[ + { flexShrink: 1, marginLeft: props.statusOffset ?? 0 }, + Platform.OS === "android" && { gap: 7 * scale }, + ]} > {status.showsProgress ? ( - + ) : ( )} {status.label} diff --git a/apps/mobile/src/features/home/swipe-row-activation.test.ts b/apps/mobile/src/features/home/swipe-row-activation.test.ts new file mode 100644 index 000000000000..43f8dcf0ca7e --- /dev/null +++ b/apps/mobile/src/features/home/swipe-row-activation.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it, vi } from "vite-plus/test"; + +import { createSwipeRowActivation } from "./swipe-row-activation"; + +describe("createSwipeRowActivation", () => { + it("activates exactly the requested rows and notifies only on change", () => { + const activation = createSwipeRowActivation(); + const listener = vi.fn(); + activation.subscribe(listener); + + activation.activate(["a", "b"]); + activation.activate(["b", "a"]); + + expect(activation.isActive("a")).toBe(true); + expect(activation.isActive("c")).toBe(false); + expect(listener).toHaveBeenCalledTimes(1); + + activation.activate(["c"]); + expect(activation.isActive("a")).toBe(false); + expect(activation.isActive("c")).toBe(true); + expect(listener).toHaveBeenCalledTimes(2); + }); + + it("defers changes while a finger is on the list so a press is never remounted", () => { + const activation = createSwipeRowActivation(); + activation.activate(["a"]); + + activation.trackTouches(["1"], ["1"]); + activation.activate(["b"]); + activation.activate(["c"]); + expect(activation.isActive("a")).toBe(true); + expect(activation.isActive("c")).toBe(false); + + activation.trackTouches([], []); + expect(activation.isActive("a")).toBe(false); + expect(activation.isActive("b")).toBe(false); + expect(activation.isActive("c")).toBe(true); + }); + + it("ignores fingers that did not start on the list", () => { + const activation = createSwipeRowActivation(); + activation.trackTouches(["1"], ["1", "2"]); + activation.activate(["a"]); + + // The list finger lifts while finger 2 stays on another control. + activation.trackTouches([], ["2"]); + expect(activation.isActive("a")).toBe(true); + }); + + it("drops a list finger whose end event never arrived", () => { + const activation = createSwipeRowActivation(); + activation.trackTouches(["1"], ["1"]); + activation.activate(["a"]); + + activation.trackTouches(["2"], ["2"]); + activation.trackTouches([], []); + expect(activation.isActive("a")).toBe(true); + }); + + it("stops notifying after unsubscribe", () => { + const activation = createSwipeRowActivation(); + const listener = vi.fn(); + const unsubscribe = activation.subscribe(listener); + unsubscribe(); + activation.activate(["a"]); + expect(listener).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/mobile/src/features/home/swipe-row-activation.ts b/apps/mobile/src/features/home/swipe-row-activation.ts new file mode 100644 index 000000000000..d56d12cba56f --- /dev/null +++ b/apps/mobile/src/features/home/swipe-row-activation.ts @@ -0,0 +1,62 @@ +import { createContext, use, useSyncExternalStore } from "react"; + +/** + * Full swipe rows (pan gesture, animated actions, hidden action buttons) only + * exist around the viewport. Every other Home row renders a dormant frame that + * paints the same content with a fraction of the native views, so a row the + * list rebuilds while scrolling is cheap. The scroll gate already disables + * swipes while the list moves, so rows are activated once it rests. + */ +export function createSwipeRowActivation() { + let activeKeys = new Set(); + // Swapping a row's frame remounts it, which would cancel a press or long + // press in progress, so changes wait until every finger that started on the + // list has lifted. + const listTouches = new Set(); + let pendingKeys: ReadonlyArray | null = null; + const listeners = new Set<() => void>(); + const apply = (keys: ReadonlyArray) => { + if (keys.length === activeKeys.size && keys.every((key) => activeKeys.has(key))) return; + activeKeys = new Set(keys); + for (const listener of listeners) listener(); + }; + return { + subscribe(listener: () => void) { + listeners.add(listener); + return () => void listeners.delete(listener); + }, + isActive: (key: string) => activeKeys.has(key), + activate(keys: ReadonlyArray) { + if (listTouches.size > 0) pendingKeys = keys; + else apply(keys); + }, + /** + * `started` are touches that just began on the list; `onScreen` is every + * finger still down anywhere. A finger on another control never holds + * changes, and one whose end event went missing is dropped here. + */ + trackTouches(started: ReadonlyArray, onScreen: ReadonlyArray) { + for (const id of started) listTouches.add(id); + for (const id of listTouches) if (!onScreen.includes(id)) listTouches.delete(id); + if (listTouches.size > 0 || pendingKeys === null) return; + const keys = pendingKeys; + pendingKeys = null; + apply(keys); + }, + }; +} + +export type SwipeRowActivation = ReturnType; + +export const SwipeRowActivationContext = createContext(null); + +const subscribeNever = () => () => {}; + +/** Rows outside an activation provider (e.g. the iPad sidebar) stay live. */ +export function useSwipeRowDormant(key: string | undefined): boolean { + const activation = use(SwipeRowActivationContext); + return useSyncExternalStore( + activation?.subscribe ?? subscribeNever, + () => activation !== null && key !== undefined && !activation.isActive(key), + ); +} diff --git a/apps/mobile/src/features/home/thread-swipe-actions.tsx b/apps/mobile/src/features/home/thread-swipe-actions.tsx index 44f3c36e6655..e62935c28861 100644 --- a/apps/mobile/src/features/home/thread-swipe-actions.tsx +++ b/apps/mobile/src/features/home/thread-swipe-actions.tsx @@ -40,6 +40,7 @@ import Animated, { } from "react-native-reanimated"; import { AppText as Text } from "../../components/AppText"; +import { SwipeRowActivationContext, type SwipeRowActivation } from "./swipe-row-activation"; import { registerThreadDismissal } from "./thread-dismissal"; // Wide enough for the longest action label ("Unarchive"). @@ -129,11 +130,14 @@ const SwipeableScrollGateContext = createContext(true); export function SwipeableScrollGateProvider(props: { readonly enabled: boolean; + readonly activation?: SwipeRowActivation; readonly children: ReactNode; }) { return ( - {props.children} + + {props.children} + ); } @@ -260,13 +264,32 @@ interface ThreadSwipeableProps { * open/mid-drag state can't leak onto another row. */ readonly resetKey?: string; + /** Paints the row without swipe machinery; see swipe-row-activation. */ + readonly dormant?: boolean; readonly simultaneousWithExternalGesture?: ComponentProps< typeof ReanimatedSwipeable >["simultaneousWithExternalGesture"]; readonly threadTitle: string; } +const closeDormant = () => {}; + export function ThreadSwipeable(props: ThreadSwipeableProps) { + if (props.dormant) { + // Mirrors ReanimatedSwipeable's container and children views. + return ( + + + {props.children(closeDormant)} + + + ); + } // Recycled content gets fresh native and animation state. Late callbacks // from the previous row retain its action, never the replacement's action. return ; diff --git a/apps/mobile/src/features/home/useThreadListActions.ts b/apps/mobile/src/features/home/useThreadListActions.ts index 9b599110883c..4a43facc6172 100644 --- a/apps/mobile/src/features/home/useThreadListActions.ts +++ b/apps/mobile/src/features/home/useThreadListActions.ts @@ -59,6 +59,15 @@ function environmentSupportsPinReorder(environmentId: EnvironmentThreadShell["en ); } +function environmentSupportsAutoSettleOptOut( + environmentId: EnvironmentThreadShell["environmentId"], +) { + return ( + appAtomRegistry.get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities + .threadAutoSettleOptOut === true + ); +} + function environmentSupportsTitleRegeneration( environmentId: EnvironmentThreadShell["environmentId"], ) { @@ -237,6 +246,11 @@ export function useThreadListActions(): { readonly unsettleThread: (thread: EnvironmentThreadShell) => Promise; readonly pinThread: (thread: EnvironmentThreadShell) => Promise; readonly unpinThread: (thread: EnvironmentThreadShell) => Promise; + /** Sets per-thread automatic settlement on or off. */ + readonly setThreadAutoSettle: ( + thread: EnvironmentThreadShell, + enabled: boolean, + ) => Promise; readonly moveThread: ( thread: EnvironmentThreadShell, direction: ThreadMoveDestination, @@ -249,6 +263,9 @@ export function useThreadListActions(): { const unsnoozeMutation = useAtomCommand(threadEnvironment.unsnooze, { reportFailure: false }); const pinMutation = useAtomCommand(threadEnvironment.pin, { reportFailure: false }); const unpinMutation = useAtomCommand(threadEnvironment.unpin, { reportFailure: false }); + const setAutoSettleMutation = useAtomCommand(threadEnvironment.setAutoSettle, { + reportFailure: false, + }); const updateThreadMetadata = useAtomCommand(threadEnvironment.updateMetadata, { reportFailure: false, }); @@ -435,6 +452,34 @@ export function useThreadListActions(): { }, [unpinMutation], ); + const setThreadAutoSettle = useCallback( + async (thread: EnvironmentThreadShell, enabled: boolean) => { + if (!environmentSupportsAutoSettleOptOut(thread.environmentId)) { + Alert.alert( + "Could not update auto-settle", + "This environment's server does not support turning auto-settle off per thread yet. Update the server to use it.", + ); + return false; + } + selectionHaptic(); + const result = await setAutoSettleMutation({ + environmentId: thread.environmentId, + input: { threadId: thread.id, enabled }, + }); + if (result._tag === "Failure") { + const error = Cause.squash(result.cause); + Alert.alert( + "Could not update auto-settle", + error instanceof Error && error.message.trim().length > 0 + ? error.message + : "The auto-settle setting could not be changed.", + ); + return false; + } + return true; + }, + [setAutoSettleMutation], + ); const regenerateThreadTitle = useCallback( async (thread: EnvironmentThreadShell) => { const key = scopedThreadKey(thread.environmentId, thread.id); @@ -698,6 +743,7 @@ export function useThreadListActions(): { unsettleThread, pinThread, unpinThread, + setThreadAutoSettle, moveThread, renameThread, regenerateThreadTitle, diff --git a/apps/mobile/src/features/observability/tracing.ts b/apps/mobile/src/features/observability/tracing.ts index eb73abba292b..ae204413e777 100644 --- a/apps/mobile/src/features/observability/tracing.ts +++ b/apps/mobile/src/features/observability/tracing.ts @@ -25,7 +25,7 @@ export function resolveTracingConfig(): TracingConfig | null { export function makeTracingLayer(config: TracingConfig | null, resource: TracingResource) { return makeRelayClientTracingLayer(config, { - serviceName: "t3-mobile-relay-client", + serviceName: "t3code-mobile", serviceVersion: resource.serviceVersion, runtime: "react-native", client: `mobile-${resource.appVariant}`, diff --git a/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx index 8c260e871a0d..29377b42dd83 100644 --- a/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsProjectOverviewRouteScreen.tsx @@ -96,9 +96,10 @@ function ProjectOverviewContent(props: { diff --git a/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts b/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts index 62f918a0e6b0..561cc53e44a7 100644 --- a/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts +++ b/apps/mobile/src/features/settings/appearance/useScaledTextRole.ts @@ -1,10 +1,6 @@ import { useMemo } from "react"; -import { - DEFAULT_BASE_FONT_SIZE, - normalizeBaseFontSize, - scaledTypographyLineHeight, -} from "../../../lib/appearancePreferences"; +import { resolveScaledTextRole } from "../../../lib/appearancePreferences"; import { MOBILE_TYPOGRAPHY } from "../../../lib/typography"; import { useAppearancePreferences } from "./AppearancePreferencesProvider"; @@ -20,15 +16,8 @@ export interface ScaledTextRole { */ export function useScaledTextRole(role: keyof typeof MOBILE_TYPOGRAPHY): ScaledTextRole { const { appearance } = useAppearancePreferences(); - return useMemo(() => { - const baseFontSize = normalizeBaseFontSize(appearance.baseFontSize); - const typography = MOBILE_TYPOGRAPHY[role]; - return { - fontSize: Math.max( - 8, - Math.round(typography.fontSize * (baseFontSize / DEFAULT_BASE_FONT_SIZE)), - ), - lineHeight: scaledTypographyLineHeight(typography, baseFontSize), - }; - }, [appearance.baseFontSize, role]); + return useMemo( + () => resolveScaledTextRole(role, appearance.baseFontSize), + [appearance.baseFontSize, role], + ); } diff --git a/apps/mobile/src/features/showcase/stageShowcaseAgentActivity.ts b/apps/mobile/src/features/showcase/stageShowcaseAgentActivity.ts index 6b1f61815b37..c4b35525c782 100644 --- a/apps/mobile/src/features/showcase/stageShowcaseAgentActivity.ts +++ b/apps/mobile/src/features/showcase/stageShowcaseAgentActivity.ts @@ -25,6 +25,9 @@ export async function stageShowcaseAgentActivity( ios: { allowAlert: true, allowBadge: true, allowSound: true }, }); if (!permission.granted) return `notification permission ${permission.status}`; + // A previous appearance's pass left its alert delivered; it would stack + // under the new one. + await Notifications.dismissAllNotificationsAsync(); if (Platform.OS === "android") { return ( diff --git a/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx b/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx index 681f6c5ee52d..7e90b0743374 100644 --- a/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx +++ b/apps/mobile/src/features/threads/NewTaskDraftScreen.tsx @@ -132,30 +132,40 @@ import { fileRoutePathSegments } from "../files/filePath"; function NewTaskWorkspaceIcon(props: { readonly workspaceMode: "local" | "worktree"; readonly worktreePath: string | null; + readonly size: number; }) { if (props.workspaceMode === "local" && props.worktreePath === null) { return ( ); } + const boxSize = (14 * props.size) / 16; return ( - + - + @@ -1476,13 +1486,13 @@ export function NewTaskDraftScreen(props: { accessibilityLabel={`Environment: ${selectedEnvironmentLabel}`} chevronDirection="right" disabled={isComposerInteractionLocked || voiceInput.isBusy} - iconNode={ + renderIcon={(size) => ( - } + )} label={`on ${selectedEnvironmentLabel}`} maxWidth={260} onPress={ @@ -1517,12 +1527,13 @@ export function NewTaskDraftScreen(props: { accessibilityHint={`Switches to ${flow.workspaceMode === "local" ? "a new worktree" : "the current checkout"}`} accessibilityLabel={workspaceLabel} disabled={isComposerInteractionLocked || voiceInput.isBusy} - iconNode={ + renderIcon={(size) => ( - } + )} label={workspaceLabel} maxWidth={flow.workspaceMode === "local" ? 220 : 148} onPress={() => flow.setWorkspaceMode(flow.workspaceMode === "local" ? "worktree" : "local")} @@ -1681,12 +1692,12 @@ export function NewTaskDraftScreen(props: { accessibilityLabel="Model and reasoning settings" disabled={isComposerInteractionLocked} emphasized - iconNode={ + renderIcon={(size) => ( - } + )} label={flow.selectedModelOption?.label ?? "Choose model"} maxWidth="100%" onPress={settingsSheetPresentation.open} diff --git a/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx b/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx index f74542076be2..27a9b3217c7e 100644 --- a/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx +++ b/apps/mobile/src/features/threads/NewTaskRouteScreen.tsx @@ -333,6 +333,7 @@ export function NewTaskRouteScreen({ route }: StaticScreenProps - } + renderIcon={(size) => ( + + )} label={currentModelOption?.label ?? currentModelSelection.model} maxWidth="100%" onPress={openSettings} diff --git a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx index 845d67483c83..e32240748861 100644 --- a/apps/mobile/src/features/threads/ThreadDetailScreen.tsx +++ b/apps/mobile/src/features/threads/ThreadDetailScreen.tsx @@ -79,6 +79,7 @@ import { useEnvironmentQuery } from "../../state/query"; import { threadDevicePreviews } from "../devices/threadDevicePreviews"; import type { QueuedThreadMessage } from "../../state/thread-outbox-model"; import { scopedThreadKey } from "../../lib/scopedEntities"; +import { useDelayedStatus } from "../../lib/useDelayedStatus"; import type { PendingApproval, PendingUserInput, @@ -354,7 +355,7 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread // The raw sync status enters "synchronizing" on every full fetch, cached or // not. Whether messages are already on screen decides the pill label: no // data yet → "Loading messages", cached data reconciling → "Syncing". - const threadSyncLabel = (() => { + const realThreadSyncLabel = (() => { switch (props.threadSyncStatus) { case "empty": case "cached": @@ -367,6 +368,9 @@ export const ThreadDetailScreen = memo(function ThreadDetailScreen(props: Thread return null; } })(); + // Opening a running thread resyncs for a few frames. The pill shows the + // sync label only when the sync lasts, so it does not flash before the timer. + const threadSyncLabel = useDelayedStatus(selectedThreadKey, realThreadSyncLabel); // One floating pill above the composer: it reads the connection phase while // disconnected, the sync state while messages load, then the working timer // once the feed is settled. diff --git a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx index 160392fdd5d9..d9e165bd0107 100644 --- a/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx +++ b/apps/mobile/src/features/threads/ThreadNavigationSidebar.tsx @@ -1,3 +1,4 @@ +import { useAndroidControlSizing } from "../../components/useAndroidControlSizing"; import { useAppearancePreferences } from "../settings/appearance/AppearancePreferencesProvider"; import { computeThreadMoveAvailability } from "./threadOrder"; import type { @@ -50,7 +51,7 @@ import { } from "../home/WorkspaceConnectionTitle"; import { SidebarHeaderActions } from "./sidebar-header-actions"; import { MaterialThreadListToolbar } from "../home/MaterialThreadListToolbar"; -import { useMaterialToolbarHeight } from "../../components/useMaterialToolbarHeight"; +import { useMaterialToolbarLayout } from "../../components/useMaterialToolbarLayout"; import { useMaterialFabScroll } from "../home/MaterialFabScrollContext"; import { SidebarFilterButton } from "./sidebar-filter-button"; import { createSidebarHeaderItems } from "./sidebar-native-header-items"; @@ -133,6 +134,7 @@ function ThreadNavigationSidebarPane( const drawerColor = materialTheme["--color-drawer"]; const insets = useSafeAreaInsets(); + const { fabClearance } = useAndroidControlSizing(); const projects = useProjects(); const threads = useThreadShells(); const { environments: workspaceEnvironments, state: catalogState } = useWorkspaceState(); @@ -150,6 +152,7 @@ function ThreadNavigationSidebarPane( unsettleThread, pinThread, unpinThread, + setThreadAutoSettle, moveThread, renameThread, regenerateThreadTitle, @@ -334,6 +337,15 @@ function ThreadNavigationSidebarPane( } return supported; }, [serverConfigs]); + const autoSettleOptOutEnvironmentIds = useMemo(() => { + const supported = new Set(); + for (const [environmentId, config] of serverConfigs) { + if (config.environment.capabilities.threadAutoSettleOptOut === true) { + supported.add(environmentId); + } + } + return supported; + }, [serverConfigs]); const pinReorderEnvironmentIds = useMemo(() => { const supported = new Set(); for (const [environmentId, config] of serverConfigs) { @@ -593,14 +605,14 @@ function ThreadNavigationSidebarPane( ); const [measuredHeaderHeight, setMeasuredHeaderHeight] = useState(null); - const materialToolbarHeight = useMaterialToolbarHeight(); + const { height, paddingTop, paddingBottom } = useMaterialToolbarLayout(); // The sticky header (title row, search field, optional connection status) // is measured so the list inset always matches its real height — no // hardcoded per-variant constants. const stickyHeaderHeight = measuredHeaderHeight ?? (Platform.OS === "android" - ? Math.max(insets.top, 12) + materialToolbarHeight + 8 + ? paddingTop + height + paddingBottom : insets.top + SIDEBAR_STICKY_HEADER_HEIGHT); const topListInset = stickyHeaderHeight + 6; const handleStickyHeaderLayout = useCallback((event: LayoutChangeEvent) => { @@ -766,6 +778,7 @@ function ThreadNavigationSidebarPane( onSettleThread={settleThread} snoozeSupported={snoozeEnvironmentIds.has(thread.environmentId)} pinningSupported={pinningEnvironmentIds.has(thread.environmentId)} + autoSettleOptOutSupported={autoSettleOptOutEnvironmentIds.has(thread.environmentId)} reorderSupported={ item.item.pinned ? pinReorderEnvironmentIds.has(thread.environmentId) @@ -778,6 +791,7 @@ function ThreadNavigationSidebarPane( onUnsettleThread={unsettleThread} onPinThread={pinThread} onUnpinThread={unpinThread} + onSetThreadAutoSettle={setThreadAutoSettle} onMoveThread={moveThread} onSwipeableClose={handleSwipeableClose} onSwipeableWillOpen={handleSwipeableWillOpen} @@ -829,6 +843,8 @@ function ThreadNavigationSidebarPane( pinReorderEnvironmentIds, pinThread, pinningEnvironmentIds, + autoSettleOptOutEnvironmentIds, + setThreadAutoSettle, projectByKey, projectTitleByProjectKey, regenerateThreadTitle, @@ -1025,7 +1041,7 @@ function ThreadNavigationSidebarPane( { paddingBottom: Platform.OS === "android" - ? Math.max(insets.bottom, 16) + 148 - insets.bottom + ? Math.max(insets.bottom, 16) + fabClearance - insets.bottom : 16 + insets.bottom, paddingTop: Platform.OS === "android" ? 6 : topListInset, }, diff --git a/apps/mobile/src/features/threads/thread-list-v2-items.tsx b/apps/mobile/src/features/threads/thread-list-v2-items.tsx index 43c5de09c726..eb089b95060a 100644 --- a/apps/mobile/src/features/threads/thread-list-v2-items.tsx +++ b/apps/mobile/src/features/threads/thread-list-v2-items.tsx @@ -35,6 +35,7 @@ import { copyTextWithHaptic } from "../../lib/copyTextWithHaptic"; import { useUniwindTheme } from "../../lib/useUniwindTheme"; import type { PendingNewTask } from "../../state/use-pending-new-tasks"; import { useThreadPr } from "../../state/use-thread-pr"; +import { useSwipeRowDormant } from "../home/swipe-row-activation"; import { ThreadSwipeable } from "../home/thread-swipe-actions"; import { buildThreadTitleRegenerationMenuItems } from "./thread-title-regeneration-menu"; import { @@ -294,8 +295,9 @@ export const ThreadListV2PendingRow = memo(function ThreadListV2PendingRow(props ) : null} @@ -493,6 +495,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { readonly onArchiveThread: (thread: EnvironmentThreadShell) => void; readonly onPinThread: (thread: EnvironmentThreadShell) => void; readonly onUnpinThread: (thread: EnvironmentThreadShell) => void; + readonly onSetThreadAutoSettle: (thread: EnvironmentThreadShell, enabled: boolean) => void; /** False on environments whose server predates thread.settle/unsettle: swipe + menu fall back to Archive instead of failing on use. */ readonly settlementSupported: boolean; @@ -500,6 +503,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { readonly snoozeSupported: boolean; /** False on servers that predate thread.pin/unpin. */ readonly pinningSupported: boolean; + /** False on servers that predate thread.auto-settle.set. */ + readonly autoSettleOptOutSupported: boolean; /** False on servers that predate thread title regeneration. */ readonly titleRegenerationSupported: boolean; /** Server supports reordering this card's section. */ @@ -514,6 +519,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { readonly canMoveDown?: boolean; readonly onSwipeableWillOpen: (methods: SwipeableMethods) => void; readonly onSwipeableClose: (methods: SwipeableMethods) => void; + /** List key checked against the Home swipe row activation. */ + readonly activationKey?: string; readonly searchMatch?: EnvironmentThreadSearchMatch; readonly searchQuery?: string; readonly simultaneousSwipeGesture?: ComponentProps< @@ -536,10 +543,12 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { onArchiveThread, onPinThread, onUnpinThread, + onSetThreadAutoSettle, onMoveThread, } = props; const snoozedRow = props.snoozed === true; const pinnedRow = props.pinned === true; + const dormant = useSwipeRowDormant(props.activationKey); const pr = useThreadPr(thread); @@ -583,6 +592,10 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { const handleUnsettle = useCallback(() => onUnsettleThread(thread), [onUnsettleThread, thread]); const handlePin = useCallback(() => onPinThread(thread), [onPinThread, thread]); const handleUnpin = useCallback(() => onUnpinThread(thread), [onUnpinThread, thread]); + const handleSetAutoSettle = useCallback( + (enabled: boolean) => onSetThreadAutoSettle(thread, enabled), + [onSetThreadAutoSettle, thread], + ); const handleMoveUp = useCallback(() => onMoveThread?.(thread, "up"), [onMoveThread, thread]); const handleMoveDown = useCallback(() => onMoveThread?.(thread, "down"), [onMoveThread, thread]); const handleArchive = useCallback(() => onArchiveThread(thread), [onArchiveThread, thread]); @@ -661,6 +674,33 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { variant, ], ); + // A submenu with the current option checked, matching web. This is a + // per-thread setting, not a lifecycle verb. + const autoSettleMenuItems = useMemo( + () => + props.autoSettleOptOutSupported + ? [ + { + id: "auto-settle", + title: "Auto-settle behavior", + image: "timer", + subactions: [ + { + id: "auto-settle:enabled", + title: "Enabled", + state: thread.autoSettleDisabledAt == null ? "on" : "off", + }, + { + id: "auto-settle:disabled", + title: "Disabled", + state: thread.autoSettleDisabledAt == null ? "off" : "on", + }, + ], + } satisfies MenuAction, + ] + : [], + [props.autoSettleOptOutSupported, thread.autoSettleDisabledAt], + ); const titleMenuItems = useMemo( () => [ { id: "rename", title: "Rename", image: "square.and.pencil" }, @@ -682,19 +722,23 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { }, ...arrangementMenuItems, ...titleMenuItems, + ...autoSettleMenuItems, { id: "delete", title: "Delete", image: "trash", attributes: { destructive: true } }, ], - [arrangementMenuItems, snoozePresetActions, titleMenuItems], + [arrangementMenuItems, autoSettleMenuItems, snoozePresetActions, titleMenuItems], ); const cardMenuActions = useMemo( () => [ CARD_MENU_ACTIONS[0]!, ...arrangementMenuItems, ...titleMenuItems, + ...autoSettleMenuItems, ...CARD_MENU_ACTIONS.slice(1), ], - [arrangementMenuItems, titleMenuItems], + [arrangementMenuItems, autoSettleMenuItems, titleMenuItems], ); + // Settled and snoozed rows keep the setting too, matching web where every + // row shares one menu builder. const slimMenuActions = useMemo( () => [ SLIM_MENU_ACTIONS[0]!, @@ -702,13 +746,19 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { (action) => action.id !== "move-up" && action.id !== "move-down", ), ...titleMenuItems, + ...autoSettleMenuItems, SLIM_MENU_ACTIONS[1]!, ], - [arrangementMenuItems, titleMenuItems], + [arrangementMenuItems, autoSettleMenuItems, titleMenuItems], ); const snoozedMenuActions = useMemo( - () => [SNOOZED_MENU_ACTIONS[0]!, ...titleMenuItems, SNOOZED_MENU_ACTIONS[1]!], - [titleMenuItems], + () => [ + SNOOZED_MENU_ACTIONS[0]!, + ...titleMenuItems, + ...autoSettleMenuItems, + SNOOZED_MENU_ACTIONS[1]!, + ], + [autoSettleMenuItems, titleMenuItems], ); const legacyMenuActions = useMemo( () => [ @@ -727,6 +777,8 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { if (nativeEvent.event === "unsnooze") handleUnsnooze(); if (nativeEvent.event === "pin") handlePin(); if (nativeEvent.event === "unpin") handleUnpin(); + if (nativeEvent.event === "auto-settle:enabled") handleSetAutoSettle(true); + if (nativeEvent.event === "auto-settle:disabled") handleSetAutoSettle(false); if (nativeEvent.event === "arrange") appAtomRegistry.set(threadArrangementOpenAtom, true); if (nativeEvent.event === "move-up") handleMoveUp(); if (nativeEvent.event === "move-down") handleMoveDown(); @@ -764,6 +816,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { handlePin, handleSettle, handleSnooze, + handleSetAutoSettle, handleUnpin, handleUnsettle, handleUnsnooze, @@ -842,8 +895,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { ) : null} @@ -1076,8 +1130,9 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { @@ -1129,6 +1184,7 @@ export const ThreadListV2Row = memo(function ThreadListV2Row(props: { setCustomSnoozeOpen(false)} onSnooze={handleSnooze} /> )} reset.restoresPercent > 0); @@ -96,7 +102,7 @@ function PoolWindowCard({ - {pool.label} + {label ?? pool.label} {pool.remainingPercent}% @@ -108,6 +114,7 @@ function PoolWindowCard({ {PACE_LABEL[pool.pace]} ) : null} + {description ? {description} : null} {nextRefill ? ( ↻ +{nextRefill.restoresPercent}%{" "} @@ -196,10 +203,12 @@ export function UsageLimitsSection({ now, failedLabels, selectedEnvironmentIds, + cursorPrompt, }: { readonly now: number; readonly failedLabels: readonly string[]; readonly selectedEnvironmentIds: ReadonlySet | null; + readonly cursorPrompt?: ReactNode; }) { const presentations = useAtomValue(environmentPresentations.presentationsAtom); const selected = @@ -209,34 +218,54 @@ export function UsageLimitsSection({ const pools = collectLimitPools(collectLimitAccounts(selected), now); const notices = collectLimitNotices(selected); const colors = useProviderColors(); + const cursorPromptAt = + Math.max( + pools.findIndex((pool) => pool.driver === "codex"), + pools.findIndex((pool) => pool.driver === "claudeAgent"), + ) + 1; return ( - {pools.length === 0 && notices.length === 0 && failedLabels.length === 0 ? ( + {pools.length === 0 && notices.length === 0 && failedLabels.length === 0 && !cursorPrompt ? ( {selected.size === 0 ? "Select an environment to see limits." : "No provider on the selected environments reports subscription limits."} ) : null} - {pools.map((pool) => ( - - - - - {DRIVER_LABEL[pool.driver] ?? pool.driver} - - - {pool.windows.map((window) => ( - - ))} - - ))} + {pools.map((pool, index) => { + const windows = displayLimitWindows(pool); + return ( + + {index === cursorPromptAt ? cursorPrompt : null} + + + + + {DRIVER_LABEL[pool.driver] ?? pool.driver} + + + {windows.map((window) => { + const details = + pool.driver === "cursor" ? cursorUsageWindowDetails(window.id) : undefined; + return ( + + ); + })} + + + ); + })} + {cursorPromptAt === pools.length ? cursorPrompt : null} {notices.length > 0 || failedLabels.length > 0 ? ( ([]); - const refresh = async (automatic = false) => { + const refresh = async (automatic = false, afterPending = false) => { const connected = [...presentations].filter( ([environmentId, presentation]) => presentation.connection.phase === "connected" && @@ -307,6 +307,7 @@ export function useRefreshLimits( environmentId, () => refreshProviders({ environmentId, input: {} }), automatic, + afterPending, ); if (result === undefined) return; setFailedEnvironments((previous) => [ @@ -354,5 +355,11 @@ export function useRefreshLimits( selectedEnvironmentIds === null || selectedEnvironmentIds.has(environmentId), ) .map(({ label }) => label); - return { now, refreshing, failedLabels, refresh: refreshManually }; + return { + now, + refreshing, + failedLabels, + refresh: refreshManually, + refreshAfterEnable: () => refresh(false, true), + }; } diff --git a/apps/mobile/src/features/usage/UsageRouteScreen.tsx b/apps/mobile/src/features/usage/UsageRouteScreen.tsx index 7494ac9b34bd..2d5e5398bc51 100644 --- a/apps/mobile/src/features/usage/UsageRouteScreen.tsx +++ b/apps/mobile/src/features/usage/UsageRouteScreen.tsx @@ -25,9 +25,12 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; import { SegmentedControl } from "../../components/SegmentedControl"; import { AppText as Text } from "../../components/AppText"; +import { ProviderIcon } from "../../components/ProviderIcon"; import { cn } from "../../lib/cn"; import { SettingsScreen } from "../settings/components/SettingsScreen"; import { useUsage, type EnvironmentUsageStatus } from "../../state/usage"; +import { serverEnvironment } from "../../state/server"; +import { useAtomCommand } from "../../state/use-atom-command"; import { SettingsSection } from "../settings/components/SettingsSection"; import { UsageDailyChart } from "./UsageDailyChart"; import { toggleUsageEnvironment } from "./usageEnvironmentSelection"; @@ -59,6 +62,7 @@ const METRIC_OPTIONS = [ ] as const satisfies readonly { value: UsageChartMetric; label: string }[]; const CHART_HEIGHT = 180; +const CURSOR_KEYCHAIN_COPY = "Requires access to your Cursor login in macOS Keychain."; /** * Two tabs over one screen. Usage is the transcript-derived spend for a @@ -97,6 +101,29 @@ export function UsageRouteScreen() { ); const isFocused = useIsFocused(); const limits = useRefreshLimits(selectedEnvironmentIds, isFocused && tab === "limits"); + const cursorAccessEnvironments = selectedEnvironments.filter((environment) => + environment.summary?.sources.some((source) => source.action === "enableCursorKeychain"), + ); + const refreshAfterCursorEnable = () => { + void refresh(); + void limits.refreshAfterEnable(); + }; + const sourceMessages = [ + ...new Set( + selectedEnvironments.flatMap( + (environment) => + environment.summary?.sources.flatMap((source) => + source.message && + !source.action && + (source.status === "partial" || + source.status === "failed" || + source.fingerprint.provider === "cursor") + ? [source.message] + : [], + ) ?? [], + ), + ), + ]; const days = useMemo( () => enumerateDays(window.sinceDay, window.untilDay), @@ -251,7 +278,6 @@ export function UsageRouteScreen() { } > - 0 ? ( + + ) : null + } /> ) : ( <> @@ -301,6 +335,11 @@ export function UsageRouteScreen() { ) : ( <> + {sourceMessages.map((message) => ( + + {message} + + ))} - + 1} + onCursorEnabled={refreshAfterCursorEnable} + /> @@ -324,6 +369,110 @@ export function UsageRouteScreen() { ); } +function CursorEnableAction({ + environmentId, + label, + onEnabled, + buttonText = "Enable", +}: { + readonly environmentId: EnvironmentId; + readonly label: string; + readonly onEnabled: () => void; + readonly buttonText?: string; +}) { + const updateSettings = useAtomCommand(serverEnvironment.updateSettings, { + label: "enable Cursor account usage", + }); + const [pending, setPending] = useState(false); + const enable = async () => { + setPending(true); + try { + const result = await updateSettings({ + environmentId, + input: { patch: { cursorKeychainUsageEnabled: true } }, + }); + if (result._tag === "Success") onEnabled(); + } finally { + setPending(false); + } + }; + return ( + void enable()} + className="rounded-full bg-primary px-4 py-2" + > + {buttonText} + + ); +} + +function CursorEnableRow({ + environmentId, + label, + showEnvironment, + bordered, + onEnabled, +}: { + readonly environmentId: EnvironmentId; + readonly label: string; + readonly showEnvironment: boolean; + readonly bordered: boolean; + readonly onEnabled: () => void; +}) { + const colors = useProviderColors(); + return ( + + + + + Cursor{showEnvironment ? ` · ${label}` : ""} + + + + + ); +} + +function CursorEnableLimits({ + environments, + onEnabled, +}: { + readonly environments: readonly EnvironmentUsageStatus[]; + readonly onEnabled: () => void; +}) { + return ( + + + + Cursor + + + {CURSOR_KEYCHAIN_COPY} + + {environments.map((environment) => ( + 1 ? `Enable on ${environment.label}` : "Enable"} + onEnabled={onEnabled} + /> + ))} + + + + ); +} + /** Headline figure, the animated daily chart, and its legend, in one card. */ function ChartCard(props: { readonly merged: MergedUsage; @@ -400,20 +549,53 @@ function ChartCard(props: { function ProviderSection(props: { readonly merged: MergedUsage; readonly metric: UsageChartMetric; + readonly cursorAccessEnvironments: readonly EnvironmentUsageStatus[]; + readonly showCursorEnvironment: boolean; + readonly onCursorEnabled: () => void; }) { const { merged, metric } = props; const colors = useProviderColors(); - if (merged.providers.length === 0) return null; + if (merged.providers.length === 0 && props.cursorAccessEnvironments.length === 0) return null; // Ranked by whatever the toggle is showing, so the rows always descend. // .sort() on a copy, not .toSorted(): Hermes doesn't ship the ES2023 method. const ordered = [...merged.providers].sort((a, b) => metric === "cost" ? b.costUsd - a.costUsd : b.totalTokens - a.totalTokens, ); + const rows: Array< + | { readonly kind: "usage"; readonly provider: (typeof ordered)[number] } + | { readonly kind: "enable"; readonly environment: EnvironmentUsageStatus } + > = ordered.map((provider) => ({ kind: "usage", provider })); + const cursorInsertAt = + Math.max( + ordered.findIndex((provider) => provider.provider === "codex"), + ordered.findIndex((provider) => provider.provider === "claude"), + ) + 1; + rows.splice( + cursorInsertAt, + 0, + ...props.cursorAccessEnvironments.map((environment) => ({ + kind: "enable" as const, + environment, + })), + ); return ( - {ordered.map((provider, index) => { + {rows.map((row, index) => { + if (row.kind === "enable") { + return ( + 0} + onEnabled={props.onCursorEnabled} + /> + ); + } + const provider = row.provider; const share = metric === "cost" ? provider.costShare : provider.tokenShare; return ( = { claude: "Claude Code", codex: "Codex", grok: "Grok Build", + cursor: "Cursor", + opencode: "OpenCode", + antigravity: "Antigravity", }; /** @@ -23,5 +33,8 @@ export function useProviderColors(): Record { claude: "#d97757", codex: scheme === "dark" ? "#e6e6e6" : "#3c3c43", grok: scheme === "dark" ? "#a1a1aa" : "#52525b", + cursor: "#8b8b8b", + opencode: "#5b9bbd", + antigravity: "#8c7bd1", }; } diff --git a/apps/mobile/src/lib/androidControlSizing.test.ts b/apps/mobile/src/lib/androidControlSizing.test.ts new file mode 100644 index 000000000000..8b9fd9751cbb --- /dev/null +++ b/apps/mobile/src/lib/androidControlSizing.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { resolveAndroidControlSizing } from "./androidControlSizing"; + +describe("Android control sizing", () => { + it.each([ + [11, 17, 48, 48, 172, 48], + [16, 24, 48, 56, 250, 48], + [22, 33, 66, 77, 344, 66], + ])( + "scales controls at %ipt", + (fontSize, iconSize, buttonSize, fabSize, menuWidth, menuItemHeight) => { + expect(resolveAndroidControlSizing(fontSize)).toMatchObject({ + iconSize, + buttonSize, + fabSize, + menuWidth, + menuItemHeight, + }); + }, + ); +}); diff --git a/apps/mobile/src/lib/androidControlSizing.ts b/apps/mobile/src/lib/androidControlSizing.ts new file mode 100644 index 000000000000..f6868ad07bdd --- /dev/null +++ b/apps/mobile/src/lib/androidControlSizing.ts @@ -0,0 +1,23 @@ +import { DEFAULT_BASE_FONT_SIZE, normalizeBaseFontSize } from "./appearancePreferences"; + +/** Android controls follow the app's text size; buttons and menu rows retain a 48dp touch target. */ +export function resolveAndroidControlSizing(baseFontSize: number) { + const scale = normalizeBaseFontSize(baseFontSize) / DEFAULT_BASE_FONT_SIZE; + const iconSize = Math.round(24 * scale); + const buttonSize = Math.max(48, Math.round(48 * scale)); + const fabSize = Math.max(48, Math.round(56 * scale)); + + return { + scale, + iconSize, + smallIconSize: Math.round(16 * scale), + mediumIconSize: Math.round(18 * scale), + buttonSize, + fabSize, + largeFabSize: Math.round(96 * scale), + menuWidth: Math.round(250 * scale), + menuItemHeight: Math.max(48, Math.round(48 * scale)), + // Two floating actions, their gap, and the space below the lower action. + fabClearance: fabSize * 2 + 36, + }; +} diff --git a/apps/mobile/src/lib/appearancePreferences.ts b/apps/mobile/src/lib/appearancePreferences.ts index 2ce6a8b5a367..981b35808a54 100644 --- a/apps/mobile/src/lib/appearancePreferences.ts +++ b/apps/mobile/src/lib/appearancePreferences.ts @@ -219,6 +219,16 @@ export function scaledTypographyLineHeight( return Math.max(10, Math.round(role.lineHeight * scale)); } +/** Text dimensions shared by React Native and Compose consumers of an appearance role. */ +export function resolveScaledTextRole(role: keyof typeof MOBILE_TYPOGRAPHY, baseFontSize: number) { + const typography = MOBILE_TYPOGRAPHY[role]; + const scale = normalizeBaseFontSize(baseFontSize) / DEFAULT_BASE_FONT_SIZE; + return { + fontSize: Math.max(8, Math.round(typography.fontSize * scale)), + lineHeight: scaledTypographyLineHeight(typography, baseFontSize), + }; +} + export function resolveNativeMarkdownTypography(baseFontSize: number): NativeMarkdownTypography { const fontSizes = resolveMarkdownFontSizes(baseFontSize); return { diff --git a/apps/mobile/src/lib/projectIcon.ts b/apps/mobile/src/lib/projectIcon.ts new file mode 100644 index 000000000000..885c583aa543 --- /dev/null +++ b/apps/mobile/src/lib/projectIcon.ts @@ -0,0 +1,81 @@ +import type { ProjectIconColor, ProjectIconOverride } from "@t3tools/contracts"; + +export type ProjectIconGlyph = + | { readonly kind: "emoji"; readonly emoji: string } + | { readonly kind: "monogram"; readonly text: string; readonly color: ProjectIconColor }; + +/** + * Visible glyph count for sizing monogram text. Hermes has no Intl.Segmenter, so combining + * marks are folded into their base character instead of full grapheme segmentation. + */ +export function countGlyphs(text: string): number { + return Array.from(text.replace(/\p{M}/gu, "")).length; +} + +/** Mirrors the automatic monogram web derives from a project name when it has no favicon. */ +export function projectMonogram(projectName: string): string { + const words = + projectName + .normalize("NFKC") + .trim() + .match(/[\p{L}\p{N}]+/gu) ?? []; + const firstWord = words[0]; + if (!firstWord) return "PR"; + + const glyphs = Array.from(firstWord); + const first = glyphs[0] ?? "P"; + const second = + glyphs.slice(1).find((glyph) => /\p{N}/u.test(glyph)) ?? + (words.length > 1 ? Array.from(words.at(-1) ?? "")[0] : glyphs.at(-1)) ?? + first; + return Array.from(`${first}${second}`.toUpperCase()).slice(0, 2).join(""); +} + +/** + * Picks what mobile draws for an assigned project icon. Mobile does not bundle + * the Lucide set, so a Lucide override keeps its color and falls back to the + * project's monogram instead of the folder glyph. + */ +export function resolveProjectIconGlyph( + projectIcon: ProjectIconOverride | null | undefined, + projectTitle: string, +): ProjectIconGlyph | null { + switch (projectIcon?.kind) { + case "emoji": + return { kind: "emoji", emoji: projectIcon.emoji }; + case "monogram": + return { kind: "monogram", text: projectIcon.text, color: projectIcon.color }; + case "lucide": + return { kind: "monogram", text: projectMonogram(projectTitle), color: projectIcon.color }; + case undefined: + return null; + } +} + +const PROJECT_ICON_COLOR_CLASSES: Record< + ProjectIconColor, + { readonly text: string; readonly background: string } +> = { + gray: { text: "text-gray-500", background: "bg-gray-500/15" }, + red: { text: "text-red-500", background: "bg-red-500/15" }, + orange: { text: "text-orange-500", background: "bg-orange-500/15" }, + amber: { text: "text-amber-500", background: "bg-amber-500/15" }, + yellow: { text: "text-yellow-500", background: "bg-yellow-500/15" }, + lime: { text: "text-lime-500", background: "bg-lime-500/15" }, + green: { text: "text-green-500", background: "bg-green-500/15" }, + emerald: { text: "text-emerald-500", background: "bg-emerald-500/15" }, + teal: { text: "text-teal-500", background: "bg-teal-500/15" }, + cyan: { text: "text-cyan-500", background: "bg-cyan-500/15" }, + sky: { text: "text-sky-500", background: "bg-sky-500/15" }, + blue: { text: "text-blue-500", background: "bg-blue-500/15" }, + indigo: { text: "text-indigo-500", background: "bg-indigo-500/15" }, + violet: { text: "text-violet-500", background: "bg-violet-500/15" }, + purple: { text: "text-purple-500", background: "bg-purple-500/15" }, + fuchsia: { text: "text-fuchsia-500", background: "bg-fuchsia-500/15" }, + pink: { text: "text-pink-500", background: "bg-pink-500/15" }, + rose: { text: "text-rose-500", background: "bg-rose-500/15" }, +}; + +export function projectIconColorClassNames(color: ProjectIconColor) { + return PROJECT_ICON_COLOR_CLASSES[color]; +} diff --git a/apps/mobile/src/lib/useDelayedStatus.ts b/apps/mobile/src/lib/useDelayedStatus.ts new file mode 100644 index 000000000000..a96edfdf23df --- /dev/null +++ b/apps/mobile/src/lib/useDelayedStatus.ts @@ -0,0 +1,18 @@ +import { createDelayedStatus, type ShownStatus } from "@t3tools/client-runtime/delayed-status"; +import { useEffect, useState } from "react"; + +/** + * Returns `value` only once it has lasted past the show delay, then holds it + * for a minimum time, so a short status never flashes. `key` is what the + * status belongs to (for example a thread). A new key drops it at once. + * Web has the same hook. + */ +export function useDelayedStatus(key: string, value: A | null): A | null { + const [shown, setShown] = useState | null>(null); + const [status] = useState(() => createDelayedStatus(setShown)); + useEffect(() => () => status.dispose(), [status]); + useEffect(() => { + status.update(key, value); + }, [status, key, value]); + return shown?.key === key ? shown.value : null; +} diff --git a/apps/mobile/src/state/use-thread-selection.ts b/apps/mobile/src/state/use-thread-selection.ts index d922eb7f6d5c..108afbbc9416 100644 --- a/apps/mobile/src/state/use-thread-selection.ts +++ b/apps/mobile/src/state/use-thread-selection.ts @@ -74,6 +74,7 @@ function threadDetailToShell( settledAt: thread.settledAt, unsettledAt: thread.unsettledAt, activeOrderKey: thread.activeOrderKey, + autoSettleDisabledAt: thread.autoSettleDisabledAt, pinnedAt: thread.pinnedAt, pinOrderKey: thread.pinOrderKey, snoozedUntil: thread.snoozedUntil ?? null, diff --git a/apps/mobile/src/widgets/SubscriptionUsage.tsx b/apps/mobile/src/widgets/SubscriptionUsage.tsx index c8cde5d444fe..a32454e5ff83 100644 --- a/apps/mobile/src/widgets/SubscriptionUsage.tsx +++ b/apps/mobile/src/widgets/SubscriptionUsage.tsx @@ -235,7 +235,11 @@ function SubscriptionUsage( spacing={accessory || dense ? 2 : 6} modifiers={props.url ? [widgetURL(props.url)] : []} > - {compact ? ( + {providers.length === 0 ? ( + + No subscription limits available. + + ) : compact ? ( {columns} diff --git a/apps/mobile/src/widgets/SubscriptionUsageCoordinator.tsx b/apps/mobile/src/widgets/SubscriptionUsageCoordinator.tsx index 84dce5d515ec..4a805b48ce22 100644 --- a/apps/mobile/src/widgets/SubscriptionUsageCoordinator.tsx +++ b/apps/mobile/src/widgets/SubscriptionUsageCoordinator.tsx @@ -2,6 +2,7 @@ import { useAtomValue } from "@effect/atom-react"; import { Atom } from "effect/unstable/reactivity"; import * as Linking from "expo-linking"; import { useEffect } from "react"; +import { Platform } from "react-native"; import { environmentCatalog } from "../connection/catalog"; import { environmentPresentations } from "../state/presentation"; import { publishSubscriptionUsage } from "./publishSubscriptionUsage"; @@ -13,6 +14,8 @@ const snapshotAtom = Atom.make((get) => buildSubscriptionUsageSnapshot( get(environmentPresentations.presentationsAtom), Linking.createURL("settings/usage", { queryParams: { tab: "limits" } }), + // Android scrolls the full list; iOS stores a bounded widget timeline. + Platform.OS === "android" ? Infinity : 6, ), ).pipe(Atom.withEquality((a, b) => JSON.stringify(a) === JSON.stringify(b))); diff --git a/apps/mobile/src/widgets/subscriptionUsageSnapshot.test.ts b/apps/mobile/src/widgets/subscriptionUsageSnapshot.test.ts index 65d4c9924f66..8ab3d6191ef5 100644 --- a/apps/mobile/src/widgets/subscriptionUsageSnapshot.test.ts +++ b/apps/mobile/src/widgets/subscriptionUsageSnapshot.test.ts @@ -65,18 +65,63 @@ describe("subscription widget snapshots", () => { expect(snapshot.url).toBe(deepLink); expect(JSON.stringify(snapshot)).not.toContain("private@example.com"); }); - it("clears data after removing environments and hides disabled providers", () => { - expect( - buildSubscriptionUsageSnapshot(new Map(), deepLink).providers.every( - (p) => p.windows.length === 0, - ), - ).toBe(true); + it("clears data after removing environments", () => { + expect(buildSubscriptionUsageSnapshot(new Map(), deepLink).providers).toEqual([]); + }); + it.each<{ name: string; overrides: Partial }>([ + { name: "disabled", overrides: { enabled: false } }, + { + name: "missing", + overrides: { installed: false, status: "error", usageLimits: undefined }, + }, + { + name: "API-key", + overrides: { + usageLimits: { checkedAt, windows: [], unavailable: { reason: "unsupported" } }, + }, + }, + ])("hides $name providers", ({ overrides }) => { expect( - buildSubscriptionUsageSnapshot( - presentations([provider({ enabled: false })]), - deepLink, - ).providers.every((p) => p.windows.length === 0), - ).toBe(true); + buildSubscriptionUsageSnapshot(presentations([provider(overrides)]), deepLink).providers, + ).toEqual([]); + }); + it.each([ + { name: "Codex", driver: "codex" }, + { name: "Claude", driver: "claudeAgent" }, + ])("only shows $name when $name and OpenCode are configured", ({ name, driver }) => { + const snapshot = buildSubscriptionUsageSnapshot( + presentations([ + provider({ + instanceId: ProviderInstanceId.make(driver), + driver: ProviderDriverKind.make(driver), + }), + provider({ + instanceId: ProviderInstanceId.make("opencode"), + driver: ProviderDriverKind.make("opencode"), + usageLimits: undefined, + }), + ]), + deepLink, + ); + expect(snapshot.providers).toHaveLength(1); + expect(snapshot.providers[0]).toMatchObject({ + name, + totalWindows: 1, + windows: [{ remaining: 60 }], + }); + expect(subscriptionUsageTimeline(snapshot, now + 15 * 60_000)[0]?.props.providers).toEqual([ + expect.objectContaining({ name, totalWindows: 0, windows: [] }), + ]); + }); + it("keeps an enabled provider visible before its first usage read", () => { + const snapshot = buildSubscriptionUsageSnapshot( + presentations([provider({ usageLimits: undefined })]), + deepLink, + ); + expect(snapshot.checkedAt).toBe(0); + expect(snapshot.providers).toEqual([ + { name: "Codex", detail: "No limits available", windows: [], expiresAt: 0, totalWindows: 0 }, + ]); }); it("uses upstream deduplication for a native account also present in a proxy hub", () => { const input = new Map([ @@ -151,6 +196,21 @@ describe("subscription widget snapshots", () => { expect(snapshot.providers[0]?.totalWindows).toBe(20); expect(snapshot.providers[0]?.windows[0]?.remaining).toBe(5); }); + it("includes every limit for the scrollable Android widget", () => { + const windows = Array.from({ length: 20 }, (_, index) => ({ + ...window, + id: `${index}`, + usedPercent: index * 5, + })); + const snapshot = buildSubscriptionUsageSnapshot( + presentations([provider({ usageLimits: { checkedAt, windows } })]), + deepLink, + Infinity, + ); + expect(snapshot.providers[0]?.windows.map((window) => window.remaining)).toEqual( + Array.from({ length: 20 }, (_, index) => 5 + index * 5), + ); + }); it("marks unknown or distant reset times stale after fifteen minutes", () => { const snapshot = buildSubscriptionUsageSnapshot( presentations([ diff --git a/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts b/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts index 826124e1053f..44f2c6ef4bed 100644 --- a/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts +++ b/apps/mobile/src/widgets/subscriptionUsageSnapshot.ts @@ -46,6 +46,8 @@ export function createWidgetRefresher(refresh: (id: Id) => Promise) function subscriptionUsageProps( accounts: readonly LimitAccount[], now: number, + configuredDrivers: ReadonlySet, + maxWindowsPerProvider: number, ): SubscriptionUsageSnapshot { const pools = collectLimitPools(accounts, now); const checked = accounts @@ -53,57 +55,68 @@ function subscriptionUsageProps( .map((account) => Date.parse(account.limits.checkedAt)); return { checkedAt: checked.length > 0 && checked.every(Number.isFinite) ? Math.min(...checked) : 0, - providers: (["codex", "claudeAgent"] as const).map((driver) => { - const pool = pools.find((candidate) => candidate.driver === driver); - const name = driver === "codex" ? "Codex" : "Claude"; - if (!pool) - return { name, detail: "No limits available", windows: [], expiresAt: 0, totalWindows: 0 }; - const checkedAt = Math.min(...pool.accounts.map((a) => Date.parse(a.limits.checkedAt))); - const expiresAt = Math.min( - checkedAt + SNAPSHOT_MAX_AGE, - ...pool.windows.flatMap((window) => window.resets.map((reset) => reset.at)), - ); - const fresh = Number.isFinite(expiresAt) && expiresAt > now; - const sortedWindows = [...pool.windows].sort( - (a, b) => a.remainingPercent - b.remainingPercent, - ); - // Keep a session and weekly limit when scoped limits fill the storage budget. - const selectedWindows = [ - ...new Set([ - sortedWindows.find((window) => window.kind === "session"), - sortedWindows.find((window) => window.kind === "weekly"), - ...sortedWindows, - ]), - ] - .filter((window) => window !== undefined) - .slice(0, 6) - .sort((a, b) => a.remainingPercent - b.remainingPercent); - return { - name, - detail: !fresh - ? "Open T3 to refresh" - : pool.accounts.length > 1 - ? `${pool.accounts.length} accounts · pooled` - : "Subscription remaining", - expiresAt: fresh ? expiresAt : 0, - totalWindows: fresh ? pool.windows.length : 0, - windows: fresh - ? selectedWindows.map((window) => ({ - kind: window.kind, - label: window.label, - remaining: Math.round(window.remainingPercent), - reset: window.resets[0] - ? `Next reset ${new Date(window.resets[0].at).toLocaleString(undefined, { - month: "short", - day: "numeric", - hour: "numeric", - minute: "2-digit", - })}` - : "Reset time unavailable", - })) - : [], - }; - }), + providers: (["codex", "claudeAgent"] as const) + .filter( + (driver) => + configuredDrivers.has(driver) || accounts.some((account) => account.driver === driver), + ) + .map((driver) => { + const pool = pools.find((candidate) => candidate.driver === driver); + const name = driver === "codex" ? "Codex" : "Claude"; + if (!pool) + return { + name, + detail: "No limits available", + windows: [], + expiresAt: 0, + totalWindows: 0, + }; + const checkedAt = Math.min(...pool.accounts.map((a) => Date.parse(a.limits.checkedAt))); + const expiresAt = Math.min( + checkedAt + SNAPSHOT_MAX_AGE, + ...pool.windows.flatMap((window) => window.resets.map((reset) => reset.at)), + ); + const fresh = Number.isFinite(expiresAt) && expiresAt > now; + const sortedWindows = [...pool.windows].sort( + (a, b) => a.remainingPercent - b.remainingPercent, + ); + // Keep a session and weekly limit when scoped limits fill the storage budget. + const selectedWindows = [ + ...new Set([ + sortedWindows.find((window) => window.kind === "session"), + sortedWindows.find((window) => window.kind === "weekly"), + ...sortedWindows, + ]), + ] + .filter((window) => window !== undefined) + .slice(0, maxWindowsPerProvider) + .sort((a, b) => a.remainingPercent - b.remainingPercent); + return { + name, + detail: !fresh + ? "Open T3 to refresh" + : pool.accounts.length > 1 + ? `${pool.accounts.length} accounts · pooled` + : "Subscription remaining", + expiresAt: fresh ? expiresAt : 0, + totalWindows: fresh ? pool.windows.length : 0, + windows: fresh + ? selectedWindows.map((window) => ({ + kind: window.kind, + label: window.label, + remaining: Math.round(window.remainingPercent), + reset: window.resets[0] + ? `Next reset ${new Date(window.resets[0].at).toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "numeric", + minute: "2-digit", + })}` + : "Reset time unavailable", + })) + : [], + }; + }), }; } @@ -111,9 +124,31 @@ function subscriptionUsageProps( export function buildSubscriptionUsageSnapshot( presentations: LimitPresentations, url: string, + maxWindowsPerProvider = 6, ): SubscriptionUsageSnapshot { // Freshness is evaluated at publication/render time, not on unrelated config emissions. - return { ...subscriptionUsageProps(collectLimitAccounts(presentations), 0), url }; + const configuredDrivers = new Set( + [...presentations.values()].flatMap((presentation) => + (presentation.serverConfig?.providers ?? []) + // Servers report default-enabled drivers even when their CLI is missing. + .filter( + (provider) => + provider.enabled && + provider.installed && + provider.usageLimits?.unavailable?.reason !== "unsupported", + ) + .map((provider) => provider.driver), + ), + ); + return { + ...subscriptionUsageProps( + collectLimitAccounts(presentations), + 0, + configuredDrivers, + maxWindowsPerProvider, + ), + url, + }; } export function subscriptionUsageTimeline(snapshot: SubscriptionUsageSnapshot, now: number) { diff --git a/apps/server/integration/OrchestrationEngineHarness.integration.ts b/apps/server/integration/OrchestrationEngineHarness.integration.ts index 0df54be2f701..1e116a91fea7 100644 --- a/apps/server/integration/OrchestrationEngineHarness.integration.ts +++ b/apps/server/integration/OrchestrationEngineHarness.integration.ts @@ -26,6 +26,7 @@ import * as Tracer from "effect/Tracer"; import * as CheckpointStore from "../src/checkpointing/CheckpointStore.ts"; import { TextGeneration } from "../src/textGeneration/TextGeneration.ts"; +import * as TerminalManager from "../src/terminal/Manager.ts"; import { OrchestrationCommandReceiptRepositoryLive } from "../src/persistence/Layers/OrchestrationCommandReceipts.ts"; import { OrchestrationEventStoreLive } from "../src/persistence/Layers/OrchestrationEventStore.ts"; import { ProjectionPendingApprovalRepositoryLive } from "../src/persistence/Layers/ProjectionPendingApprovals.ts"; @@ -340,6 +341,7 @@ export const makeOrchestrationIntegrationHarness = ( tryHandlePromptCommand: () => Effect.succeed(false), }), ), + Layer.provide(Layer.mock(TerminalManager.TerminalManager)({ closeIdle: () => Effect.void })), Layer.provideMerge(runtimeServicesLayer), Layer.provideMerge(gitWorkflowLayer), Layer.provideMerge(textGenerationLayer), diff --git a/apps/server/integration/orphanedProviderSessionStartup.integration.test.ts b/apps/server/integration/orphanedProviderSessionStartup.integration.test.ts index 77173af1b377..b483a3f99f8e 100644 --- a/apps/server/integration/orphanedProviderSessionStartup.integration.test.ts +++ b/apps/server/integration/orphanedProviderSessionStartup.integration.test.ts @@ -100,7 +100,7 @@ const startupDependencies = Layer.mergeAll( Layer.succeed(ServiceLauncherClient.ServiceLauncherClient, { managed: false, requestUpdate: () => Effect.die("unused"), - prepareTrial: Effect.sync(() => undefined), + prepareTrial: Effect.undefined, }), Layer.succeed( HttpServer.HttpServer, diff --git a/apps/server/package.json b/apps/server/package.json index b7f1bce88768..afa2ca18fdfc 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -27,6 +27,7 @@ "@effect/platform-node": "catalog:", "@effect/platform-node-shared": "catalog:", "@ff-labs/fff-node": "0.9.4", + "@napi-rs/keyring": "^1.3.0", "@opencode-ai/sdk": "^1.3.15", "diff": "8.0.3", "effect": "catalog:", diff --git a/apps/server/scripts/acp-mock-agent.ts b/apps/server/scripts/acp-mock-agent.ts index db1248a60201..ca25d625bc1d 100644 --- a/apps/server/scripts/acp-mock-agent.ts +++ b/apps/server/scripts/acp-mock-agent.ts @@ -19,6 +19,8 @@ const emitToolCalls = process.env.T3_ACP_EMIT_TOOL_CALLS === "1"; const emitInterleavedAssistantToolCalls = process.env.T3_ACP_EMIT_INTERLEAVED_ASSISTANT_TOOL_CALLS === "1"; const emitGenericToolPlaceholders = process.env.T3_ACP_EMIT_GENERIC_TOOL_PLACEHOLDERS === "1"; +const emitBackgroundToolDuringAnswer = + process.env.T3_ACP_EMIT_BACKGROUND_TOOL_DURING_ANSWER === "1"; const emitAskQuestion = process.env.T3_ACP_EMIT_ASK_QUESTION === "1"; const emitXAiAskUserQuestion = process.env.T3_ACP_EMIT_XAI_ASK_USER_QUESTION === "1"; const emitXAiExitPlanMode = process.env.T3_ACP_EMIT_XAI_EXIT_PLAN_MODE === "1"; @@ -966,6 +968,47 @@ const program = Effect.gen(function* () { return yield* Effect.never; } + if (emitBackgroundToolDuringAnswer) { + // A command backgrounded earlier reports progress and then finishes + // while the next answer is still streaming. + const toolCallId = "background-1"; + const say = (text: string) => + agent.client.sessionUpdate({ + sessionId: requestedSessionId, + update: { sessionUpdate: "agent_message_chunk", content: { type: "text", text } }, + }); + const progress = (status: "in_progress" | "completed", stdout: string) => + agent.client.sessionUpdate({ + sessionId: requestedSessionId, + update: { + sessionUpdate: "tool_call_update", + toolCallId, + status, + rawOutput: { stdout }, + }, + }); + yield* agent.client.sessionUpdate({ + sessionId: requestedSessionId, + update: { + sessionUpdate: "tool_call", + toolCallId, + title: "Terminal", + kind: "execute", + status: "in_progress", + rawInput: { command: "sleep 3 && echo done" }, + }, + }); + yield* say("| a | b |\n|---|---|\n| 1 "); + yield* progress("in_progress", "."); + yield* say("| x |\n"); + yield* progress("completed", "done"); + yield* say("| 2 | y |\n"); + // Agents can repeat a terminal update after the call finished. + yield* progress("completed", "done"); + yield* say("| 3 | z |"); + return { stopReason: "end_turn" }; + } + if (emitInterleavedAssistantToolCalls) { const toolCallId = "tool-call-1"; diff --git a/apps/server/src/assets/AssetAccess.ts b/apps/server/src/assets/AssetAccess.ts index e1f6633fbeca..8dbd1c755966 100644 --- a/apps/server/src/assets/AssetAccess.ts +++ b/apps/server/src/assets/AssetAccess.ts @@ -190,7 +190,7 @@ const optionOnNotFound = ( effect: Effect.Effect, ): Effect.Effect, PlatformError.PlatformError, R> => effect.pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ PlatformError: (error) => error.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(error), @@ -213,9 +213,9 @@ const resolveCanonicalWorkspaceFile = Effect.fn("AssetAccess.resolveCanonicalWor const fileSystem = yield* FileSystem.FileSystem; const workspacePaths = yield* WorkspacePaths.WorkspacePaths; const resolved = yield* workspacePaths.resolveRelativePathWithinRoot(input).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ - WorkspacePathOutsideRootError: () => Effect.succeed(Option.none()), + WorkspacePathOutsideRootError: () => Effect.succeedNone, }), ); if (Option.isNone(resolved)) return null; diff --git a/apps/server/src/assets/NativeAppIconResolver.ts b/apps/server/src/assets/NativeAppIconResolver.ts index 89a6d0636012..34299f95142f 100644 --- a/apps/server/src/assets/NativeAppIconResolver.ts +++ b/apps/server/src/assets/NativeAppIconResolver.ts @@ -43,10 +43,10 @@ function appFromCacheKey(key: string): ToolActivityNativeAppReference { const existingFile = Effect.fn("NativeAppIconResolver.existingFile")(function* (filePath: string) { const fileSystem = yield* FileSystem.FileSystem; const info = yield* fileSystem.stat(filePath).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ PlatformError: (error) => - error.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(error), + error.reason._tag === "NotFound" ? Effect.succeedNone : Effect.fail(error), }), ); return Option.isSome(info) && info.value.type === "File" ? filePath : null; diff --git a/apps/server/src/auth/PairingGrantStore.test.ts b/apps/server/src/auth/PairingGrantStore.test.ts index 9a093be41ca4..beb07627c66a 100644 --- a/apps/server/src/auth/PairingGrantStore.test.ts +++ b/apps/server/src/auth/PairingGrantStore.test.ts @@ -3,7 +3,6 @@ import { expect, it } from "@effect/vitest"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import * as Stream from "effect/Stream"; import * as Queue from "effect/Queue"; import * as TestClock from "effect/testing/TestClock"; @@ -47,10 +46,10 @@ const makePairingGrantStoreTestLayer = ( AuthPairingLinks.AuthPairingLinkRepository, AuthPairingLinks.AuthPairingLinkRepository.of({ create: () => Effect.void, - consumeAvailable: () => Effect.succeed(Option.none()), + consumeAvailable: () => Effect.succeedNone, listActive: () => Effect.succeed([]), revoke: () => Effect.succeed(false), - getByCredential: () => Effect.succeed(Option.none()), + getByCredential: () => Effect.succeedNone, ...overrides, }), ), diff --git a/apps/server/src/auth/ServerSecretStore.ts b/apps/server/src/auth/ServerSecretStore.ts index e936a1f85c99..c386f7e51d3c 100644 --- a/apps/server/src/auth/ServerSecretStore.ts +++ b/apps/server/src/auth/ServerSecretStore.ts @@ -174,7 +174,7 @@ export const make = Effect.gen(function* () { Effect.map((bytes) => Option.some(Uint8Array.from(bytes))), Effect.catch((cause) => cause.reason._tag === "NotFound" - ? Effect.succeed(Option.none()) + ? Effect.succeedNone : Effect.fail( new SecretStoreReadError({ resource: `secret ${name}`, diff --git a/apps/server/src/auth/dpop.ts b/apps/server/src/auth/dpop.ts index 43f90e440915..a841f4fd444c 100644 --- a/apps/server/src/auth/dpop.ts +++ b/apps/server/src/auth/dpop.ts @@ -115,7 +115,7 @@ export const verifyRequestDpopProof = (input: { "environment.dpop.failure_code": mapped.dpopFailureReason, }); } - return yield* Effect.fail(mapped); + return yield* mapped; }), ), ); diff --git a/apps/server/src/auth/http.test.ts b/apps/server/src/auth/http.test.ts index 3d53ee088376..793f86349650 100644 --- a/apps/server/src/auth/http.test.ts +++ b/apps/server/src/auth/http.test.ts @@ -5,7 +5,6 @@ import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; import * as Etag from "effect/unstable/http/Etag"; @@ -69,7 +68,7 @@ it.effect("sets the selected browser session cookies through the HTTP route", () Effect.gen(function* () { const crypto = yield* Crypto.Crypto; const unusedSecretStore = ServerSecretStore.ServerSecretStore.of({ - get: () => Effect.succeed(Option.none()), + get: () => Effect.succeedNone, set: () => Effect.void, create: () => Effect.void, getOrCreateRandom: () => Effect.die("Not used by these routes."), diff --git a/apps/server/src/bin.test.ts b/apps/server/src/bin.test.ts index fc8fe914d797..63744147c1d6 100644 --- a/apps/server/src/bin.test.ts +++ b/apps/server/src/bin.test.ts @@ -105,7 +105,6 @@ const makeCliTestServerConfig = (baseDir: string) => otlpTracesExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpMetricsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpLogsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, - otlpServiceName: "t3-server", otelEnvironment: OtelEnvironment.none, mode: "web", port: 0, diff --git a/apps/server/src/checkpointing/CheckpointDiffQuery.test.ts b/apps/server/src/checkpointing/CheckpointDiffQuery.test.ts index 7bfd313a2103..7fa6065109c3 100644 --- a/apps/server/src/checkpointing/CheckpointDiffQuery.test.ts +++ b/apps/server/src/checkpointing/CheckpointDiffQuery.test.ts @@ -89,10 +89,10 @@ describe("CheckpointDiffQuery.layer", () => { getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, getProjectShells: () => Effect.die("unused"), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeedNone, + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), getThreadCheckpointContext: () => Effect.sync(() => { @@ -113,9 +113,9 @@ describe("CheckpointDiffQuery.layer", () => { }), getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, + getThreadDetailById: () => Effect.succeedNone, + getThreadDetailSnapshot: () => Effect.succeedNone, searchThreads: () => Effect.succeed({ matches: [] }), }), ), @@ -206,18 +206,18 @@ describe("CheckpointDiffQuery.layer", () => { getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, getProjectShells: () => Effect.die("unused"), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeedNone, + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), + getThreadCheckpointContext: () => Effect.succeedSome(threadCheckpointContext), getFullThreadDiffContext: () => Effect.die("unused"), getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, + getThreadDetailById: () => Effect.succeedNone, + getThreadDetailSnapshot: () => Effect.succeedNone, searchThreads: () => Effect.succeed({ matches: [] }), }), ), @@ -298,18 +298,18 @@ describe("CheckpointDiffQuery.layer", () => { getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, getProjectShells: () => Effect.die("unused"), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeedNone, + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), + getThreadCheckpointContext: () => Effect.succeedSome(threadCheckpointContext), getFullThreadDiffContext: () => Effect.die("unused"), getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, + getThreadDetailById: () => Effect.succeedNone, + getThreadDetailSnapshot: () => Effect.succeedNone, searchThreads: () => Effect.succeed({ matches: [] }), }), ), @@ -375,18 +375,18 @@ describe("CheckpointDiffQuery.layer", () => { getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, getProjectShells: () => Effect.die("unused"), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeedNone, + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.some(threadCheckpointContext)), + getThreadCheckpointContext: () => Effect.succeedSome(threadCheckpointContext), getFullThreadDiffContext: () => Effect.die("unused"), getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, + getThreadDetailById: () => Effect.succeedNone, + getThreadDetailSnapshot: () => Effect.succeedNone, searchThreads: () => Effect.succeed({ matches: [] }), }), ), @@ -437,18 +437,18 @@ describe("CheckpointDiffQuery.layer", () => { getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, getProjectShells: () => Effect.die("unused"), - getProjectShellById: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeedNone, + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), - getFullThreadDiffContext: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeedNone, + getFullThreadDiffContext: () => Effect.succeedNone, getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, + getThreadDetailById: () => Effect.succeedNone, + getThreadDetailSnapshot: () => Effect.succeedNone, searchThreads: () => Effect.succeed({ matches: [] }), }), ), diff --git a/apps/server/src/cli/config.test.ts b/apps/server/src/cli/config.test.ts index c5e892974474..6da622ff01f8 100644 --- a/apps/server/src/cli/config.test.ts +++ b/apps/server/src/cli/config.test.ts @@ -20,6 +20,7 @@ import { import * as NetService from "@t3tools/shared/Net"; import * as OtelEnvironment from "@t3tools/shared/otelEnvironment"; import * as NodeServices from "@effect/platform-node/NodeServices"; +import * as ServerConfig from "../config.ts"; import { deriveServerPaths } from "../config.ts"; import { resolveServerConfig } from "./config.ts"; @@ -56,7 +57,6 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { otlpTracesExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpMetricsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpLogsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, - otlpServiceName: "t3-server", otelEnvironment: OtelEnvironment.none, devAllowedOrigins: [], } as const; @@ -640,7 +640,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { expect(resolved.otlpMetricsUrl).toBe("https://collector.example.com/v1/metrics"); expect(resolved.otlpLogsUrl).toBe("https://collector.example.com/v1/logs"); // The endpoint is the machine's to name. The service is not. - expect(resolved.otlpServiceName).toBe("t3-server"); + expect(ServerConfig.otlpResource(resolved).serviceName).toBe("t3code-server"); }), ); @@ -653,7 +653,7 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { OTEL_RESOURCE_ATTRIBUTES: "service.name=some-other-app", }); - expect(resolved.otlpServiceName).toBe("t3-server"); + expect(ServerConfig.otlpResource(resolved).serviceName).toBe("t3code-server"); }), ); @@ -668,13 +668,12 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { T3CODE_OTLP_TRACES_URL: "", T3CODE_OTLP_METRICS_URL: " ", T3CODE_OTLP_LOGS_URL: "", - T3CODE_OTLP_SERVICE_NAME: "", }); expect(resolved.otlpTracesUrl).toBe("https://collector.example.com/v1/traces"); expect(resolved.otlpMetricsUrl).toBe("https://collector.example.com/v1/metrics"); expect(resolved.otlpLogsUrl).toBe("https://collector.example.com/v1/logs"); - expect(resolved.otlpServiceName).toBe("t3-server"); + expect(ServerConfig.otlpResource(resolved).serviceName).toBe("t3code-server"); }), ); @@ -685,13 +684,12 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { OTEL_SERVICE_NAME: "t3", T3CODE_OTLP_TRACES_URL: "http://localhost:4318/v1/traces", T3CODE_OTLP_LOGS_URL: "http://localhost:4318/v1/logs", - T3CODE_OTLP_SERVICE_NAME: "t3-local", }); expect(resolved.otlpTracesUrl).toBe("http://localhost:4318/v1/traces"); expect(resolved.otlpMetricsUrl).toBe("https://collector.example.com/v1/metrics"); expect(resolved.otlpLogsUrl).toBe("http://localhost:4318/v1/logs"); - expect(resolved.otlpServiceName).toBe("t3-local"); + expect(ServerConfig.otlpResource(resolved).serviceName).toBe("t3code-server"); }), ); @@ -1464,4 +1462,145 @@ it.layer(NodeServices.layer)("cli config resolution", (it) => { expect(resolved.otlpLogsUrl).toBe("http://collector.internal:4318/v1/logs"); }), ); + + const minimalWebFlags = (baseDir: string) => ({ + mode: Option.some("web" as const), + port: Option.some(3773), + host: Option.none(), + baseDir: Option.some(baseDir), + cwd: Option.none(), + devUrl: Option.none(), + noBrowser: Option.none(), + bootstrapFd: Option.none(), + autoBootstrapProjectFromCwd: Option.none(), + logWebSocketEvents: Option.none(), + tailscaleServeEnabled: Option.none(), + tailscaleServePort: Option.none(), + }); + + it.effect( + "resolves each signal's endpoint through T3CODE_OTLP_*_URL, an OTEL endpoint, the bootstrap envelope, and persisted Settings, in that order", + () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const baseDir = yield* fs.makeTempDirectoryScoped({ + prefix: "t3-cli-config-otel-precedence-", + }); + const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined); + yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }); + yield* fs.writeFileString( + derivedPaths.settingsPath, + // @effect-diagnostics-next-line preferSchemaOverJson:off + `${JSON.stringify({ observability: { otlpLogsUrl: "http://settings:4318/v1/logs" } })}\n`, + ); + + const fd = yield* openBootstrapFd( + makeDesktopBootstrap({ + otlpMetricsUrl: "http://bootstrap:4318/v1/metrics", + // Blank, not an endpoint: it must not stand in front of Settings. + otlpLogsUrl: "", + }), + ); + + const resolved = yield* resolveServerConfig( + { + ...minimalWebFlags(baseDir), + mode: Option.some("desktop"), + port: Option.some(4888), + bootstrapFd: Option.some(fd), + }, + Option.none(), + ).pipe( + Effect.provide( + Layer.mergeAll( + ConfigProvider.layer( + ConfigProvider.fromEnv({ + env: { + T3CODE_OTLP_TRACES_URL: "http://t3:4318/v1/traces", + T3CODE_OTLP_HEADERS: "x-key=secret", + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "http://otel-traces:4318/custom", + OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "http://otel-metrics:4318/custom", + OTEL_EXPORTER_OTLP_HEADERS: "x-key=otel", + }, + }), + ), + NetService.layer, + ), + ), + ); + + // T3CODE_OTLP_TRACES_URL wins over the OTEL variable for the same + // signal, and keeps T3 Code's own headers since T3 Code still owns it. + expect(resolved.otlpTracesUrl).toBe("http://t3:4318/v1/traces"); + expect(resolved.otlpTracesExport.headers).toEqual({ "x-key": "secret" }); + // Metrics named no T3CODE_OTLP_METRICS_URL, so the OTEL endpoint wins + // over the bootstrap envelope and brings the OTEL headers and protocol. + expect(resolved.otlpMetricsUrl).toBe("http://otel-metrics:4318/custom"); + expect(resolved.otlpMetricsExport).toMatchObject({ + protocol: "http/protobuf", + headers: { "x-key": "otel" }, + }); + // Logs named no T3 or OTEL endpoint and a blank bootstrap value, so + // Settings answers, and logs keep the shared headers since no OTEL + // endpoint claimed them. + expect(resolved.otlpLogsUrl).toBe("http://settings:4318/v1/logs"); + expect(resolved.otlpLogsExport.headers).toEqual({ "x-key": "secret" }); + }), + ); + + it.effect("keeps an OTEL endpoint's claim on a signal when only its headers do not read", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const baseDir = yield* fs.makeTempDirectoryScoped({ + prefix: "t3-cli-config-otel-off-", + }); + const derivedPaths = yield* deriveExplicitServerPaths(baseDir, undefined); + yield* fs.makeDirectory(path.dirname(derivedPaths.settingsPath), { recursive: true }); + yield* fs.writeFileString( + derivedPaths.settingsPath, + // @effect-diagnostics-next-line preferSchemaOverJson:off + `${JSON.stringify({ observability: { otlpLogsUrl: "http://settings:4318/v1/logs" } })}\n`, + ); + + const fd = yield* openBootstrapFd( + makeDesktopBootstrap({ otlpMetricsUrl: "http://bootstrap:4318/v1/metrics" }), + ); + + const resolved = yield* resolveServerConfig( + { + ...minimalWebFlags(baseDir), + mode: Option.some("desktop"), + port: Option.some(4888), + bootstrapFd: Option.some(fd), + }, + Option.none(), + ).pipe( + Effect.provide( + Layer.mergeAll( + ConfigProvider.layer( + ConfigProvider.fromEnv({ + env: { + T3CODE_OTLP_TRACES_URL: "http://t3:4318/v1/traces", + OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel:4318", + OTEL_EXPORTER_OTLP_HEADERS: "x-key=%zz", + }, + }), + ), + NetService.layer, + ), + ), + ); + + // T3CODE_OTLP_TRACES_URL still wins outright. + expect(resolved.otlpTracesUrl).toBe("http://t3:4318/v1/traces"); + // A bad header list costs only the headers: the OTEL endpoint still + // claims metrics and logs over the bootstrap envelope and Settings. + expect(resolved.otlpMetricsUrl).toBe("http://otel:4318/v1/metrics"); + expect(resolved.otlpMetricsExport.headers).toBeUndefined(); + expect(resolved.otlpLogsUrl).toBe("http://otel:4318/v1/logs"); + expect(resolved.otlpLogsExport.headers).toBeUndefined(); + }), + ); }); diff --git a/apps/server/src/cli/config.ts b/apps/server/src/cli/config.ts index 146ec2c1aeff..95eb24d76b9f 100644 --- a/apps/server/src/cli/config.ts +++ b/apps/server/src/cli/config.ts @@ -105,10 +105,6 @@ const EnvServerConfig = Config.all({ Config.option, Config.map(Option.getOrUndefined), ), - otlpServiceName: Config.String("T3CODE_OTLP_SERVICE_NAME").pipe( - Config.option, - Config.map(Option.getOrUndefined), - ), otlpHeaders: Config.schema(OtlpHeadersFromString, "T3CODE_OTLP_HEADERS").pipe( Config.option, Config.map(Option.getOrUndefined), @@ -232,15 +228,6 @@ const resolveOptionPrecedence = ( ...values: ReadonlyArray> ): Option.Option => Option.firstSomeOf(values); -/** - * A set but blank `T3CODE_OTLP_SERVICE_NAME` is not a name. Taking one as an - * answer would file every span under the empty string. - */ -const named = (value: string | undefined) => { - const trimmed = value?.trim(); - return trimmed === undefined || trimmed === "" ? undefined : trimmed; -}; - const loadPersistedObservabilitySettings = Effect.fn(function* (settingsPath: string) { const fs = yield* FileSystem.FileSystem; const exists = yield* fs.exists(settingsPath).pipe(Effect.orElseSucceed(() => false)); @@ -463,7 +450,6 @@ export const resolveServerConfig = ( otlpTracesExport: signalExport(otelEnvironment.traces.settings), otlpMetricsExport: signalExport(otelEnvironment.metrics.settings), otlpLogsExport: signalExport(otelEnvironment.logs.settings), - otlpServiceName: named(env.otlpServiceName) ?? "t3-server", otelEnvironment, mode, port, diff --git a/apps/server/src/cli/pair.ts b/apps/server/src/cli/pair.ts index f64150f7425f..a743672572da 100644 --- a/apps/server/src/cli/pair.ts +++ b/apps/server/src/cli/pair.ts @@ -325,7 +325,6 @@ const makePairServerConfig = Effect.fn(function* (input: { otlpTracesExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpMetricsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpLogsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, - otlpServiceName: "t3-server", otelEnvironment: OtelEnvironment.none, mode: "web", port: state.port, diff --git a/apps/server/src/cli/theme.ts b/apps/server/src/cli/theme.ts index 26a0c337fb27..ab662596267a 100644 --- a/apps/server/src/cli/theme.ts +++ b/apps/server/src/cli/theme.ts @@ -260,12 +260,10 @@ const writeDefaultTheme = Effect.fn(function* (input: { // Falling through here would overwrite whatever landed in between, which // is exactly the loss this loop exists to prevent. if (attempt >= CONCURRENT_WRITE_ATTEMPTS) { - return yield* Effect.fail( - new ThemeSettingsBusyError({ - settingsPath: input.settingsPath, - attempts: CONCURRENT_WRITE_ATTEMPTS, - }), - ); + return yield* new ThemeSettingsBusyError({ + settingsPath: input.settingsPath, + attempts: CONCURRENT_WRITE_ATTEMPTS, + }); } continue; } @@ -299,12 +297,13 @@ const publishThemeFile = Effect.fn(function* (input: { Effect.mapError((cause) => new ThemeFileUnreadableError({ filePath: input.filePath, cause })), ); if (info.type !== "File") { - return yield* Effect.fail(new ThemeFileUnreadableError({ filePath: input.filePath })); + return yield* new ThemeFileUnreadableError({ filePath: input.filePath }); } if (Number(info.size) > MAX_THEME_FILE_BYTES) { - return yield* Effect.fail( - new ThemeFileTooLargeError({ filePath: input.filePath, limit: MAX_THEME_FILE_BYTES }), - ); + return yield* new ThemeFileTooLargeError({ + filePath: input.filePath, + limit: MAX_THEME_FILE_BYTES, + }); } // An explicit source path is the user's own input, and a symlink there is a @@ -320,17 +319,15 @@ const publishThemeFile = Effect.fn(function* (input: { ); const raw = readThemeFileGuarded(resolvedSource, MAX_THEME_FILE_BYTES); if (raw === null) { - return yield* Effect.fail(new ThemeFileUnreadableError({ filePath: input.filePath })); + return yield* new ThemeFileUnreadableError({ filePath: input.filePath }); } const decoded = decodeThemeFileJsonExit(raw); if (decoded._tag === "Failure") { - return yield* Effect.fail( - new ThemeFileInvalidError({ filePath: input.filePath, cause: decoded.cause }), - ); + return yield* new ThemeFileInvalidError({ filePath: input.filePath, cause: decoded.cause }); } if (!environmentThemeFileHasColors(decoded.value)) { - return yield* Effect.fail(new ThemeFileColorlessError({ filePath: input.filePath })); + return yield* new ThemeFileColorlessError({ filePath: input.filePath }); } const fileBasename = path.basename(input.filePath, ".json"); @@ -338,7 +335,7 @@ const publishThemeFile = Effect.fn(function* (input: { // The same rules the watcher applies when it reads the directory back, so a // publish cannot report success for a file that will then be skipped. if (!isEnvironmentThemeId(themeId) || UNPUBLISHABLE_THEME_IDS.has(themeId)) { - return yield* Effect.fail(new ThemeFileIdInvalidError({ themeId, filePath: input.filePath })); + return yield* new ThemeFileIdInvalidError({ themeId, filePath: input.filePath }); } const destinationPath = path.join(input.themesDir, `${themeId}.json`); @@ -479,7 +476,7 @@ const themeSetCommand = Command.make("set", { const fs = yield* FileSystem.FileSystem; const target = yield* expandHomePath(flags.theme.trim()); if (target.length === 0) { - return yield* Effect.fail(new ThemeTargetMissingError()); + return yield* new ThemeTargetMissingError(); } const paths = yield* resolveThemePaths(flags.baseDir); @@ -514,15 +511,15 @@ const themeSetCommand = Command.make("set", { revertPublish = published.revert; cleanupPublish = published.cleanup; } else if (looksLikePath) { - return yield* Effect.fail(new ThemeFileUnreadableError({ filePath: target })); + return yield* new ThemeFileUnreadableError({ filePath: target }); } else if (isEnvironmentThemeId(target)) { const known = yield* resolvableThemeIds(paths.themesDir); if (!known.includes(target)) { - return yield* Effect.fail(new ThemeIdUnknownError({ themeId: target, known })); + return yield* new ThemeIdUnknownError({ themeId: target, known }); } themeId = target; } else { - return yield* Effect.fail(new ThemeIdInvalidError({ themeId: target })); + return yield* new ThemeIdInvalidError({ themeId: target }); } // set means set: if the default cannot be written, the publish that diff --git a/apps/server/src/cloud/CliState.test.ts b/apps/server/src/cloud/CliState.test.ts index 39f904b47b89..d59b89875e79 100644 --- a/apps/server/src/cloud/CliState.test.ts +++ b/apps/server/src/cloud/CliState.test.ts @@ -8,6 +8,7 @@ import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; import { ServerConfig } from "../config.ts"; import * as CliState from "./CliState.ts"; import { + CLOUD_ENDPOINT_CONFIRMED_ORIGIN, CLOUD_ENDPOINT_RUNTIME_CONFIG, CLOUD_LINKED_USER_ID, CLOUD_MINT_PUBLIC_KEY, @@ -24,6 +25,7 @@ const persistedCloudLinkSecrets = [ RELAY_ENVIRONMENT_CREDENTIAL_SECRET, CLOUD_MINT_PUBLIC_KEY, CLOUD_ENDPOINT_RUNTIME_CONFIG, + CLOUD_ENDPOINT_CONFIRMED_ORIGIN, PUBLISH_AGENT_ACTIVITY_SECRET, ] as const; diff --git a/apps/server/src/cloud/CliState.ts b/apps/server/src/cloud/CliState.ts index 9af9a032f856..dc77609ccc92 100644 --- a/apps/server/src/cloud/CliState.ts +++ b/apps/server/src/cloud/CliState.ts @@ -3,6 +3,7 @@ import * as Option from "effect/Option"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; import { + CLOUD_ENDPOINT_CONFIRMED_ORIGIN, CLOUD_ENDPOINT_RUNTIME_CONFIG, CLOUD_LINKED_USER_ID, CLOUD_MINT_PUBLIC_KEY, @@ -67,6 +68,7 @@ export const clearPersistedCloudLink = Effect.gen(function* () { secrets.remove(RELAY_ENVIRONMENT_CREDENTIAL_SECRET), secrets.remove(CLOUD_MINT_PUBLIC_KEY), secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG), + secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN), secrets.remove(PUBLISH_AGENT_ACTIVITY_SECRET), ], { concurrency: "unbounded" }, diff --git a/apps/server/src/cloud/CliTokenManager.ts b/apps/server/src/cloud/CliTokenManager.ts index 4172578d45f3..2a666651a9a5 100644 --- a/apps/server/src/cloud/CliTokenManager.ts +++ b/apps/server/src/cloud/CliTokenManager.ts @@ -341,7 +341,7 @@ const pollDeviceToken = Effect.fn("cloud.cli_token.poll_device_token")(function* const response = yield* HttpClientRequest.post(metadata.tokenEndpoint).pipe( HttpClientRequest.bodyUrlParams(params), httpClient.execute, - Effect.map(Option.some), + Effect.asSome, Effect.catchIf(isTransportError, () => Effect.succeedNone), ); // Transport failures and upstream 5xx are transient while the device code diff --git a/apps/server/src/cloud/ManagedEndpointRuntime.test.ts b/apps/server/src/cloud/ManagedEndpointRuntime.test.ts index ba2cf5c5ac05..71677a43d6b8 100644 --- a/apps/server/src/cloud/ManagedEndpointRuntime.test.ts +++ b/apps/server/src/cloud/ManagedEndpointRuntime.test.ts @@ -7,10 +7,12 @@ import * as Fiber from "effect/Fiber"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as PlatformError from "effect/PlatformError"; +import * as Queue from "effect/Queue"; import * as Sink from "effect/Sink"; import * as Stream from "effect/Stream"; import * as TestClock from "effect/testing/TestClock"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; +import type { RelayManagedEndpointRuntimeConfig } from "@t3tools/contracts/relay"; import * as RelayClient from "@t3tools/shared/relayClient"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; @@ -38,7 +40,7 @@ const runtimeDependencies = ( Layer.succeed(ChildProcessSpawner.ChildProcessSpawner, spawner), relayClientLayer, Layer.mock(ServerSecretStore.ServerSecretStore)({ - get: () => Effect.succeed(Option.none()), + get: () => Effect.succeedNone, }), ); @@ -62,6 +64,7 @@ function makeHandle(input: { readonly onKill: () => void; readonly isRunning?: () => boolean; readonly exitCode?: Effect.Effect; + readonly output?: Stream.Stream; }) { return ChildProcessSpawner.makeHandle({ pid: ChildProcessSpawner.ProcessId(input.pid), @@ -75,13 +78,70 @@ function makeHandle(input: { stdin: Sink.drain, stdout: Stream.empty, stderr: Stream.empty, - all: Stream.empty, + all: input.output ?? Stream.empty, getInputFd: () => Sink.drain, getOutputFd: () => Stream.empty, }); } describe("CloudManagedEndpointRuntime", () => { + it("retries connector startup failures but stops for unsupported runtimes", () => { + expect( + ManagedEndpointRuntime.isRetryableManagedEndpointRuntimeStatus({ + status: "failed", + failure: "not-installed", + reason: "The relay client is not installed.", + }), + ).toBe(true); + expect( + ManagedEndpointRuntime.isRetryableManagedEndpointRuntimeStatus({ + status: "failed", + failure: "spawn-failed", + reason: "spawn failed", + }), + ).toBe(true); + expect( + ManagedEndpointRuntime.isRetryableManagedEndpointRuntimeStatus({ + status: "failed", + failure: "unsupported-platform", + reason: "Relay client is unsupported on linux-arm.", + }), + ).toBe(false); + expect( + ManagedEndpointRuntime.isRetryableManagedEndpointRuntimeStatus({ status: "unsupported" }), + ).toBe(false); + }); + + it.effect("serializes updates to persisted cloud link state", () => + Effect.gen(function* () { + const firstEntered = yield* Deferred.make(); + const releaseFirst = yield* Deferred.make(); + const secondEntered = yield* Deferred.make(); + const runtime = yield* buildCloudManagedEndpointRuntime( + ChildProcessSpawner.make(() => Effect.die("unused")), + ); + + const first = yield* runtime + .withLinkStateLock( + Deferred.succeed(firstEntered, undefined).pipe( + Effect.andThen(Deferred.await(releaseFirst)), + ), + ) + .pipe(Effect.forkChild); + yield* Deferred.await(firstEntered); + + const second = yield* runtime + .withLinkStateLock(Deferred.succeed(secondEntered, undefined)) + .pipe(Effect.forkChild); + expect(yield* Deferred.isDone(secondEntered)).toBe(false); + + yield* Deferred.succeed(releaseFirst, undefined); + yield* Fiber.join(first); + yield* Fiber.join(second); + expect(yield* Deferred.isDone(secondEntered)).toBe(true); + }), + ); + it("classifies Cloudflare connection and warning output", () => { expect( ManagedEndpointRuntime.classifyRelayClientOutput( @@ -109,6 +169,125 @@ describe("CloudManagedEndpointRuntime", () => { ).toBe("warning"); }); + it("recognizes tunnel authorization failures without matching ordinary transport errors", () => { + expect( + ManagedEndpointRuntime.isRejectedRelayClientTunnelOutput( + '2026-09-15T06:30:43Z ERR Register tunnel error from server side error="Failed to get tunnel" connIndex=0 event=0 ip=198.41.200.23', + ), + ).toBe(true); + expect( + ManagedEndpointRuntime.isRejectedRelayClientTunnelOutput( + '2026-06-17T02:00:00Z ERR Register tunnel error from server side error="Unauthorized: Record for tunnel not found" connIndex=0', + ), + ).toBe(true); + expect( + ManagedEndpointRuntime.isRejectedRelayClientTunnelOutput( + '2026-06-17T02:00:00Z ERR Register tunnel error from server side error="Unauthorized: Invalid tunnel secret" connIndex=0', + ), + ).toBe(true); + expect( + ManagedEndpointRuntime.isRejectedRelayClientTunnelOutput( + '2026-06-17T02:00:00Z ERR Register tunnel error from server side error="connection timed out" connIndex=0', + ), + ).toBe(false); + }); + + it.effect("keeps recovery requests sent before the server starts consuming them", () => + Effect.gen(function* () { + const runtime = yield* buildCloudManagedEndpointRuntime( + ChildProcessSpawner.make(() => Effect.die("unused")), + ); + const config = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "token", + tunnelId: "tunnel-1", + }; + + yield* runtime.requestRecovery(config); + + expect(Option.getOrNull(yield* Stream.runHead(runtime.recoveryRequests))).toEqual(config); + }), + ); + + it.effect("recovers a rejected tunnel without waiting for the connector to exit", () => + Effect.gen(function* () { + const output = yield* Queue.unbounded(); + const firstBatchObserved = yield* Deferred.make(); + const secondBatchObserved = yield* Deferred.make(); + const recoveryRequested = yield* Deferred.make(); + const recoveryRetried = yield* Deferred.make(); + let recoveryRequestCount = 0; + const spawned: Array = []; + const encoder = new TextEncoder(); + const connectorOutput = Stream.fromQueue(output).pipe( + Stream.tap((chunk) => { + const line = new TextDecoder().decode(chunk); + if (line === "first checkpoint\n") { + return Deferred.succeed(firstBatchObserved, undefined).pipe(Effect.asVoid); + } + if (line === "second checkpoint\n") { + return Deferred.succeed(secondBatchObserved, undefined).pipe(Effect.asVoid); + } + return Effect.void; + }), + ); + const spawner = ChildProcessSpawner.make(() => + Effect.gen(function* () { + const pid = 600; + spawned.push(pid); + const handle = makeHandle({ pid, onKill: () => {}, output: connectorOutput }); + yield* Effect.addFinalizer(() => handle.kill().pipe(Effect.ignore)); + return handle; + }), + ); + const runtime = yield* buildCloudManagedEndpointRuntime(spawner); + const config = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "token", + tunnelId: "deleted-tunnel", + }; + const rejectedLine = + '2026-09-15T06:30:43Z ERR Register tunnel error from server side error="Failed to get tunnel" connIndex=0 event=0 ip=198.41.200.23\n'; + + yield* runtime.recoveryRequests.pipe( + Stream.runForEach((requested) => { + recoveryRequestCount += 1; + return Deferred.succeed( + recoveryRequestCount === 1 ? recoveryRequested : recoveryRetried, + requested, + ).pipe(Effect.asVoid); + }), + Effect.forkChild, + ); + yield* runtime.applyConfig(config); + + yield* Queue.offer(output, encoder.encode(rejectedLine.repeat(3))); + yield* Queue.offer(output, encoder.encode("first checkpoint\n")); + yield* Deferred.await(firstBatchObserved); + expect(yield* Deferred.isDone(recoveryRequested)).toBe(false); + + yield* Queue.offer( + output, + encoder.encode( + "2026-06-17T02:00:00Z INF Registered tunnel connection connIndex=0\n" + + rejectedLine.repeat(3), + ), + ); + yield* Queue.offer(output, encoder.encode("second checkpoint\n")); + yield* Deferred.await(secondBatchObserved); + expect(yield* Deferred.isDone(recoveryRequested)).toBe(false); + + yield* Queue.offer(output, encoder.encode(rejectedLine)); + + expect(yield* Deferred.await(recoveryRequested)).toEqual(config); + + yield* Queue.offer(output, encoder.encode(rejectedLine.repeat(4))); + + expect(yield* Deferred.await(recoveryRetried)).toEqual(config); + expect(spawned).toEqual([600]); + }), + ); + it.effect("starts, deduplicates, rotates, and stops the Cloudflare connector", () => Effect.gen(function* () { const spawned: Array = []; @@ -156,8 +335,8 @@ describe("CloudManagedEndpointRuntime", () => { expect(spawned.map((command) => command.command)).toEqual(["cloudflared", "cloudflared"]); expect(spawned.map((command) => command.args)).toEqual([ - ["tunnel", "run"], - ["tunnel", "run"], + ["tunnel", "--no-autoupdate", "--loglevel", "info", "--output", "default", "run"], + ["tunnel", "--no-autoupdate", "--loglevel", "info", "--output", "default", "run"], ]); expect(spawned.map((command) => command.options.env?.TUNNEL_TOKEN)).toEqual([ "token-1", @@ -378,6 +557,37 @@ describe("CloudManagedEndpointRuntime", () => { }).pipe(Effect.provide(TestClock.layer())), ); + it.effect("a recovery that returns the same config keeps the crash backoff", () => + Effect.gen(function* () { + const { spawner, spawned, exits, spawnSignals } = yield* makeCrashLoopSpawner(900, 4); + const runtime = yield* buildCloudManagedEndpointRuntime(spawner); + const config = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "same-token", + tunnelId: "same-tunnel", + }; + // The startup consumer re-applies whatever the relay hands back. When the + // relay confirms the current tunnel, that must not look like a config change. + yield* runtime.recoveryRequests.pipe( + Stream.runForEach((requested) => runtime.applyConfig(requested).pipe(Effect.asVoid)), + Effect.forkChild, + ); + + yield* runtime.applyConfig(config); + yield* Deferred.succeed(exits[0]!, ChildProcessSpawner.ExitCode(1)); + yield* Deferred.await(spawnSignals[1]!); + expect(spawned).toEqual([900, 901]); + + // Second rapid crash still waits out the base delay. + yield* Deferred.succeed(exits[1]!, ChildProcessSpawner.ExitCode(1)); + yield* TestClock.adjust(Duration.millis(999)); + expect(spawned).toEqual([900, 901]); + yield* TestClock.adjust(Duration.millis(1)); + yield* Deferred.await(spawnSignals[2]!); + expect(spawned).toEqual([900, 901, 902]); + }).pipe(Effect.provide(TestClock.layer())), + ); + it.effect("an explicit config change clears the backoff and preempts a delayed restart", () => Effect.gen(function* () { const { spawner, spawned, exits, spawnSignals } = yield* makeCrashLoopSpawner(800, 3); @@ -510,6 +720,7 @@ describe("CloudManagedEndpointRuntime", () => { expect(status).toEqual({ status: "failed", providerKind: "cloudflare_tunnel", + failure: "not-installed", reason: "The relay client is not installed.", }); expect(spawn).not.toHaveBeenCalled(); diff --git a/apps/server/src/cloud/ManagedEndpointRuntime.ts b/apps/server/src/cloud/ManagedEndpointRuntime.ts index cc657bdebf1b..21091c5c446e 100644 --- a/apps/server/src/cloud/ManagedEndpointRuntime.ts +++ b/apps/server/src/cloud/ManagedEndpointRuntime.ts @@ -6,7 +6,7 @@ import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; +import * as Queue from "effect/Queue"; import * as Ref from "effect/Ref"; import * as Result from "effect/Result"; import * as Semaphore from "effect/Semaphore"; @@ -15,22 +15,6 @@ import * as Stream from "effect/Stream"; import * as ChildProcess from "effect/unstable/process/ChildProcess"; import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; -import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; -import { CLOUD_ENDPOINT_RUNTIME_CONFIG, decodeRuntimeConfig } from "./config.ts"; - -function bytesToString(bytes: Uint8Array): string { - return new TextDecoder().decode(bytes); -} - -const readRuntimeConfig = Effect.gen(function* () { - const secrets = yield* ServerSecretStore.ServerSecretStore; - const bytes = yield* secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG); - if (Option.isNone(bytes)) { - return null; - } - return Option.getOrNull(decodeRuntimeConfig(bytesToString(bytes.value))); -}); - export type CloudManagedEndpointRuntimeStatus = | { readonly status: "disabled"; @@ -38,6 +22,7 @@ export type CloudManagedEndpointRuntimeStatus = | { readonly status: "failed"; readonly providerKind: RelayManagedEndpointRuntimeConfig["providerKind"]; + readonly failure: "unsupported-platform" | "not-installed" | "spawn-failed"; readonly reason: string; readonly tunnelId?: string; readonly tunnelName?: string; @@ -60,6 +45,9 @@ export class CloudManagedEndpointRuntime extends Context.Service< readonly applyConfig: ( config: RelayManagedEndpointRuntimeConfig | null, ) => Effect.Effect; + readonly recoveryRequests: Stream.Stream; + readonly requestRecovery: (config: RelayManagedEndpointRuntimeConfig) => Effect.Effect; + readonly withLinkStateLock: (effect: Effect.Effect) => Effect.Effect; } >()("t3/cloud/ManagedEndpointRuntime/CloudManagedEndpointRuntime") {} @@ -79,6 +67,8 @@ interface ActiveConnector { const RELAY_RESTART_STABLE_UPTIME_MS = 30_000; const RELAY_RESTART_BACKOFF_BASE_MS = 1_000; const RELAY_RESTART_BACKOFF_MAX_MS = 60_000; +// Newly created tunnels can fail authorization briefly while Cloudflare propagates their token. +const TUNNEL_AUTHORIZATION_FAILURES_BEFORE_RECOVERY = 4; export function classifyRelayClientOutput(line: string): "connected" | "warning" | "debug" { if (/\bRegistered tunnel connection\b/iu.test(line)) { @@ -90,6 +80,32 @@ export function classifyRelayClientOutput(line: string): "connected" | "warning" return /\b(?:ERR|WRN|FTL|PNC)\b/u.test(line) ? "warning" : "debug"; } +/** + * Cloudflare's edge rejects a connector whose tunnel was deleted or whose + * token no longer matches. Current edge output is + * `error="Failed to get tunnel"` with no prefix; older edges prefixed the + * same messages with `Unauthorized:`. Match both so recovery fires on either. + */ +export function isRejectedRelayClientTunnelOutput(line: string): boolean { + return ( + /\bRegister tunnel error from server side\b/iu.test(line) && + /error="(?:Unauthorized:\s*)?(?:Failed to get tunnel|Record for tunnel not found|Invalid tunnel secret)"/iu.test( + line, + ) + ); +} + +/** Connector startup failures can clear after installation or a later spawn attempt. */ +export function isRetryableManagedEndpointRuntimeStatus(status: unknown): boolean { + if (typeof status !== "object" || status === null || !("status" in status)) { + return false; + } + if (status.status !== "failed" || !("failure" in status)) { + return false; + } + return status.failure === "not-installed" || status.failure === "spawn-failed"; +} + function runtimeConfigKey(config: RelayManagedEndpointRuntimeConfig): string { return JSON.stringify({ providerKind: config.providerKind, @@ -117,8 +133,10 @@ export const make = Effect.gen(function* () { const relayClient = yield* RelayClient.RelayClient; const activeRef = yield* Ref.make(null); const desiredConfigRef = yield* Ref.make(null); + const recoveryRequests = yield* Queue.sliding(1); const reconcileSemaphore = yield* Semaphore.make(1); const restartDelayRef = yield* Ref.make(0); + const linkStateSemaphore = yield* Semaphore.make(1); let reconcileConfig: CloudManagedEndpointRuntime["Service"]["applyConfig"]; const stopActive = Effect.gen(function* () { @@ -191,6 +209,7 @@ export const make = Effect.gen(function* () { tunnelId: connector.config.tunnelId, tunnelName: connector.config.tunnelName, }); + yield* Queue.offer(recoveryRequests, connector.config); yield* reconcileConfig(desiredConfig); }), ); @@ -198,8 +217,10 @@ export const make = Effect.gen(function* () { Effect.catchCause((cause) => Effect.logWarning("Relay client supervisor failed", { cause })), ); - const observeConnectorOutput = (connector: ActiveConnector) => - connector.child.all.pipe( + const observeConnectorOutput = (connector: ActiveConnector) => { + let rejectedRegistrations = 0; + + return connector.child.all.pipe( Stream.decodeText(), Stream.splitLines, Stream.map((line) => line.trim()), @@ -214,8 +235,22 @@ export const make = Effect.gen(function* () { }; switch (classifyRelayClientOutput(line)) { case "connected": + rejectedRegistrations = 0; return Effect.logInfo("Relay client tunnel connection registered", attributes); case "warning": + if (isRejectedRelayClientTunnelOutput(line)) { + rejectedRegistrations += 1; + if (rejectedRegistrations >= TUNNEL_AUTHORIZATION_FAILURES_BEFORE_RECOVERY) { + rejectedRegistrations = 0; + return Effect.logWarning( + "Relay client tunnel was rejected; requesting recovery", + attributes, + ).pipe( + Effect.andThen(Queue.offer(recoveryRequests, connector.config)), + Effect.asVoid, + ); + } + } return Effect.logWarning("Relay client reported a transport warning", attributes); case "debug": return Effect.logDebug("Relay client output", attributes); @@ -230,6 +265,7 @@ export const make = Effect.gen(function* () { }), ), ); + }; reconcileConfig = Effect.fn("CloudManagedEndpointRuntime.reconcileConfig")(function* (config) { if (!config || config.providerKind !== "cloudflare_tunnel") { @@ -261,6 +297,7 @@ export const make = Effect.gen(function* () { return { status: "failed", providerKind: "cloudflare_tunnel", + failure: executable.status === "unsupported" ? "unsupported-platform" : "not-installed", reason: executable.status === "unsupported" ? `Relay client is unsupported on ${executable.platform}-${executable.arch}.` @@ -273,16 +310,20 @@ export const make = Effect.gen(function* () { const connectorScope = yield* Scope.make("sequential"); const child = yield* spawner .spawn( - ChildProcess.make(executable.executablePath, ["tunnel", "run"], { - detached: false, - env: { - ...process.env, - TUNNEL_TOKEN: config.connectorToken, + ChildProcess.make( + executable.executablePath, + ["tunnel", "--no-autoupdate", "--loglevel", "info", "--output", "default", "run"], + { + detached: false, + env: { + ...process.env, + TUNNEL_TOKEN: config.connectorToken, + }, + shell: false, + stderr: "pipe", + stdout: "pipe", }, - shell: false, - stderr: "pipe", - stdout: "pipe", - }), + ), ) .pipe( Effect.provideService(Scope.Scope, connectorScope), @@ -303,6 +344,7 @@ export const make = Effect.gen(function* () { Effect.as({ status: "failed", providerKind: "cloudflare_tunnel", + failure: "spawn-failed", reason: String(cause), ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}), ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}), @@ -338,6 +380,7 @@ export const make = Effect.gen(function* () { return { status: "failed", providerKind: "cloudflare_tunnel", + failure: "spawn-failed", reason: "Relay client did not start.", ...(config.tunnelId ? { tunnelId: config.tunnelId } : {}), ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}), @@ -347,26 +390,31 @@ export const make = Effect.gen(function* () { const applyConfig = Effect.fn("CloudManagedEndpointRuntime.applyConfig")( (config: RelayManagedEndpointRuntimeConfig | null) => reconcileSemaphore.withPermits(1)( - // An explicit config change starts over with a fresh backoff. - Ref.set(restartDelayRef, 0).pipe( - Effect.andThen(Ref.set(desiredConfigRef, config)), - Effect.andThen(reconcileConfig(config)), - ), + Effect.gen(function* () { + // A real config change starts over with a fresh backoff. Recovery + // that hands back the same tunnel and token must keep the delay, or + // a crash-looping connector respawns on every recovery round trip. + const desired = yield* Ref.get(desiredConfigRef); + const unchanged = + desired !== null && + config !== null && + runtimeConfigKey(desired) === runtimeConfigKey(config); + if (!unchanged) { + yield* Ref.set(restartDelayRef, 0); + } + yield* Ref.set(desiredConfigRef, config); + return yield* reconcileConfig(config); + }), ), ); const runtime = CloudManagedEndpointRuntime.of({ applyConfig, + recoveryRequests: Stream.fromQueue(recoveryRequests), + requestRecovery: (config) => Queue.offer(recoveryRequests, config).pipe(Effect.asVoid), + withLinkStateLock: linkStateSemaphore.withPermits(1), }); - const initialConfig = yield* readRuntimeConfig.pipe( - Effect.catch((cause) => - Effect.logWarning("Failed to read managed endpoint runtime config", { cause }).pipe( - Effect.as(null), - ), - ), - ); - yield* runtime.applyConfig(initialConfig); yield* Effect.addFinalizer(() => runtime.applyConfig(null)); return runtime; }); diff --git a/apps/server/src/cloud/config.ts b/apps/server/src/cloud/config.ts index 2eff693f61e6..9b1b281ba2da 100644 --- a/apps/server/src/cloud/config.ts +++ b/apps/server/src/cloud/config.ts @@ -1,4 +1,7 @@ -import { RelayManagedEndpointRuntimeConfig } from "@t3tools/contracts/relay"; +import { + RelayManagedEndpointOrigin, + RelayManagedEndpointRuntimeConfig, +} from "@t3tools/contracts/relay"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; @@ -7,6 +10,7 @@ import type * as ServerSecretStore from "../auth/ServerSecretStore.ts"; export const CLOUD_MINT_PUBLIC_KEY = "cloud-mint-ed25519-public-key"; export const CLOUD_ENDPOINT_RUNTIME_CONFIG = "cloud-endpoint-runtime-config"; +export const CLOUD_ENDPOINT_CONFIRMED_ORIGIN = "cloud-endpoint-confirmed-origin"; export const CLOUD_LINKED_USER_ID = "cloud-linked-user-id"; export const RELAY_URL_SECRET = "cloud-relay-url"; export const RELAY_ISSUER_SECRET = "cloud-relay-issuer"; @@ -21,6 +25,19 @@ export const decodeRuntimeConfig = Schema.decodeUnknownOption( Schema.fromJsonString(RelayManagedEndpointRuntimeConfig), ); +export const ManagedEndpointConfirmedOrigin = Schema.Struct({ + config: RelayManagedEndpointRuntimeConfig, + origin: RelayManagedEndpointOrigin, +}); + +export const encodeConfirmedOriginJson = Schema.encodeEffect( + Schema.fromJsonString(ManagedEndpointConfirmedOrigin), +); + +export const decodeConfirmedOrigin = Schema.decodeUnknownOption( + Schema.fromJsonString(ManagedEndpointConfirmedOrigin), +); + export function isAgentActivityPublishingEnabledValue(value: string | null): boolean { return value === "true"; } diff --git a/apps/server/src/cloud/http.test.ts b/apps/server/src/cloud/http.test.ts index 0f24e6f34176..64a512a33aba 100644 --- a/apps/server/src/cloud/http.test.ts +++ b/apps/server/src/cloud/http.test.ts @@ -1,12 +1,18 @@ import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, expect, it } from "@effect/vitest"; +import * as Deferred from "effect/Deferred"; +import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as PlatformError from "effect/PlatformError"; +import * as Schema from "effect/Schema"; +import * as TestClock from "effect/testing/TestClock"; import * as Tracer from "effect/Tracer"; +import * as Stream from "effect/Stream"; import { HttpClient, HttpClientResponse, @@ -14,7 +20,7 @@ import { type HttpClientRequest, } from "effect/unstable/http"; -import { EnvironmentId } from "@t3tools/contracts"; +import { DESKTOP_UPDATE_RESTART_MARKER_FILE, EnvironmentId } from "@t3tools/contracts"; import { RelayClientTracer } from "@t3tools/shared/relayTracing"; import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; @@ -29,16 +35,37 @@ import { import * as ServerEnvironment from "../environment/ServerEnvironment.ts"; import { CLOUD_CLI_DESIRED_LINK_SECRET } from "./CliState.ts"; import * as CliTokenManager from "./CliTokenManager.ts"; -import type { RelayLinkProofRequest } from "@t3tools/contracts/relay"; -import { CLOUD_ENDPOINT_RUNTIME_CONFIG, RELAY_URL_SECRET } from "./config.ts"; +import { + RelayManagedEndpointRecoveryRegistrationRequest, + type RelayLinkProofRequest, +} from "@t3tools/contracts/relay"; +import { + CLOUD_ENDPOINT_CONFIRMED_ORIGIN, + CLOUD_ENDPOINT_RUNTIME_CONFIG, + CLOUD_LINKED_USER_ID, + decodeConfirmedOrigin, + decodeRuntimeConfig, + RELAY_ENVIRONMENT_CREDENTIAL_SECRET, + RELAY_URL_SECRET, +} from "./config.ts"; import { consumeCloudReplayGuards, isSupportedLinkProviderKind, linkProofScopes, pendingServiceUpdateExists, + parseManagedEndpointLocalOrigin, reconcileDesiredCloudLink, + reconcileDesiredCloudLinkIfStillDesired, + recoverManagedCloudTunnel, + registerManagedCloudTunnelRecovery, releaseManagedTunnelOnShutdown, + startManagedCloudTunnelIfOriginConfirmed, } from "./http.ts"; +import { + managedTunnelStartupAction, + retryManagedTunnelRegistration, +} from "./managedTunnelStartup.ts"; +import { shouldRetryCloudLink } from "./relayResponse.ts"; import * as ManagedEndpointRuntime from "./ManagedEndpointRuntime.ts"; import { traceAuthenticatedRelayRequest, traceRelayRequest } from "./traceRelayRequest.ts"; @@ -54,6 +81,9 @@ const storeFailure = (tag: "AlreadyExists" | "PermissionDenied") => }); const unusedSecretStoreOperation = () => Effect.die("unused secret-store operation"); +const decodeManagedTunnelRecoveryRegistration = Schema.decodeUnknownEffect( + Schema.fromJsonString(RelayManagedEndpointRecoveryRegistrationRequest), +); function makeSecretStore( create: ServerSecretStore.ServerSecretStore["Service"]["create"], @@ -209,6 +239,9 @@ describe("reconcileDesiredCloudLink", () => { ManagedEndpointRuntime.CloudManagedEndpointRuntime, ManagedEndpointRuntime.CloudManagedEndpointRuntime.of({ applyConfig: unusedSecretStoreOperation, + recoveryRequests: Stream.empty, + requestRecovery: () => Effect.void, + withLinkStateLock: (effect) => effect, } satisfies ManagedEndpointRuntime.CloudManagedEndpointRuntime["Service"]), ), Effect.provideService( @@ -219,7 +252,7 @@ describe("reconcileDesiredCloudLink", () => { CliTokenManager.CloudCliTokenManager, CliTokenManager.CloudCliTokenManager.of({ get: unusedSecretStoreOperation(), - getExisting: Effect.succeed(Option.none()), + getExisting: Effect.succeedNone, hasCredential: unusedSecretStoreOperation(), store: () => unusedSecretStoreOperation(), clear: unusedSecretStoreOperation(), @@ -234,6 +267,39 @@ describe("reconcileDesiredCloudLink", () => { ); }); +describe("parseManagedEndpointLocalOrigin", () => { + it.each([ + { + input: "http://127.0.0.1:80", + httpBaseUrl: "http://127.0.0.1", + wsBaseUrl: "ws://127.0.0.1", + port: 80, + }, + { + input: "https://127.0.0.1:443", + httpBaseUrl: "https://127.0.0.1", + wsBaseUrl: "wss://127.0.0.1", + port: 443, + }, + ])("accepts an explicit default port in $input", ({ input, httpBaseUrl, wsBaseUrl, port }) => { + expect(parseManagedEndpointLocalOrigin(input)).toEqual({ + httpBaseUrl, + wsBaseUrl, + origin: { localHttpHost: "127.0.0.1", localHttpPort: port }, + }); + }); + + it.each([ + "ftp://127.0.0.1:3773", + "http://user:password@127.0.0.1:3773", + "http://127.0.0.1:3773/api", + "http://127.0.0.1:3773?mode=test", + "http://127.0.0.1:3773#fragment", + ])("rejects non-origin URL %s", (input) => { + expect(() => parseManagedEndpointLocalOrigin(input)).toThrow("Invalid local origin"); + }); +}); + describe("releaseManagedTunnelOnShutdown", () => { const cliToken: CliTokenManager.PersistedToken = { accessToken: "cli-access-token", @@ -251,7 +317,10 @@ describe("releaseManagedTunnelOnShutdown", () => { Effect.sync(() => { values.set(name, value); }), - create: unusedSecretStoreOperation, + create: (name, value) => + Effect.sync(() => { + values.set(name, value); + }), getOrCreateRandom: unusedSecretStoreOperation, remove: (name) => Effect.sync(() => { @@ -265,7 +334,9 @@ describe("releaseManagedTunnelOnShutdown", () => { readonly store: ServerSecretStore.ServerSecretStore["Service"]; readonly applyConfigCalls: Array; readonly requests: Array; + readonly onRequest?: (request: HttpClientRequest.HttpClientRequest) => Effect.Effect; readonly respond?: () => Response; + readonly respondEffect?: Effect.Effect; } // Writes the launcher's durable state file into this test's baseDir with @@ -285,6 +356,20 @@ describe("releaseManagedTunnelOnShutdown", () => { ); }); + // Writes the marker the desktop app leaves just before it stops its backend + // to install an update, and returns when it was written. + const writeDesktopUpdateRestartMarker = Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const config = yield* ServerConfigModule.ServerConfig; + const runtimeDir = path.join(config.baseDir, "runtime"); + const markerPath = path.join(runtimeDir, DESKTOP_UPDATE_RESTART_MARKER_FILE); + yield* fs.makeDirectory(runtimeDir, { recursive: true }); + yield* fs.writeFileString(markerPath, ""); + const { mtime } = yield* fs.stat(markerPath); + return Option.getOrThrow(mtime).getTime(); + }); + const provideReleaseHarness = (harness: ReleaseHarness) => (effect: Effect.Effect) => @@ -303,10 +388,19 @@ describe("releaseManagedTunnelOnShutdown", () => { applyConfig: (config) => Effect.sync(() => { harness.applyConfigCalls.push(config); - return { - status: "disabled", - } satisfies ManagedEndpointRuntime.CloudManagedEndpointRuntimeStatus; + return config === null + ? ({ + status: "disabled", + } satisfies ManagedEndpointRuntime.CloudManagedEndpointRuntimeStatus) + : ({ + status: "running", + providerKind: "cloudflare_tunnel", + pid: 123, + } satisfies ManagedEndpointRuntime.CloudManagedEndpointRuntimeStatus); }), + recoveryRequests: Stream.empty, + requestRecovery: () => Effect.void, + withLinkStateLock: (effect) => effect, }), ), Effect.provideService( @@ -317,7 +411,7 @@ describe("releaseManagedTunnelOnShutdown", () => { CliTokenManager.CloudCliTokenManager, CliTokenManager.CloudCliTokenManager.of({ get: unusedSecretStoreOperation(), - getExisting: Effect.succeed(Option.some(cliToken)), + getExisting: Effect.succeedSome(cliToken), hasCredential: unusedSecretStoreOperation(), store: () => unusedSecretStoreOperation(), clear: unusedSecretStoreOperation(), @@ -328,11 +422,14 @@ describe("releaseManagedTunnelOnShutdown", () => { HttpClient.make((request) => Effect.sync(() => { harness.requests.push(request); - return HttpClientResponse.fromWeb( - request, - (harness.respond ?? (() => Response.json({ ok: true })))(), - ); - }), + }).pipe( + Effect.andThen(harness.onRequest?.(request) ?? Effect.void), + Effect.andThen( + harness.respondEffect ?? + Effect.sync(() => (harness.respond ?? (() => Response.json({ ok: true })))()), + ), + Effect.map((response) => HttpClientResponse.fromWeb(request, response)), + ), ), ), // The release consults the launcher state file under the configured @@ -348,10 +445,27 @@ describe("releaseManagedTunnelOnShutdown", () => { // The persisted state of a CLI-managed link whose tunnel is releasable. const managedLinkSecrets = [ [CLOUD_ENDPOINT_RUNTIME_CONFIG, "runtime-config"], + [CLOUD_ENDPOINT_CONFIRMED_ORIGIN, "confirmed-origin"], [RELAY_URL_SECRET, "https://relay.example.test"], [CLOUD_CLI_DESIRED_LINK_SECRET, "managed"], ] as const; + it.effect("does not recreate a link that was unlinked while startup registration retried", () => { + const { store, values } = makeMemorySecretStore(managedLinkSecrets); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + // Registration started while this marker existed. Unlink removes it + // before startup receives the relay's final not_linked response. + values.delete(CLOUD_CLI_DESIRED_LINK_SECRET); + + expect(yield* reconcileDesiredCloudLinkIfStillDesired("http://127.0.0.1:3773")).toBeNull(); + expect(requests).toEqual([]); + expect(applyConfigCalls).toEqual([]); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }); + it.effect("stops the connector, releases the relay tunnel, and drops the dead token", () => { const { store, values } = makeMemorySecretStore(managedLinkSecrets); const applyConfigCalls: Array = []; @@ -370,6 +484,7 @@ describe("releaseManagedTunnelOnShutdown", () => { ); expect(request.headers.authorization).toBe("Bearer cli-access-token"); expect(values.has(CLOUD_ENDPOINT_RUNTIME_CONFIG)).toBe(false); + expect(values.has(CLOUD_ENDPOINT_CONFIRMED_ORIGIN)).toBe(false); }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); }); @@ -454,6 +569,38 @@ describe("releaseManagedTunnelOnShutdown", () => { }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); }); + it.effect("keeps the tunnel once when the desktop app restarts it for an update", () => { + const { store, values } = makeMemorySecretStore(managedLinkSecrets); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + yield* TestClock.setTime(yield* writeDesktopUpdateRestartMarker); + + expect(yield* releaseManagedTunnelOnShutdown()).toBe(false); + expect(requests).toEqual([]); + expect(values.has(CLOUD_ENDPOINT_RUNTIME_CONFIG)).toBe(true); + + // The shutdown consumed the marker, so a later quit releases the tunnel. + expect(yield* releaseManagedTunnelOnShutdown()).toBe(true); + expect(requests).toHaveLength(1); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }); + + it.effect("releases the tunnel when the desktop update marker is stale", () => { + const { store } = makeMemorySecretStore(managedLinkSecrets); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + const writtenAt = yield* writeDesktopUpdateRestartMarker; + yield* TestClock.setTime(writtenAt + Duration.toMillis(Duration.minutes(2))); + + expect(yield* releaseManagedTunnelOnShutdown()).toBe(true); + expect(requests).toHaveLength(1); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }); + it.effect("still releases a pending update when the launcher is stopping", () => { // `t3 service uninstall` or `systemctl stop` during the pending window: // the launcher writes its stop marker before signalling the child, so no @@ -579,6 +726,501 @@ describe("releaseManagedTunnelOnShutdown", () => { }), ); }); + + it.effect("registers an existing tunnel and starts the confirmed connector", () => { + const { store } = makeMemorySecretStore([ + [ + CLOUD_ENDPOINT_RUNTIME_CONFIG, + '{"providerKind":"cloudflare_tunnel","connectorToken":"existing-token","tunnelId":"existing-tunnel"}', + ], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect(yield* registerManagedCloudTunnelRecovery("http://127.0.0.1:3773")).toMatchObject({ + status: "ready", + }); + expect(requests).toHaveLength(1); + expect(requests[0]?.method).toBe("POST"); + expect(requests[0]?.url).toBe( + "https://relay.example.test/v1/environments/env_123/tunnel/recovery", + ); + expect(requests[0]?.headers.authorization).toBe("Bearer environment-credential"); + const body = requests[0]?.body; + expect(body?._tag).toBe("Uint8Array"); + if (body?._tag === "Uint8Array") { + expect( + yield* decodeManagedTunnelRecoveryRegistration(new TextDecoder().decode(body.body)), + ).toMatchObject({ + cloudUserId: "user-123", + tunnelId: "existing-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + } + expect(applyConfigCalls).toHaveLength(1); + }).pipe( + provideReleaseHarness({ + store, + applyConfigCalls, + requests, + respond: () => Response.json({ status: "ready" }), + }), + ); + }); + + it.effect("reconciles a changed port after a relay outage outlasts the startup fallback", () => { + const config = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "existing-token", + tunnelId: "existing-tunnel", + }; + const { store } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, JSON.stringify(config)], + [ + CLOUD_ENDPOINT_CONFIRMED_ORIGIN, + JSON.stringify({ + config, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + let relayAvailable = false; + const localOrigin = "http://127.0.0.1:4884"; + + return Effect.gen(function* () { + const fallbackStarted = yield* Deferred.make(); + const firstFailure = yield* Deferred.make(); + expect(yield* startManagedCloudTunnelIfOriginConfirmed(localOrigin)).toBe(false); + const registration = yield* Effect.forkChild( + retryManagedTunnelRegistration( + registerManagedCloudTunnelRecovery(localOrigin).pipe( + Effect.tapError(() => Deferred.succeed(firstFailure, undefined)), + ), + shouldRetryCloudLink, + startManagedCloudTunnelIfOriginConfirmed(localOrigin, { + requireConfirmedOrigin: false, + }).pipe( + Effect.orDie, + Effect.tap((started) => { + expect(started).toBe(true); + return Deferred.succeed(fallbackStarted, undefined); + }), + Effect.asVoid, + ), + ), + { startImmediately: true }, + ); + yield* Deferred.await(firstFailure); + yield* TestClock.adjust("15 minutes"); + yield* Effect.raceFirst( + Deferred.await(fallbackStarted), + Fiber.join(registration).pipe( + Effect.andThen(Effect.die("Registration ended before starting the fallback")), + ), + ); + expect(applyConfigCalls).toEqual([config]); + const attemptsBeforeRecovery = requests.length; + + relayAvailable = true; + yield* TestClock.adjust("1 minute"); + expect(yield* Fiber.join(registration)).toMatchObject({ status: "ready" }); + expect(requests.length).toBeGreaterThan(attemptsBeforeRecovery); + const marker = yield* store.get(CLOUD_ENDPOINT_CONFIRMED_ORIGIN); + expect(Option.isSome(marker)).toBe(true); + if (Option.isSome(marker)) { + expect( + Option.getOrThrow(decodeConfirmedOrigin(new TextDecoder().decode(marker.value))), + ).toEqual({ + config, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 4884 }, + }); + } + }).pipe( + provideReleaseHarness({ + store, + applyConfigCalls, + requests, + respond: () => + relayAvailable + ? Response.json({ status: "ready" }) + : Response.json({ message: "relay unavailable" }, { status: 503 }), + }), + ); + }); + + it.effect( + "starts a connector with a marker for the current origin without contacting relay", + () => { + const configJson = + '{"providerKind":"cloudflare_tunnel","connectorToken":"existing-token","tunnelId":"existing-tunnel"}'; + const config = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "existing-token", + tunnelId: "existing-tunnel", + }; + const { store } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, configJson], + [ + CLOUD_ENDPOINT_CONFIRMED_ORIGIN, + `{"config":${configJson},"origin":{"localHttpHost":"127.0.0.1","localHttpPort":3773}}`, + ], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect(yield* startManagedCloudTunnelIfOriginConfirmed("http://127.0.0.1:3773")).toBe(true); + expect(applyConfigCalls).toEqual([config]); + expect(requests).toEqual([]); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }, + ); + + it.effect.each([ + { name: "missing", marker: undefined, origin: "http://127.0.0.1:3773" }, + { + name: "stale", + marker: + '{"config":{"providerKind":"cloudflare_tunnel","connectorToken":"existing-token","tunnelId":"existing-tunnel"},"origin":{"localHttpHost":"127.0.0.1","localHttpPort":3773}}', + origin: "http://127.0.0.1:4884", + }, + ])("does not start a connector with a $name origin marker", ({ marker, origin }) => { + const entries: Array = [ + [ + CLOUD_ENDPOINT_RUNTIME_CONFIG, + '{"providerKind":"cloudflare_tunnel","connectorToken":"existing-token","tunnelId":"existing-tunnel"}', + ], + ]; + if (marker !== undefined) entries.push([CLOUD_ENDPOINT_CONFIRMED_ORIGIN, marker]); + const { store } = makeMemorySecretStore(entries); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect(yield* startManagedCloudTunnelIfOriginConfirmed(origin)).toBe(false); + expect(applyConfigCalls).toEqual([]); + expect(requests).toEqual([]); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }); + + it.effect( + "starts the stored connector without a marker when confirmation is not required", + () => { + const config = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "existing-token", + tunnelId: "existing-tunnel", + }; + const { store } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, JSON.stringify(config)], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect( + yield* startManagedCloudTunnelIfOriginConfirmed("http://127.0.0.1:3773", { + requireConfirmedOrigin: false, + }), + ).toBe(true); + expect(applyConfigCalls).toEqual([config]); + expect(requests).toEqual([]); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }, + ); + + it.effect.each(["replaced", "removed"] as const)( + "does not activate a tunnel when its runtime config is %s during registration", + (mutation) => { + const originalConfig = + '{"providerKind":"cloudflare_tunnel","connectorToken":"existing-token","tunnelId":"existing-tunnel"}'; + const { store, values } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, originalConfig], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect(yield* registerManagedCloudTunnelRecovery("http://127.0.0.1:3773")).toEqual({ + status: "superseded", + }); + expect(applyConfigCalls).toEqual([]); + expect(values.has(CLOUD_ENDPOINT_CONFIRMED_ORIGIN)).toBe(false); + }).pipe( + provideReleaseHarness({ + store, + applyConfigCalls, + requests, + respond: () => { + if (mutation === "replaced") { + values.set( + CLOUD_ENDPOINT_RUNTIME_CONFIG, + new TextEncoder().encode( + '{"providerKind":"cloudflare_tunnel","connectorToken":"fresh-token","tunnelId":"fresh-tunnel"}', + ), + ); + } else { + values.delete(CLOUD_ENDPOINT_RUNTIME_CONFIG); + } + return Response.json({ status: "ready" }); + }, + }), + ); + }, + ); + + it.effect("requests startup recovery for a legacy config without a recorded tunnel ID", () => { + const { store } = makeMemorySecretStore([ + [ + CLOUD_ENDPOINT_RUNTIME_CONFIG, + '{"providerKind":"cloudflare_tunnel","connectorToken":"token"}', + ], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + const registration = yield* registerManagedCloudTunnelRecovery("http://127.0.0.1:3773"); + expect(registration).toEqual({ + status: "recovery_required", + config: { providerKind: "cloudflare_tunnel", connectorToken: "token" }, + }); + expect( + managedTunnelStartupAction({ + wantsCliLink: false, + registration, + }), + ).toEqual({ + action: "request_recovery", + config: { providerKind: "cloudflare_tunnel", connectorToken: "token" }, + }); + expect(requests).toEqual([]); + expect(applyConfigCalls).toEqual([]); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }); + + it.effect("recovers a web-linked tunnel with its environment credential", () => { + const oldConfig = + '{"providerKind":"cloudflare_tunnel","connectorToken":"old-token","tunnelId":"old-tunnel"}'; + const nextConfig = { + providerKind: "cloudflare_tunnel", + connectorToken: "new-token", + tunnelId: "new-tunnel", + } as const; + const { store, values } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, oldConfig], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect(yield* recoverManagedCloudTunnel("http://127.0.0.1:3773")).toBe(true); + expect(requests).toHaveLength(1); + expect(requests[0]?.method).toBe("POST"); + expect(requests[0]?.url).toBe("https://relay.example.test/v1/environments/env_123/tunnel"); + expect(requests[0]?.headers.authorization).toBe("Bearer environment-credential"); + expect(applyConfigCalls).toEqual([nextConfig]); + expect( + Option.getOrNull( + decodeRuntimeConfig(new TextDecoder().decode(values.get(CLOUD_ENDPOINT_RUNTIME_CONFIG))), + ), + ).toEqual(nextConfig); + }).pipe( + provideReleaseHarness({ + store, + applyConfigCalls, + requests, + respond: () => + Response.json({ + endpoint: { + httpBaseUrl: "https://environment.example.test/", + wsBaseUrl: "wss://environment.example.test/ws", + providerKind: "cloudflare_tunnel", + }, + endpointRuntime: nextConfig, + }), + }), + ); + }); + + it.effect("allows managed tunnel provisioning to take longer than ten seconds", () => + Effect.gen(function* () { + const oldConfig = + '{"providerKind":"cloudflare_tunnel","connectorToken":"old-token","tunnelId":"old-tunnel"}'; + const nextConfig = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "new-token", + tunnelId: "new-tunnel", + }; + const { store } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, oldConfig], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + const requestStarted = yield* Deferred.make(); + const response = yield* Deferred.make(); + const recovery = yield* recoverManagedCloudTunnel("http://127.0.0.1:3773").pipe( + provideReleaseHarness({ + store, + applyConfigCalls, + requests, + onRequest: () => Deferred.succeed(requestStarted, undefined), + respondEffect: Deferred.await(response), + }), + Effect.forkChild({ startImmediately: true }), + ); + + yield* Deferred.await(requestStarted); + expect(requests).toHaveLength(1); + yield* TestClock.adjust("11 seconds"); + yield* Effect.yieldNow; + yield* Deferred.succeed( + response, + Response.json({ + endpoint: { + httpBaseUrl: "https://environment.example.test/", + wsBaseUrl: "wss://environment.example.test/ws", + providerKind: "cloudflare_tunnel", + }, + endpointRuntime: nextConfig, + }), + ); + + expect(yield* Fiber.join(recovery)).toBe(true); + expect(requests).toHaveLength(1); + expect(applyConfigCalls).toEqual([nextConfig]); + }), + ); + + it.effect("does not recover an environment without a managed tunnel credential", () => { + const { store } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, "old-config"], + [RELAY_URL_SECRET, "https://relay.example.test"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect(yield* recoverManagedCloudTunnel("http://127.0.0.1:3773")).toBe(false); + expect(applyConfigCalls).toEqual([]); + expect(requests).toEqual([]); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }); + + it.effect("ignores recovery requests for a tunnel that has already been replaced", () => { + const { store } = makeMemorySecretStore([ + [ + CLOUD_ENDPOINT_RUNTIME_CONFIG, + '{"providerKind":"cloudflare_tunnel","connectorToken":"current-token","tunnelId":"current-tunnel"}', + ], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + expect( + yield* recoverManagedCloudTunnel("http://127.0.0.1:3773", { + providerKind: "cloudflare_tunnel", + connectorToken: "old-token", + tunnelId: "old-tunnel", + }), + ).toBe(false); + expect(requests).toEqual([]); + expect(applyConfigCalls).toEqual([]); + }).pipe(provideReleaseHarness({ store, applyConfigCalls, requests })); + }); + + it.effect.each([ + { status: 401, errorTag: "EnvironmentHttpUnauthorizedError" }, + { status: 403, errorTag: "EnvironmentHttpForbiddenError" }, + { status: 409, errorTag: "EnvironmentHttpBadRequestError" }, + ])("preserves a permanent $status relay recovery failure", ({ status, errorTag }) => { + const { store } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, "old-config"], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + + return Effect.gen(function* () { + const error = yield* Effect.flip(recoverManagedCloudTunnel("http://127.0.0.1:3773")); + + expect(error._tag).toBe(errorTag); + expect(requests).toHaveLength(1); + expect(applyConfigCalls).toEqual([]); + }).pipe( + provideReleaseHarness({ + store, + applyConfigCalls, + requests, + respond: () => Response.json({}, { status }), + }), + ); + }); + + it.effect("keeps a tunnel configuration replaced during recovery", () => { + const { store, values } = makeMemorySecretStore([ + [CLOUD_ENDPOINT_RUNTIME_CONFIG, "old-config"], + [RELAY_URL_SECRET, "https://relay.example.test"], + [CLOUD_LINKED_USER_ID, "user-123"], + [RELAY_ENVIRONMENT_CREDENTIAL_SECRET, "environment-credential"], + ]); + const applyConfigCalls: Array = []; + const requests: Array = []; + const freshConfig = new TextEncoder().encode("fresh-config"); + + return Effect.gen(function* () { + expect(yield* recoverManagedCloudTunnel("http://127.0.0.1:3773")).toBe(false); + expect(values.get(CLOUD_ENDPOINT_RUNTIME_CONFIG)).toBe(freshConfig); + expect(applyConfigCalls).toEqual([]); + }).pipe( + provideReleaseHarness({ + store, + applyConfigCalls, + requests, + respond: () => { + values.set(CLOUD_ENDPOINT_RUNTIME_CONFIG, freshConfig); + return Response.json({ + endpoint: { + httpBaseUrl: "https://environment.example.test/", + wsBaseUrl: "wss://environment.example.test/ws", + providerKind: "cloudflare_tunnel", + }, + endpointRuntime: { + providerKind: "cloudflare_tunnel", + connectorToken: "replacement-token", + }, + }); + }, + }), + ); + }); }); describe("link proof provider kinds", () => { diff --git a/apps/server/src/cloud/http.ts b/apps/server/src/cloud/http.ts index e0d458b4b97c..b4c366a500c1 100644 --- a/apps/server/src/cloud/http.ts +++ b/apps/server/src/cloud/http.ts @@ -11,6 +11,7 @@ import { EnvironmentHttpConflictError, EnvironmentHttpInternalServerError, EnvironmentHttpUnauthorizedError, + DESKTOP_UPDATE_RESTART_MARKER_FILE, } from "@t3tools/contracts"; import { RelayCloudEnvironmentHealthProofPayload, @@ -28,6 +29,10 @@ import { RelayEnvironmentLinkProofPayload, RelayLinkProofRequest, RelayManagedEndpointOrigin, + RelayManagedEndpointRecoveryProofPayload, + RelayManagedEndpointRecoveryRegistrationResponse, + RelayManagedEndpointRecoveryResponse, + type RelayManagedEndpointRuntimeConfig, RelayOkResponse, } from "@t3tools/contracts/relay"; import { withRelayClientTracing } from "@t3tools/shared/relayTracing"; @@ -36,12 +41,14 @@ import { RELAY_HEALTH_REQUEST_TYP, RELAY_HEALTH_RESPONSE_TYP, RELAY_LINK_PROOF_TYP, + RELAY_MANAGED_TUNNEL_RECOVERY_TYP, RELAY_MINT_REQUEST_TYP, RELAY_MINT_RESPONSE_TYP, signRelayJwt, verifyRelayJwt, } from "@t3tools/shared/relayJwt"; import { isSecureRelayUrl } from "@t3tools/shared/relayUrl"; +import * as Clock from "effect/Clock"; import * as DateTime from "effect/DateTime"; import * as Crypto from "effect/Crypto"; import * as Duration from "effect/Duration"; @@ -50,10 +57,12 @@ import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; +import * as Schedule from "effect/Schedule"; import * as HttpEffect from "effect/unstable/http/HttpEffect"; import { HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; +import * as HttpServer from "effect/unstable/http/HttpServer"; import * as EnvironmentAuth from "../auth/EnvironmentAuth.ts"; import * as ServerSecretStore from "../auth/ServerSecretStore.ts"; @@ -68,9 +77,13 @@ import { } from "./serviceProtocol.ts"; import { CLOUD_ENDPOINT_RUNTIME_CONFIG, + CLOUD_ENDPOINT_CONFIRMED_ORIGIN, + decodeConfirmedOrigin, CLOUD_LINKED_USER_ID, CLOUD_MINT_PUBLIC_KEY, + decodeRuntimeConfig, encodeEndpointRuntimeConfigJson, + encodeConfirmedOriginJson, PUBLISH_AGENT_ACTIVITY_SECRET, RELAY_ENVIRONMENT_CREDENTIAL_SECRET, RELAY_ISSUER_SECRET, @@ -85,7 +98,7 @@ import { import * as CliTokenManager from "./CliTokenManager.ts"; import { getOrCreateEnvironmentKeyPairFromSecretStore } from "./environmentKeys.ts"; import { traceRelayRequest } from "./traceRelayRequest.ts"; -import { filterRelayResponse, relayRequestError } from "./relayResponse.ts"; +import { filterRelayResponse, relayRequestError, shouldRetryCloudLink } from "./relayResponse.ts"; const CLOUD_MINT_NONCE_PREFIX = "cloud-mint-nonce-"; const CLOUD_MINT_JTI_PREFIX = "cloud-mint-jti-"; @@ -93,6 +106,9 @@ const CLOUD_HEALTH_NONCE_PREFIX = "cloud-health-nonce-"; const CLOUD_HEALTH_JTI_PREFIX = "cloud-health-jti-"; const CLOUD_PROOF_MAX_LIFETIME_SECONDS = 5 * 60; const CLOUD_PROOF_CLOCK_SKEW_SECONDS = 60; +// The desktop app stops its backends within seconds of writing the marker. +const DESKTOP_UPDATE_RESTART_MARKER_TTL = Duration.minutes(1); +const MANAGED_ENDPOINT_PROVISION_REQUEST_TIMEOUT = Duration.minutes(2); const LOOPBACK_HOSTNAMES = new Set(["127.0.0.1", "::1", "localhost"]); const CLOUD_CREDENTIAL_RESPONSE_HEADERS = { "cache-control": "no-store", @@ -136,8 +152,9 @@ export function consumeCloudReplayGuards(input: { readonly names: ReadonlyArray; readonly value: Uint8Array; }) { - return Effect.all( - input.names.map((name) => + return Effect.forEach( + input.names, + (name) => input.secrets.create(name, input.value).pipe( Effect.as(true), Effect.catchIf(ServerSecretStore.isSecretStoreError, (error) => @@ -146,7 +163,6 @@ export function consumeCloudReplayGuards(input: { : Effect.fail(error), ), ), - ), { concurrency: input.names.length }, ).pipe(Effect.map((created) => created.every(Boolean))); } @@ -297,6 +313,33 @@ function endpointRequestPort(url: URL): number { return Number(url.port || (url.protocol === "https:" ? 443 : 80)); } +export function parseManagedEndpointLocalOrigin(localOrigin: string) { + const url = new URL(localOrigin); + if ( + localOrigin !== localOrigin.trim() || + (url.protocol !== "http:" && url.protocol !== "https:") || + url.username !== "" || + url.password !== "" || + url.pathname !== "/" || + url.search !== "" || + url.hash !== "" || + localOrigin.includes("?") || + localOrigin.includes("#") + ) { + throw new Error("Invalid local origin"); + } + const wsUrl = new URL(url.origin); + wsUrl.protocol = url.protocol === "https:" ? "wss:" : "ws:"; + return { + httpBaseUrl: url.origin, + wsBaseUrl: wsUrl.origin, + origin: { + localHttpHost: url.hostname, + localHttpPort: endpointRequestPort(url), + } satisfies RelayManagedEndpointOrigin, + }; +} + function isAllowedEndpointOrigin(input: { readonly origin: RelayManagedEndpointOrigin; readonly requestUrl: string; @@ -451,55 +494,249 @@ const cloudLinkProofHandler = Effect.fn("environment.cloud.linkProof")( ), ); -const applyCloudRelayConfig = Effect.fn("environment.cloud.applyRelayConfig")(function* ( +function managedEndpointRuntimeConfigsMatch( + left: RelayManagedEndpointRuntimeConfig, + right: RelayManagedEndpointRuntimeConfig, +): boolean { + return ( + left.providerKind === right.providerKind && + left.connectorToken === right.connectorToken && + left.tunnelId === right.tunnelId && + left.tunnelName === right.tunnelName + ); +} + +const activateManagedTunnel = Effect.fn("environment.cloud.activateManagedTunnel")(function* ( dependencies: CloudHttpDependencies, - payload: RelayEnvironmentConfigRequest, + input: { + readonly config: RelayManagedEndpointRuntimeConfig; + readonly configJson: string; + readonly origin: RelayManagedEndpointOrigin; + }, ) { - yield* validateRelayConfigPayload(payload); - yield* validateLinkedCloudUser({ - secrets: dependencies.secrets, - cloudUserId: payload.cloudUserId, + return yield* dependencies.endpointRuntime.withLinkStateLock( + Effect.gen(function* () { + const currentConfig = yield* dependencies.secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG); + if (Option.isNone(currentConfig) || bytesToString(currentConfig.value) !== input.configJson) { + return null; + } + const status = yield* dependencies.endpointRuntime.applyConfig(input.config); + if (status.status !== "running") { + return yield* new EnvironmentCloudEndpointUnavailableError({ + message: "Managed endpoint runtime could not be started.", + endpointRuntimeStatus: status, + }); + } + const marker = yield* encodeConfirmedOriginJson({ + config: input.config, + origin: input.origin, + }); + yield* dependencies.secrets.set(CLOUD_ENDPOINT_CONFIRMED_ORIGIN, stringToBytes(marker)); + return status; + }), + ); +}); + +const activateManagedTunnelWithRetry = ( + dependencies: CloudHttpDependencies, + input: { + readonly config: RelayManagedEndpointRuntimeConfig; + readonly configJson: string; + readonly origin: RelayManagedEndpointOrigin; + }, + retryRuntimeFailures: boolean, +) => { + const activate = activateManagedTunnel(dependencies, input); + return retryRuntimeFailures + ? activate.pipe( + Effect.retry({ + while: (error) => + error._tag === "EnvironmentCloudEndpointUnavailableError" && + ManagedEndpointRuntime.isRetryableManagedEndpointRuntimeStatus( + error.endpointRuntimeStatus, + ), + schedule: Schedule.exponential("1 second").pipe( + Schedule.modifyDelay(({ duration }) => + Effect.succeed(Duration.min(duration, Duration.seconds(30))), + ), + Schedule.jittered, + ), + }), + ) + : activate; +}; + +export const startManagedCloudTunnelIfOriginConfirmed = Effect.fn( + "environment.cloud.startManagedCloudTunnelIfOriginConfirmed", +)(function* (localOrigin: string, options?: { readonly requireConfirmedOrigin?: boolean }) { + const dependencies = yield* cloudHttpDependencies; + const requireConfirmedOrigin = options?.requireConfirmedOrigin ?? true; + const parsedOrigin = yield* Effect.try({ + try: () => parseManagedEndpointLocalOrigin(localOrigin), + catch: () => + new EnvironmentHttpBadRequestError({ + message: "Could not resolve local environment origin.", + }), }); - yield* validateCloudMintPublicKey(payload.cloudMintPublicKey); - const endpointRuntimeStatus = yield* dependencies.endpointRuntime.applyConfig( - payload.endpointRuntime, + return yield* dependencies.endpointRuntime.withLinkStateLock( + Effect.gen(function* () { + const [runtimeBytes, markerBytes] = yield* Effect.all([ + dependencies.secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG), + dependencies.secrets.get(CLOUD_ENDPOINT_CONFIRMED_ORIGIN), + ]); + if (Option.isNone(runtimeBytes)) return false; + const config = Option.getOrNull(decodeRuntimeConfig(bytesToString(runtimeBytes.value))); + if (config === null || config.providerKind !== "cloudflare_tunnel") return false; + // With the marker required, only a config the relay already confirmed on + // this port may start. Without it, startup is falling back after the + // relay stayed unreachable: an unconfirmed origin may send traffic to a + // stale port, but that beats no remote access at all. + if (requireConfirmedOrigin) { + if (Option.isNone(markerBytes)) return false; + const marker = Option.getOrNull(decodeConfirmedOrigin(bytesToString(markerBytes.value))); + if ( + marker === null || + !managedEndpointRuntimeConfigsMatch(marker.config, config) || + marker.origin.localHttpHost !== parsedOrigin.origin.localHttpHost || + marker.origin.localHttpPort !== parsedOrigin.origin.localHttpPort + ) { + return false; + } + } + const status = yield* dependencies.endpointRuntime.applyConfig(config); + if (status.status !== "running") { + return yield* new EnvironmentCloudEndpointUnavailableError({ + message: "Managed endpoint runtime could not be started.", + endpointRuntimeStatus: status, + }); + } + return true; + }), ); - const ok = - endpointRuntimeStatus.status === "disabled" || endpointRuntimeStatus.status === "running"; - if (!ok) { - return yield* new EnvironmentCloudEndpointUnavailableError({ - message: "Managed endpoint runtime could not be started.", - endpointRuntimeStatus, +}); + +const applyCloudRelayConfig = Effect.fn("environment.cloud.applyRelayConfig")(function* ( + dependencies: CloudHttpDependencies, + payload: RelayEnvironmentConfigRequest, + options?: { + readonly lockHeld?: boolean; + readonly confirmedOrigin?: RelayManagedEndpointOrigin; + }, +) { + const apply = Effect.gen(function* () { + yield* validateRelayConfigPayload(payload); + yield* validateLinkedCloudUser({ + secrets: dependencies.secrets, + cloudUserId: payload.cloudUserId, }); - } + yield* validateCloudMintPublicKey(payload.cloudMintPublicKey); + // Reject unsupported runtimes before touching the connector so a bad + // payload cannot stop a healthy tunnel on its way to a 503. + if ( + payload.endpointRuntime !== null && + payload.endpointRuntime.providerKind !== "cloudflare_tunnel" + ) { + return yield* new EnvironmentCloudEndpointUnavailableError({ + message: "Managed endpoint runtime could not be started.", + endpointRuntimeStatus: { + status: "unsupported", + providerKind: payload.endpointRuntime.providerKind, + }, + }); + } + yield* dependencies.endpointRuntime.applyConfig(null); + yield* dependencies.secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN); - yield* dependencies.secrets.set(RELAY_URL_SECRET, stringToBytes(payload.relayUrl)); - yield* dependencies.secrets.set( - RELAY_ISSUER_SECRET, - stringToBytes(payload.relayIssuer ?? payload.relayUrl), - ); - yield* dependencies.secrets.set(CLOUD_LINKED_USER_ID, stringToBytes(payload.cloudUserId)); - yield* dependencies.secrets.set( - RELAY_ENVIRONMENT_CREDENTIAL_SECRET, - stringToBytes(payload.environmentCredential), - ); - yield* dependencies.secrets.set(CLOUD_MINT_PUBLIC_KEY, stringToBytes(payload.cloudMintPublicKey)); - if (payload.endpointRuntime) { - const endpointRuntimeJson = yield* encodeEndpointRuntimeConfigJson(payload.endpointRuntime); + yield* dependencies.secrets.set(RELAY_URL_SECRET, stringToBytes(payload.relayUrl)); yield* dependencies.secrets.set( - CLOUD_ENDPOINT_RUNTIME_CONFIG, - stringToBytes(endpointRuntimeJson), + RELAY_ISSUER_SECRET, + stringToBytes(payload.relayIssuer ?? payload.relayUrl), ); - } else { - yield* dependencies.secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG); - } - return { ok, endpointRuntimeStatus } satisfies EnvironmentCloudRelayConfigResult; + yield* dependencies.secrets.set(CLOUD_LINKED_USER_ID, stringToBytes(payload.cloudUserId)); + yield* dependencies.secrets.set( + RELAY_ENVIRONMENT_CREDENTIAL_SECRET, + stringToBytes(payload.environmentCredential), + ); + yield* dependencies.secrets.set( + CLOUD_MINT_PUBLIC_KEY, + stringToBytes(payload.cloudMintPublicKey), + ); + if (payload.endpointRuntime) { + const endpointRuntimeJson = yield* encodeEndpointRuntimeConfigJson(payload.endpointRuntime); + yield* dependencies.secrets.set( + CLOUD_ENDPOINT_RUNTIME_CONFIG, + stringToBytes(endpointRuntimeJson), + ); + } else { + yield* dependencies.secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG); + } + if (payload.endpointRuntime === null || options?.confirmedOrigin === undefined) { + return { + ok: true, + endpointRuntimeStatus: { status: "disabled" }, + } satisfies EnvironmentCloudRelayConfigResult; + } + const endpointRuntimeStatus = yield* dependencies.endpointRuntime.applyConfig( + payload.endpointRuntime, + ); + if (endpointRuntimeStatus.status !== "running") { + return yield* new EnvironmentCloudEndpointUnavailableError({ + message: "Managed endpoint runtime could not be started.", + endpointRuntimeStatus, + }); + } + const marker = yield* encodeConfirmedOriginJson({ + config: payload.endpointRuntime, + origin: options.confirmedOrigin, + }); + yield* dependencies.secrets.set(CLOUD_ENDPOINT_CONFIRMED_ORIGIN, stringToBytes(marker)); + return { ok: true, endpointRuntimeStatus } satisfies EnvironmentCloudRelayConfigResult; + }); + return yield* options?.lockHeld ? apply : dependencies.endpointRuntime.withLinkStateLock(apply); }); const cloudRelayConfigHandler = Effect.fn("environment.cloud.relayConfig")( function* (dependencies: CloudHttpDependencies, payload: RelayEnvironmentConfigRequest) { yield* requireEnvironmentScope(AuthRelayWriteScope); - return yield* applyCloudRelayConfig(dependencies, payload); + const result = yield* applyCloudRelayConfig(dependencies, payload); + if (payload.endpointRuntime?.providerKind === "cloudflare_tunnel") { + const server = yield* HttpServer.HttpServer; + const address = server.address; + if (typeof address === "string" || !("port" in address)) { + return yield* new EnvironmentHttpInternalServerError({ + message: "Could not resolve the local server origin.", + }); + } + const registration = yield* registerManagedCloudTunnelRecovery( + `http://127.0.0.1:${address.port}`, + ).pipe( + Effect.retry({ + times: 2, + while: (error) => + shouldRetryCloudLink(error) && + error._tag !== "EnvironmentCloudEndpointUnavailableError", + }), + ); + if (registration.status === "superseded") { + return yield* new EnvironmentHttpConflictError({ + message: "The managed tunnel configuration changed during registration.", + }); + } + if (registration.status === "recovery_required") { + yield* dependencies.endpointRuntime.requestRecovery(registration.config); + } + if (registration.status !== "ready") { + return yield* new EnvironmentCloudEndpointUnavailableError({ + message: "Managed endpoint origin could not be confirmed.", + endpointRuntimeStatus: { status: "disabled" }, + }); + } + return { + ok: true, + endpointRuntimeStatus: registration.endpointRuntimeStatus, + } satisfies EnvironmentCloudRelayConfigResult; + } + return result; }, Effect.catchIf(EnvironmentAuth.isServerAuthInternalError, (error) => failEnvironmentCloudInternalError(error.message)(error), @@ -508,10 +745,14 @@ const cloudRelayConfigHandler = Effect.fn("environment.cloud.relayConfig")( ServerSecretStore.isSecretStoreError, failEnvironmentCloudInternalError("Could not persist environment relay configuration."), ), - Effect.catchTag( - "SchemaError", - failEnvironmentCloudInternalError("Could not persist environment relay configuration."), - ), + Effect.catchTags({ + SchemaError: failEnvironmentCloudInternalError( + "Could not persist environment relay configuration.", + ), + PlatformError: failEnvironmentCloudInternalError( + "Could not register the managed endpoint origin.", + ), + }), ); const relayClientRequest = ( @@ -521,6 +762,7 @@ const relayClientRequest = ( readonly token: string; readonly payload: unknown; readonly schema: Schema.Decoder; + readonly timeout?: Duration.Input; }, ) => HttpClientRequest.post(input.url).pipe( @@ -529,25 +771,20 @@ const relayClientRequest = ( Effect.flatMap(dependencies.httpClient.execute), Effect.flatMap(filterRelayResponse), Effect.flatMap(HttpClientResponse.schemaBodyJson(input.schema)), + Effect.timeout(input.timeout ?? "10 seconds"), Effect.mapError(relayRequestError), withRelayClientTracing, ); const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesiredLinkWith")( function* (dependencies: CloudHttpDependencies, localOrigin: string) { - const localUrl = yield* Effect.try({ - try: () => new URL(localOrigin), + const parsedOrigin = yield* Effect.try({ + try: () => parseManagedEndpointLocalOrigin(localOrigin), catch: () => new EnvironmentHttpBadRequestError({ message: "Could not resolve local environment origin.", }), }); - if (localUrl.origin !== localOrigin) { - return yield* new EnvironmentHttpBadRequestError({ - message: "Could not resolve local environment origin.", - }); - } - const localWsOrigin = localOrigin.replace(/^http/u, "ws"); const token = yield* dependencies.cliTokenManager.getExisting.pipe( Effect.flatMap( Option.match({ @@ -580,16 +817,13 @@ const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesi challenge: challenge.challenge, relayIssuer: relayUrl, endpoint: { - httpBaseUrl: localOrigin, - wsBaseUrl: localWsOrigin, + httpBaseUrl: parsedOrigin.httpBaseUrl, + wsBaseUrl: parsedOrigin.wsBaseUrl, providerKind: managedTunnelsEnabled ? "cloudflare_tunnel" : "manual", }, - origin: { - localHttpHost: localUrl.hostname, - localHttpPort: endpointRequestPort(localUrl), - }, + origin: parsedOrigin.origin, }, - localOrigin, + parsedOrigin.httpBaseUrl, ); const link = yield* relayClientRequest(dependencies, { url: `${relayUrl}/v1/client/environment-links`, @@ -601,16 +835,27 @@ const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesi managedTunnelsEnabled, }, schema: RelayEnvironmentLinkResponse, + timeout: MANAGED_ENDPOINT_PROVISION_REQUEST_TIMEOUT, }); yield* setCliDesiredCloudLink(true, mode); - return yield* applyCloudRelayConfig(dependencies, { - relayUrl, - relayIssuer: link.relayIssuer, - cloudUserId: link.cloudUserId, - environmentCredential: link.environmentCredential, - cloudMintPublicKey: link.cloudMintPublicKey, - endpointRuntime: link.endpointRuntime, - }); + yield* applyCloudRelayConfig( + dependencies, + { + relayUrl, + relayIssuer: link.relayIssuer, + cloudUserId: link.cloudUserId, + environmentCredential: link.environmentCredential, + cloudMintPublicKey: link.cloudMintPublicKey, + endpointRuntime: link.endpointRuntime, + }, + { + lockHeld: true, + confirmedOrigin: parsedOrigin.origin, + }, + ); + // Callers decide on managed tunnel recovery from the mode this link + // actually used, not from a value read before the relay round trip. + return mode; }, Effect.catchIf( ServerSecretStore.isSecretStoreError, @@ -627,7 +872,260 @@ const reconcileDesiredCloudLinkWith = Effect.fn("environment.cloud.reconcileDesi export const reconcileDesiredCloudLink = Effect.fn("environment.cloud.reconcileDesiredLink")( function* (localOrigin: string) { - return yield* reconcileDesiredCloudLinkWith(yield* cloudHttpDependencies, localOrigin); + const dependencies = yield* cloudHttpDependencies; + return yield* dependencies.endpointRuntime.withLinkStateLock( + reconcileDesiredCloudLinkWith(dependencies, localOrigin), + ); + }, +); + +export const reconcileDesiredCloudLinkIfStillDesired = Effect.fn( + "environment.cloud.reconcileDesiredLinkIfStillDesired", +)(function* (localOrigin: string) { + const dependencies = yield* cloudHttpDependencies; + return yield* dependencies.endpointRuntime.withLinkStateLock( + Effect.gen(function* () { + if (!(yield* readCliDesiredCloudLink)) { + return null; + } + return yield* reconcileDesiredCloudLinkWith(dependencies, localOrigin); + }), + ); +}); + +type ManagedTunnelRecoveryProofInput = { + readonly environmentId: RelayManagedEndpointRecoveryProofPayload["environmentId"]; + readonly cloudUserId: string; + readonly relayUrl: string; +} & ( + | { + readonly action: "register"; + readonly tunnelId: string; + readonly origin: RelayManagedEndpointOrigin; + } + | { readonly action: "recover"; readonly origin: RelayManagedEndpointOrigin } +); + +const makeManagedTunnelRecoveryProof = Effect.fn( + "environment.cloud.makeManagedTunnelRecoveryProof", +)(function* (dependencies: CloudHttpDependencies, input: ManagedTunnelRecoveryProofInput) { + const keyPair = yield* getOrCreateEnvironmentKeyPairFromSecretStore(dependencies.secrets); + const configuredIssuer = yield* dependencies.secrets.get(RELAY_ISSUER_SECRET); + const now = yield* DateTime.now; + const issuedAt = Math.floor(now.epochMilliseconds / 1_000); + const claims = { + iss: `t3-env:${input.environmentId}`, + aud: normalizeRelayIssuer( + Option.isSome(configuredIssuer) ? bytesToString(configuredIssuer.value) : input.relayUrl, + ), + sub: input.environmentId, + jti: yield* Crypto.Crypto.pipe(Effect.flatMap((crypto) => crypto.randomUUIDv4)), + iat: issuedAt, + exp: issuedAt + 60, + environmentId: input.environmentId, + cloudUserId: input.cloudUserId, + }; + const payload = + input.action === "register" + ? { + ...claims, + action: "register" as const, + tunnelId: input.tunnelId, + origin: input.origin, + } + : { ...claims, action: "recover" as const, origin: input.origin }; + + return yield* signRelayJwt({ + privateKey: keyPair.privateKey, + typ: RELAY_MANAGED_TUNNEL_RECOVERY_TYP, + payload, + }).pipe( + Effect.mapError( + () => + new EnvironmentHttpInternalServerError({ + message: "Could not sign the managed tunnel recovery request.", + }), + ), + ); +}); + +export const registerManagedCloudTunnelRecovery = Effect.fn( + "environment.cloud.registerManagedCloudTunnelRecovery", +)(function* (localOrigin: string, options?: { readonly retryRuntimeFailures?: boolean }) { + const dependencies = yield* cloudHttpDependencies; + const [runtimeConfig, relayUrl, cloudUserId, environmentCredential] = yield* Effect.all([ + dependencies.secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG), + dependencies.secrets.get(RELAY_URL_SECRET), + dependencies.secrets.get(CLOUD_LINKED_USER_ID), + dependencies.secrets.get(RELAY_ENVIRONMENT_CREDENTIAL_SECRET), + ]); + if ( + Option.isNone(runtimeConfig) || + Option.isNone(relayUrl) || + Option.isNone(cloudUserId) || + Option.isNone(environmentCredential) + ) { + return { status: "not_linked" as const }; + } + + const config = Option.getOrNull(decodeRuntimeConfig(bytesToString(runtimeConfig.value))); + if (config?.providerKind !== "cloudflare_tunnel") { + return { status: "not_linked" as const }; + } + + const parsedOrigin = yield* Effect.try({ + try: () => parseManagedEndpointLocalOrigin(localOrigin), + catch: () => + new EnvironmentHttpBadRequestError({ + message: "Could not resolve local environment origin.", + }), + }); + if (config.tunnelId === undefined) { + return { status: "recovery_required" as const, config }; + } + const origin = parsedOrigin.origin; + const environmentId = yield* dependencies.environment.getEnvironmentId; + const relayUrlValue = bytesToString(relayUrl.value); + const cloudUserIdValue = bytesToString(cloudUserId.value); + const proof = yield* makeManagedTunnelRecoveryProof(dependencies, { + action: "register", + environmentId, + cloudUserId: cloudUserIdValue, + relayUrl: relayUrlValue, + tunnelId: config.tunnelId, + origin, + }); + const registered = yield* relayClientRequest(dependencies, { + url: `${relayUrlValue}/v1/environments/${encodeURIComponent(environmentId)}/tunnel/recovery`, + token: bytesToString(environmentCredential.value), + payload: { + cloudUserId: cloudUserIdValue, + tunnelId: config.tunnelId, + origin, + proof, + }, + schema: RelayManagedEndpointRecoveryRegistrationResponse, + }); + if (registered.status === "recovery_required") { + return { status: registered.status, config }; + } + const endpointRuntimeStatus = yield* activateManagedTunnelWithRetry( + dependencies, + { + config, + configJson: bytesToString(runtimeConfig.value), + origin, + }, + options?.retryRuntimeFailures === true, + ); + return endpointRuntimeStatus === null + ? { status: "superseded" as const } + : { status: "ready" as const, endpointRuntimeStatus }; +}); + +export const recoverManagedCloudTunnel = Effect.fn("environment.cloud.recoverManagedCloudTunnel")( + function* ( + localOrigin: string, + expectedConfig?: RelayManagedEndpointRuntimeConfig, + options?: { readonly retryRuntimeFailures?: boolean }, + ) { + const dependencies = yield* cloudHttpDependencies; + const [runtimeConfig, relayUrl, cloudUserId, environmentCredential] = yield* Effect.all([ + dependencies.secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG), + dependencies.secrets.get(RELAY_URL_SECRET), + dependencies.secrets.get(CLOUD_LINKED_USER_ID), + dependencies.secrets.get(RELAY_ENVIRONMENT_CREDENTIAL_SECRET), + ]); + if ( + Option.isNone(runtimeConfig) || + Option.isNone(relayUrl) || + Option.isNone(cloudUserId) || + Option.isNone(environmentCredential) + ) { + return false; + } + if (expectedConfig !== undefined) { + const current = Option.getOrNull(decodeRuntimeConfig(bytesToString(runtimeConfig.value))); + if ( + current === null || + current.providerKind !== expectedConfig.providerKind || + current.connectorToken !== expectedConfig.connectorToken || + current.tunnelId !== expectedConfig.tunnelId || + current.tunnelName !== expectedConfig.tunnelName + ) { + return false; + } + } + + const parsedOrigin = yield* Effect.try({ + try: () => parseManagedEndpointLocalOrigin(localOrigin), + catch: () => + new EnvironmentHttpBadRequestError({ + message: "Could not resolve local environment origin.", + }), + }); + + const environmentId = yield* dependencies.environment.getEnvironmentId; + const relayUrlValue = bytesToString(relayUrl.value); + const cloudUserIdValue = bytesToString(cloudUserId.value); + const origin = parsedOrigin.origin; + const proof = yield* makeManagedTunnelRecoveryProof(dependencies, { + action: "recover", + environmentId, + cloudUserId: cloudUserIdValue, + relayUrl: relayUrlValue, + origin, + }); + const recovered = yield* relayClientRequest(dependencies, { + url: `${relayUrlValue}/v1/environments/${encodeURIComponent(environmentId)}/tunnel`, + token: bytesToString(environmentCredential.value), + payload: { + cloudUserId: cloudUserIdValue, + origin, + proof, + }, + schema: RelayManagedEndpointRecoveryResponse, + timeout: MANAGED_ENDPOINT_PROVISION_REQUEST_TIMEOUT, + }); + if (recovered.endpointRuntime.providerKind !== "cloudflare_tunnel") { + return yield* new EnvironmentHttpInternalServerError({ + message: "T3 Connect returned an unsupported managed tunnel configuration.", + }); + } + + const encoded = yield* encodeEndpointRuntimeConfigJson(recovered.endpointRuntime).pipe( + Effect.mapError( + () => + new EnvironmentHttpInternalServerError({ + message: "Could not persist the recovered managed tunnel configuration.", + }), + ), + ); + const stored = yield* dependencies.endpointRuntime.withLinkStateLock( + Effect.gen(function* () { + const currentConfig = yield* dependencies.secrets.get(CLOUD_ENDPOINT_RUNTIME_CONFIG); + if ( + Option.isNone(currentConfig) || + bytesToString(currentConfig.value) !== bytesToString(runtimeConfig.value) + ) { + return false; + } + yield* dependencies.secrets.set(CLOUD_ENDPOINT_RUNTIME_CONFIG, stringToBytes(encoded)); + yield* dependencies.secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN); + return true; + }), + ); + if (!stored) return false; + const status = yield* activateManagedTunnelWithRetry( + dependencies, + { + config: recovered.endpointRuntime, + configJson: encoded, + origin, + }, + options?.retryRuntimeFailures === true, + ); + return status !== null; }, ); @@ -663,6 +1161,29 @@ const pendingUpdateHandoffExists = Effect.gen(function* () { return !stopping; }); +// The desktop app writes its marker right before it stops this server to +// install an update, whether a remote client or the local app started it. +// Reading consumes it, so shutdown checks it first. Only a fresh marker counts, +// so a marker the server never read (a hard kill) cannot keep the tunnel on a +// later quit. +const desktopUpdateRestartPending = Effect.gen(function* () { + const config = yield* ServerConfig.ServerConfig; + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const markerPath = path.join(config.baseDir, "runtime", DESKTOP_UPDATE_RESTART_MARKER_FILE); + const marker = yield* fs.stat(markerPath).pipe(Effect.option); + if (Option.isNone(marker)) { + return false; + } + yield* fs.remove(markerPath).pipe(Effect.ignore); + const now = yield* Clock.currentTimeMillis; + return Option.match(marker.value.mtime, { + onNone: () => false, + onSome: (writtenAt) => + now - writtenAt.getTime() < Duration.toMillis(DESKTOP_UPDATE_RESTART_MARKER_TTL), + }); +}); + // Cloudflare bills per provisioned tunnel, so an environment that goes offline // must not leave its tunnel behind. Releasing deletes only the tunnel — the // relay keeps the link and its hostname reservation, and the next startup's @@ -677,24 +1198,23 @@ export const releaseManagedTunnelOnShutdown = Effect.fn( if (Option.isNone(runtimeConfig)) { return false; } - // Only CLI-desired managed links release on shutdown, because the startup - // reconcile that provisions the replacement tunnel only runs for them. A - // link installed by a web/mobile client comes back after a restart by - // reapplying the stored connector token — it has no boot-time re-provision - // path — so its tunnel must survive the restart. (Unlink still deletes it.) + // Only CLI-desired managed links release eagerly because this request uses + // CLI authorization. Web/mobile links register startup recovery with their + // environment credential, and the relay reaper removes them after they are + // down for the configured grace period. Unlink still deletes either kind. if (!(yield* readCliDesiredCloudLink) || (yield* readCliDesiredLinkMode) !== "managed") { return false; } - // A shutdown that hands off to a pending remote update is not the - // environment going offline: the launcher immediately brings a server back - // (the new version, or the old one after a rollback). Deleting the tunnel - // here forces that server to provision a replacement UUID, and the public - // hostname's route to the new tunnel takes 1-2 minutes to propagate — the - // dominant cost of an update restart. Keep the tunnel instead: the next + // A shutdown that hands off to a pending update is not the environment + // going offline: the service launcher or the desktop app immediately brings + // a server back (the new version, or the old one after a rollback). Deleting + // the tunnel here forces that server to provision a replacement UUID, and the + // public hostname's route to the new tunnel takes 1-2 minutes to propagate — + // the dominant cost of an update restart. Keep the tunnel instead: the next // boot respawns the connector from the stored config and is reachable as // soon as it connects, and the reconcile confirms the still-live tunnel // without replacing it. - if (yield* pendingUpdateHandoffExists) { + if ((yield* desktopUpdateRestartPending) || (yield* pendingUpdateHandoffExists)) { yield* Effect.logInfo("Keeping the managed tunnel across the update restart"); return false; } @@ -738,6 +1258,7 @@ export const releaseManagedTunnelOnShutdown = Effect.fn( bytesToString(storedConfig.value) === bytesToString(runtimeConfig.value) ) { yield* dependencies.secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG); + yield* dependencies.secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN); } return true; }); @@ -784,21 +1305,26 @@ const cloudLinkStateHandler = Effect.fn("environment.cloud.linkState")( const cloudUnlinkHandler = Effect.fn("environment.cloud.unlink")( function* (dependencies: CloudHttpDependencies) { yield* requireEnvironmentScope(AuthRelayWriteScope); - const endpointRuntimeStatus = yield* dependencies.endpointRuntime.applyConfig(null); - yield* Effect.all( - [ - dependencies.secrets.remove(CLOUD_LINKED_USER_ID), - dependencies.secrets.remove(RELAY_URL_SECRET), - dependencies.secrets.remove(RELAY_ISSUER_SECRET), - dependencies.secrets.remove(RELAY_ENVIRONMENT_CREDENTIAL_SECRET), - dependencies.secrets.remove(CLOUD_MINT_PUBLIC_KEY), - dependencies.secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG), - dependencies.secrets.remove(PUBLISH_AGENT_ACTIVITY_SECRET), - ], - { concurrency: 7 }, + return yield* dependencies.endpointRuntime.withLinkStateLock( + Effect.gen(function* () { + const endpointRuntimeStatus = yield* dependencies.endpointRuntime.applyConfig(null); + yield* Effect.all( + [ + dependencies.secrets.remove(CLOUD_LINKED_USER_ID), + dependencies.secrets.remove(RELAY_URL_SECRET), + dependencies.secrets.remove(RELAY_ISSUER_SECRET), + dependencies.secrets.remove(RELAY_ENVIRONMENT_CREDENTIAL_SECRET), + dependencies.secrets.remove(CLOUD_MINT_PUBLIC_KEY), + dependencies.secrets.remove(CLOUD_ENDPOINT_RUNTIME_CONFIG), + dependencies.secrets.remove(CLOUD_ENDPOINT_CONFIRMED_ORIGIN), + dependencies.secrets.remove(PUBLISH_AGENT_ACTIVITY_SECRET), + ], + { concurrency: 8 }, + ); + yield* setCliDesiredCloudLink(false); + return { ok: true, endpointRuntimeStatus } satisfies EnvironmentCloudRelayConfigResult; + }), ); - yield* setCliDesiredCloudLink(false); - return { ok: true, endpointRuntimeStatus } satisfies EnvironmentCloudRelayConfigResult; }, Effect.catchIf( ServerSecretStore.isSecretStoreError, diff --git a/apps/server/src/cloud/managedTunnelStartup.test.ts b/apps/server/src/cloud/managedTunnelStartup.test.ts new file mode 100644 index 000000000000..1d75f606cd2f --- /dev/null +++ b/apps/server/src/cloud/managedTunnelStartup.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it } from "@effect/vitest"; +import * as Deferred from "effect/Deferred"; +import * as Effect from "effect/Effect"; +import * as Fiber from "effect/Fiber"; +import * as TestClock from "effect/testing/TestClock"; + +import { + managedTunnelStartupAction, + retryManagedTunnelRegistration, +} from "./managedTunnelStartup.ts"; + +describe("managedTunnelStartupAction", () => { + const config = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "connector-token", + tunnelId: "tunnel-1", + }; + + it("requests tunnel recovery only when the relay proves it is needed", () => { + expect( + managedTunnelStartupAction({ + wantsCliLink: true, + registration: { status: "recovery_required", config }, + }), + ).toEqual({ action: "request_recovery", config }); + }); + + it("creates a desired CLI link only when no local managed link exists", () => { + expect( + managedTunnelStartupAction({ + wantsCliLink: true, + registration: { status: "not_linked" }, + }), + ).toEqual({ action: "reconcile_link" }); + }); + + it.each(["ready", "unavailable"] as const)( + "does not provision after a %s registration result", + (status) => { + expect( + managedTunnelStartupAction({ + wantsCliLink: true, + registration: { status }, + }), + ).toEqual({ action: "none" }); + }, + ); +}); + +describe("retryManagedTunnelRegistration", () => { + it.effect("does not fall back or retry when registration is permanently rejected", () => + Effect.gen(function* () { + let attempts = 0; + let fallbacks = 0; + const error = yield* Effect.flip( + retryManagedTunnelRegistration( + Effect.suspend(() => { + attempts += 1; + return Effect.fail("not authorized"); + }), + () => false, + Effect.sync(() => { + fallbacks += 1; + }), + ), + ); + expect(error).toBe("not authorized"); + expect(attempts).toBe(1); + expect(fallbacks).toBe(0); + }), + ); + + it.effect("stops retrying after the retry window so startup can fall back", () => + Effect.gen(function* () { + let attempts = 0; + const registration = Effect.suspend(() => { + attempts += 1; + return Effect.fail("relay unavailable" as const); + }); + const fiber = yield* Effect.forkChild( + Effect.flip(retryManagedTunnelRegistration(registration, () => true)), + { startImmediately: true }, + ); + yield* TestClock.adjust("15 minutes"); + expect(yield* Fiber.join(fiber)).toBe("relay unavailable"); + // Capped at 30 seconds between attempts, ten minutes allows a bounded run. + expect(attempts).toBeGreaterThan(5); + expect(attempts).toBeLessThan(60); + }), + ); + + it.effect("waits for successful registration before it activates the connector", () => + Effect.gen(function* () { + const firstAttempt = yield* Deferred.make(); + let attempts = 0; + let activations = 0; + let reconciliations = 0; + const registration = Effect.suspend(() => { + attempts += 1; + if (attempts === 1) { + return Deferred.succeed(firstAttempt, undefined).pipe( + Effect.andThen(Effect.fail("relay unavailable" as const)), + ); + } + return Effect.succeed({ status: "ready" as const }); + }); + const startup = retryManagedTunnelRegistration(registration, () => true).pipe( + Effect.tap((result) => + Effect.sync(() => { + const action = managedTunnelStartupAction({ wantsCliLink: true, registration: result }); + if (action.action === "reconcile_link") { + reconciliations += 1; + } + activations += 1; + }), + ), + ); + + const fiber = yield* Effect.forkChild(startup, { startImmediately: true }); + yield* Deferred.await(firstAttempt); + expect(attempts).toBe(1); + expect(activations).toBe(0); + + yield* TestClock.adjust("2 seconds"); + yield* Fiber.join(fiber); + + expect(attempts).toBe(2); + expect(activations).toBe(1); + expect(reconciliations).toBe(0); + }), + ); +}); diff --git a/apps/server/src/cloud/managedTunnelStartup.ts b/apps/server/src/cloud/managedTunnelStartup.ts new file mode 100644 index 000000000000..511d5545a0ac --- /dev/null +++ b/apps/server/src/cloud/managedTunnelStartup.ts @@ -0,0 +1,78 @@ +import type { RelayManagedEndpointRuntimeConfig } from "@t3tools/contracts/relay"; +import * as Duration from "effect/Duration"; +import * as Effect from "effect/Effect"; +import * as Schedule from "effect/Schedule"; + +export type ManagedTunnelRegistrationResult = + | { readonly status: "not_linked" | "ready" | "unavailable" | "superseded" } + | { + readonly status: "recovery_required"; + readonly config: RelayManagedEndpointRuntimeConfig; + }; + +export type ManagedTunnelStartupAction = + | { readonly action: "none" } + | { readonly action: "reconcile_link" } + | { + readonly action: "request_recovery"; + readonly config: RelayManagedEndpointRuntimeConfig; + }; + +export function managedTunnelStartupAction(input: { + readonly wantsCliLink: boolean; + readonly registration: ManagedTunnelRegistrationResult; +}): ManagedTunnelStartupAction { + if (input.registration.status === "recovery_required") { + return { + action: "request_recovery", + config: input.registration.config, + }; + } + if (input.wantsCliLink && input.registration.status === "not_linked") { + return { action: "reconcile_link" }; + } + return { action: "none" }; +} + +// After this window the host can start its stored connector config while +// registration keeps retrying to reconcile the origin when the relay returns. +const MANAGED_TUNNEL_REGISTRATION_RETRY_WINDOW = Duration.minutes(10); + +export const retryManagedTunnelRegistration = ( + registration: Effect.Effect, + isRetryable: (error: E) => boolean, + onRetryWindowExhausted?: Effect.Effect, +) => { + const schedule = Schedule.exponential("1 second").pipe( + Schedule.modifyDelay(({ duration }) => + Effect.succeed(Duration.min(duration, Duration.seconds(30))), + ), + Schedule.jittered, + ); + const withinWindow = registration.pipe( + Effect.retry({ + while: isRetryable, + schedule: schedule.pipe( + Schedule.upTo({ duration: MANAGED_TUNNEL_REGISTRATION_RETRY_WINDOW }), + ), + }), + ); + if (onRetryWindowExhausted === undefined) return withinWindow; + return withinWindow.pipe( + Effect.catchIf(isRetryable, () => + onRetryWindowExhausted.pipe( + Effect.andThen(registration.pipe(Effect.retry({ while: isRetryable, schedule }))), + ), + ), + ); +}; + +// A host asks the relay for a replacement tunnel at most this often. Every +// managed host shares one relay, so a host stuck in a bad loop must not turn +// into a fleet-wide request storm. +export const MANAGED_TUNNEL_RECOVERY_COOLDOWN = Duration.minutes(2); + +// Existing hosts register on their first boot after an upgrade, and desktop +// auto-update delivers that boot to many hosts at once. Spread the first +// registration so the relay and Cloudflare see a ramp instead of a spike. +export const MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER = Duration.seconds(30); diff --git a/apps/server/src/cloud/relayTracing.ts b/apps/server/src/cloud/relayTracing.ts index e35c94545a5e..eeea28a2b68f 100644 --- a/apps/server/src/cloud/relayTracing.ts +++ b/apps/server/src/cloud/relayTracing.ts @@ -7,14 +7,14 @@ const relayClientTracingConfig = resolveRelayClientTracingConfig(); export const headlessRelayClientTracingLayer = makeRelayClientTracingLayer( relayClientTracingConfig, { - serviceName: "t3-headless-relay-client", + serviceName: "t3code-server", runtime: "node", client: "headless-cli", }, ); export const serverRelayBrokerTracingLayer = makeRelayClientTracingLayer(relayClientTracingConfig, { - serviceName: "t3-server", + serviceName: "t3code-server", runtime: "node", client: "environment-server", component: "relay-broker", diff --git a/apps/server/src/cloud/selfUpdate.test.ts b/apps/server/src/cloud/selfUpdate.test.ts index 8ca0baa64a32..05c96c95c2df 100644 --- a/apps/server/src/cloud/selfUpdate.test.ts +++ b/apps/server/src/cloud/selfUpdate.test.ts @@ -105,7 +105,7 @@ const makeHarness = Effect.fn("test.make_self_update_harness")(function* ( order.push("accept"); return "launcher-id"; })), - prepareTrial: Effect.sync((): undefined => undefined), + prepareTrial: Effect.undefined, }); const config = yield* ServerConfig.ServerConfig.pipe( Effect.provide(ServerConfig.layerTest(process.cwd(), baseDir)), diff --git a/apps/server/src/compileCache.test.ts b/apps/server/src/compileCache.test.ts new file mode 100644 index 000000000000..17827bb38ba9 --- /dev/null +++ b/apps/server/src/compileCache.test.ts @@ -0,0 +1,53 @@ +// @effect-diagnostics nodeBuiltinImport:off - the test kills a real Node process to prove the cache is on disk. +import * as NodeChildProcess from "node:child_process"; +import * as NodeFSP from "node:fs/promises"; +import * as NodeOS from "node:os"; +import * as NodePath from "node:path"; +import { assert, it } from "@effect/vitest"; + +it.each([false, true])( + "persists an enabled cache before forced exit (disabled: %s)", + async (disabled) => { + const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "t3-compile-cache-")); + try { + const cacheDirectory = NodePath.join(directory, "cache"); + const child = NodeChildProcess.spawnSync( + process.execPath, + [ + "--input-type=module", + "--eval", + `import * as Effect from ${JSON.stringify(import.meta.resolve("effect/Effect"))}; +const { flushCompileCache } = await import(${JSON.stringify(new URL("./compileCache.ts", import.meta.url).href)}); +await Effect.runPromise(flushCompileCache); +process.kill(process.pid, "SIGKILL");`, + ], + { + encoding: "utf8", + env: { + ...process.env, + NODE_COMPILE_CACHE: cacheDirectory, + NODE_DISABLE_COMPILE_CACHE: disabled ? "1" : undefined, + }, + }, + ); + assert.equal(child.error, undefined); + assert.equal(child.stderr, ""); + assert.notEqual(child.status, 0); + const entries = await NodeFSP.readdir(cacheDirectory, { recursive: true }).catch( + (error: NodeJS.ErrnoException) => { + if (error.code === "ENOENT") return []; + throw error; + }, + ); + const files = await Promise.all( + entries.map((entry) => NodeFSP.stat(NodePath.join(cacheDirectory, entry))), + ); + assert.equal( + files.some((entry) => entry.isFile()), + !disabled, + ); + } finally { + await NodeFSP.rm(directory, { recursive: true, force: true }); + } + }, +); diff --git a/apps/server/src/compileCache.ts b/apps/server/src/compileCache.ts new file mode 100644 index 000000000000..d31e4305774c --- /dev/null +++ b/apps/server/src/compileCache.ts @@ -0,0 +1,9 @@ +import * as NodeModule from "node:module"; +import * as Effect from "effect/Effect"; + +// Desktop enables this cache before loading the backend. Windows force-kills +// the backend on quit, so persist it after startup instead of waiting for exit. +// This is a no-op when caching is disabled, including normal dev launches. +export const flushCompileCache = Effect.try(() => NodeModule.flushCompileCache()).pipe( + Effect.ignore, +); diff --git a/apps/server/src/config.ts b/apps/server/src/config.ts index bf4877cef692..53279bfe5641 100644 --- a/apps/server/src/config.ts +++ b/apps/server/src/config.ts @@ -82,7 +82,6 @@ export class ServerConfig extends Context.Service< readonly otlpTracesExport: OtelEnvironment.SignalExport; readonly otlpMetricsExport: OtelEnvironment.SignalExport; readonly otlpLogsExport: OtelEnvironment.SignalExport; - readonly otlpServiceName: string; /** * What the standard `OTEL_*` variables asked for. The endpoints above are * already resolved from it; this carries the rest, which T3 Code has no @@ -128,12 +127,13 @@ export const layer = (config: ServerConfig["Service"]) => Layer.succeed(ServerCo * produced them. */ export const otlpResource = (config: ServerConfig["Service"]) => ({ - serviceName: config.otlpServiceName, + serviceName: "t3code-server", ...(config.otelEnvironment.serviceVersion === undefined ? {} : { serviceVersion: config.otelEnvironment.serviceVersion }), attributes: { ...config.otelEnvironment.resourceAttributes, + "service.namespace": "t3code", "service.runtime": "t3-server", "service.mode": config.mode, }, @@ -233,7 +233,6 @@ const makeTest = Effect.fn("ServerConfig.makeTest")(function* ( otlpTracesExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpMetricsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpLogsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, - otlpServiceName: "t3-server", otelEnvironment: OtelEnvironment.none, cwd, baseDir, diff --git a/apps/server/src/desktopUpdate/DesktopAppUpdate.ts b/apps/server/src/desktopUpdate/DesktopAppUpdate.ts index 9c5d88bd7910..dc29375f0a47 100644 --- a/apps/server/src/desktopUpdate/DesktopAppUpdate.ts +++ b/apps/server/src/desktopUpdate/DesktopAppUpdate.ts @@ -106,7 +106,7 @@ export const make = Effect.fn("desktopUpdate.desktopAppUpdate.make")(function* ( ? emitStage(desktopUpdateProgressStage(report.state)).pipe( Effect.as(Option.none()), ) - : Effect.succeed(Option.some(report)), + : Effect.succeedSome(report), ), Stream.filterMap( Option.match({ diff --git a/apps/server/src/device/DeviceHubProxy.test.ts b/apps/server/src/device/DeviceHubProxy.test.ts index 0f039274e207..d80b0e714645 100644 --- a/apps/server/src/device/DeviceHubProxy.test.ts +++ b/apps/server/src/device/DeviceHubProxy.test.ts @@ -111,6 +111,32 @@ describe("device hub proxy", () => { await response.text(); }); + it("reads Android fold state but requires operate scope to change it", async () => { + const path = "http://t3.test/api/device-hub/vendor/serve-emu/api/fold?device=emulator-5554"; + const reader = fixture([AuthOrchestrationReadScope]); + const read = await reader.handler(new Request(path)); + expect(read.status).toBe(200); + await read.text(); + expect(reader.requests).toEqual([ + "http://hub.test/vendor/serve-emu/api/fold?device=emulator-5554", + ]); + const denied = await reader.handler( + new Request(path, { method: "POST", body: '{"posture":"closed"}' }), + ); + expect(denied.status).toBe(403); + expect(reader.requests).toHaveLength(1); + + const operator = fixture([AuthOrchestrationOperateScope]); + const changed = await operator.handler( + new Request(path, { method: "POST", body: '{"posture":"closed"}' }), + ); + expect(changed.status).toBe(200); + await changed.text(); + expect(operator.requests).toEqual([ + "http://hub.test/vendor/serve-emu/api/fold?device=emulator-5554", + ]); + }); + it("never forwards the vendor shell endpoint", async () => { const { handler, requests } = fixture([AuthOrchestrationOperateScope]); expect( @@ -139,3 +165,29 @@ it.each([ expect(await response.text()).not.toContain("private credential diagnostic"); expect(requests).toEqual([]); }); + +it.each([1, 3])( + "forwards fixed Duo display %s through the authenticated read proxy", + async (panel) => { + const { handler, requests } = fixture([AuthOrchestrationReadScope]); + const route = `/vendor/serve-sim/helper/duo/panel/${panel}/stream.avcc`; + const response = await handler( + new Request(`http://t3.test/api/device-hub${route}?wsTicket=secret`), + ); + expect(response.status).toBe(200); + await response.text(); + expect(requests).toEqual([`http://hub.test${route}`]); + }, +); + +it.each(["/panel/2/stream.avcc", "/panel/1/webrtc/offer", "/panel/3/exec"])( + "rejects unsupported Duo route %s", + async (route) => { + const { handler, requests } = fixture([AuthOrchestrationReadScope]); + const response = await handler( + new Request(`http://t3.test/api/device-hub/vendor/serve-sim/helper/duo${route}`), + ); + expect(response.status).toBe(404); + expect(requests).toEqual([]); + }, +); diff --git a/apps/server/src/device/DeviceHubProxy.ts b/apps/server/src/device/DeviceHubProxy.ts index dd048480b3dd..706137796654 100644 --- a/apps/server/src/device/DeviceHubProxy.ts +++ b/apps/server/src/device/DeviceHubProxy.ts @@ -42,8 +42,9 @@ const ALLOWED_PATHS: ReadonlyArray = [ /^\/vendor\/serve-sim\/api\/screenshot$/, /^\/vendor\/serve-sim\/api\/event-log(\/events)?$/, /^\/vendor\/serve-sim\/helper\/[^/]+\/(stream\.mjpeg|stream\.avcc|config|health|ax|foreground)$/, + /^\/vendor\/serve-sim\/helper\/[^/]+\/panel\/(1|3)\/stream\.avcc$/, /^\/vendor\/serve-sim\/appstate$/, - /^\/vendor\/serve-emu\/api\/(devices|screenshot|stream-mode|stream-settings|accessibility)$/, + /^\/vendor\/serve-emu\/api\/(devices|screenshot|stream-mode|stream-settings|accessibility|fold)$/, /^\/vendor\/serve-emu\/health$/, ]; @@ -51,6 +52,7 @@ const ALLOWED_PATHS: ReadonlyArray = [ const MUTABLE_PATHS: ReadonlyArray = [ /^\/vendor\/serve-sim\/api\/screenshot$/, /^\/vendor\/serve-emu\/api\/(screenshot|stream-mode|stream-settings)$/, + /^\/vendor\/serve-emu\/api\/fold$/, ]; const ALLOWED_WS_PATHS: ReadonlyArray = [ @@ -141,7 +143,7 @@ const proxyWebSocket = Effect.fn("DeviceHubProxy.proxyWebSocket")(function* ( pumpFrames(client, writeToUpstream), ); }), - ).pipe(Effect.catchCause(() => Effect.void)); + ).pipe(Effect.ignoreCause); return HttpServerResponse.empty(); }); @@ -203,7 +205,7 @@ const handler = Effect.gen(function* () { } const controlsDevice = (upgrade && hubPath !== "/api/devices/ws") || - (!readOnly && /\/api\/stream-(mode|settings)$/.test(hubPath)); + (!readOnly && /\/api\/(stream-(mode|settings)|fold)$/.test(hubPath)); yield* authenticate(controlsDevice ? AuthOrchestrationOperateScope : AuthOrchestrationReadScope); const devices = yield* DeviceService.DeviceService; const ready = yield* devices.currentReadiness(url.value.searchParams.get("hostId") ?? undefined); diff --git a/apps/server/src/device/DeviceService.ts b/apps/server/src/device/DeviceService.ts index 2435fbca34ab..dcb81d95eb99 100644 --- a/apps/server/src/device/DeviceService.ts +++ b/apps/server/src/device/DeviceService.ts @@ -906,13 +906,11 @@ export const makeWithHosts = Effect.fn("DeviceService.makeWithHosts")(function* lifecycleLock.withPermit( Effect.gen(function* () { if (!installTool) - return yield* Effect.fail( - new DeviceOperationError({ - operation: "update device tool", - reason: "request_failed", - cause: new Error("Tool installation is unavailable in this device service."), - }), - ); + return yield* new DeviceOperationError({ + operation: "update device tool", + reason: "request_failed", + cause: new Error("Tool installation is unavailable in this device service."), + }); yield* installTool(tool); return yield* inspect; }), diff --git a/apps/server/src/device/DeviceToolchain.ts b/apps/server/src/device/DeviceToolchain.ts index e8e7d5cae46a..f30960ed2212 100644 --- a/apps/server/src/device/DeviceToolchain.ts +++ b/apps/server/src/device/DeviceToolchain.ts @@ -26,7 +26,7 @@ import * as Semaphore from "effect/Semaphore"; import * as ProcessRunner from "../processRunner.ts"; const DEVICE_HUB_PACKAGE = "expo-device-hub"; -export const DEVICE_HUB_VERSION = "0.10.1"; +export const DEVICE_HUB_VERSION = "0.12.0"; const AGENT_DEVICE_PACKAGE = "agent-device"; export const AGENT_DEVICE_VERSION = "0.21.12"; diff --git a/apps/server/src/device/LocalDeviceHost.ts b/apps/server/src/device/LocalDeviceHost.ts index e704d28bbc34..5d0ba5176a1e 100644 --- a/apps/server/src/device/LocalDeviceHost.ts +++ b/apps/server/src/device/LocalDeviceHost.ts @@ -326,7 +326,7 @@ export const make = Effect.fn("LocalDeviceHost.make")(function* () { } } yield* fs.remove(hubStatePath(), { force: true }).pipe(Effect.ignore); - }).pipe(Effect.catchCause(() => Effect.void)); + }).pipe(Effect.ignoreCause); const recordHub = (hub: HubProcess, hubTool: DeviceToolPaths) => encodeHubStateFile({ @@ -422,7 +422,7 @@ export const make = Effect.fn("LocalDeviceHost.make")(function* () { Stream.runForEach((line) => Effect.logDebug("Device hub output", { pid: Number(hub.child.pid), output: line }), ), - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, ); /** diff --git a/apps/server/src/device/deviceToolMaintenance.ts b/apps/server/src/device/deviceToolMaintenance.ts index 43cc64e8faca..0d7d61a18d59 100644 --- a/apps/server/src/device/deviceToolMaintenance.ts +++ b/apps/server/src/device/deviceToolMaintenance.ts @@ -128,9 +128,12 @@ const runMaintenance = Effect.fn("DeviceToolchain.maintenance")(function* ( ], }); if (result.code !== 0) - return yield* Effect.fail( - new DeviceToolMaintenanceError({ operation, tool, exitCode: result.code, cause: result }), - ); + return yield* new DeviceToolMaintenanceError({ + operation, + tool, + exitCode: result.code, + cause: result, + }); }); export const pruneLocalDeviceTools = Effect.fn("DeviceToolchain.prune")(function* ( diff --git a/apps/server/src/diagnostics/TraceDiagnostics.ts b/apps/server/src/diagnostics/TraceDiagnostics.ts index 58b08ea8b572..ca5552058f3f 100644 --- a/apps/server/src/diagnostics/TraceDiagnostics.ts +++ b/apps/server/src/diagnostics/TraceDiagnostics.ts @@ -420,8 +420,9 @@ export const make = Effect.gen(function* () { const readAt = options.readAt ?? (yield* DateTime.now); const slowSpanThresholdMs = options.slowSpanThresholdMs ?? DEFAULT_SLOW_SPAN_THRESHOLD_MS; const paths = toRotatedTracePaths(options.traceFilePath, options.maxFiles); - const results = yield* Effect.all( - paths.map((path) => + const results = yield* Effect.forEach( + paths, + (path) => readTraceFile(fileSystem, path).pipe( Effect.tapError((cause) => Effect.logWarning("Failed to read local trace file.").pipe( @@ -434,7 +435,6 @@ export const make = Effect.gen(function* () { ), Effect.result, ), - ), { concurrency: 1, }, diff --git a/apps/server/src/environment/ServerEnvironment.test.ts b/apps/server/src/environment/ServerEnvironment.test.ts index 07231c473c55..ad09b890bfc1 100644 --- a/apps/server/src/environment/ServerEnvironment.test.ts +++ b/apps/server/src/environment/ServerEnvironment.test.ts @@ -6,7 +6,6 @@ import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import * as PlatformError from "effect/PlatformError"; import * as Schema from "effect/Schema"; @@ -34,7 +33,7 @@ const makeServerEnvironmentLayer = (baseDir: string) => const emptySecretStoreLayer = Layer.succeed( ServerSecretStore.ServerSecretStore, ServerSecretStore.ServerSecretStore.of({ - get: () => Effect.succeed(Option.none()), + get: () => Effect.succeedNone, set: () => Effect.void, create: () => Effect.void, getOrCreateRandom: () => Effect.succeed(new Uint8Array()), @@ -59,7 +58,6 @@ const makeServerConfig = Effect.fn(function* (baseDir: string) { otlpTracesExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpMetricsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpLogsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, - otlpServiceName: "t3-server", otelEnvironment: OtelEnvironment.none, cwd: process.cwd(), baseDir, diff --git a/apps/server/src/environment/ServerEnvironment.ts b/apps/server/src/environment/ServerEnvironment.ts index a36c0a03b6f2..d58c014d86e8 100644 --- a/apps/server/src/environment/ServerEnvironment.ts +++ b/apps/server/src/environment/ServerEnvironment.ts @@ -127,13 +127,12 @@ const makeIdentity = Effect.gen(function* () { }); yield* fileSystem.writeFileString(tempPath, `${value}\n`); // Publish the completed file without replacing an ID created by another process. - yield* fileSystem - .link(tempPath, destinationPath) - .pipe( - Effect.catch((cause) => - cause.reason._tag === "AlreadyExists" ? Effect.void : Effect.fail(cause), - ), - ); + yield* fileSystem.link(tempPath, destinationPath).pipe( + Effect.catchIf( + (cause) => cause.reason._tag === "AlreadyExists", + () => Effect.void, + ), + ); if (mode === "recover") { // Keep the recovery ID so delayed initializers also publish the same winner. yield* fileSystem.remove(tempPath); @@ -236,6 +235,7 @@ export const make = Effect.gen(function* () { threadPinning: true, threadPinReorder: true, threadActiveReorder: true, + threadAutoSettleOptOut: true, threadTitleRegeneration: true, threadPullRequests: true, pullRequestStackActions: true, diff --git a/apps/server/src/environment/ServerEnvironmentLabel.ts b/apps/server/src/environment/ServerEnvironmentLabel.ts index 1d944492331a..4a5d0a009b9a 100644 --- a/apps/server/src/environment/ServerEnvironmentLabel.ts +++ b/apps/server/src/environment/ServerEnvironmentLabel.ts @@ -128,7 +128,7 @@ const runFriendlyLabelCommand = Effect.fn("runFriendlyLabelCommand")(function* ( cause, }), ), - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ ServerEnvironmentLabelCommandError: (error) => Effect.logDebug(error.message).pipe( diff --git a/apps/server/src/environment/ServerEnvironmentMachine.ts b/apps/server/src/environment/ServerEnvironmentMachine.ts index 9d11a1ef59fd..e9f5e51af600 100644 --- a/apps/server/src/environment/ServerEnvironmentMachine.ts +++ b/apps/server/src/environment/ServerEnvironmentMachine.ts @@ -107,7 +107,7 @@ const readOptionalFile = Effect.fn("readOptionalFile")(function* (path: string) const fileSystem = yield* FileSystem.FileSystem; return yield* fileSystem.readFileString(path).pipe( Effect.map(normalize), - Effect.catch(() => Effect.succeed(null)), + Effect.orElseSucceed(() => null), ); }); @@ -125,7 +125,7 @@ const runProbe = Effect.fn("runMachineProbe")(function* (input: { }) .pipe( Effect.map((result) => (result.code === 0 ? normalize(result.stdout) : null)), - Effect.catch(() => Effect.succeed(null)), + Effect.orElseSucceed(() => null), ); }); diff --git a/apps/server/src/git/GitManager.ts b/apps/server/src/git/GitManager.ts index 43d674662d61..f57afeb2a419 100644 --- a/apps/server/src/git/GitManager.ts +++ b/apps/server/src/git/GitManager.ts @@ -2097,7 +2097,7 @@ export const make = Effect.gen(function* () { title: generated.title, bodyFile, }) - .pipe(Effect.ensuring(fileSystem.remove(bodyFile).pipe(Effect.catch(() => Effect.void)))); + .pipe(Effect.ensuring(fileSystem.remove(bodyFile).pipe(Effect.ignore))); const created = yield* findOpenPr(cwd, headContext); if (!created) { diff --git a/apps/server/src/git/linkCreatedPullRequest.test.ts b/apps/server/src/git/linkCreatedPullRequest.test.ts index 2c33d82792bd..2e8be05febe7 100644 --- a/apps/server/src/git/linkCreatedPullRequest.test.ts +++ b/apps/server/src/git/linkCreatedPullRequest.test.ts @@ -83,7 +83,7 @@ const makeDependencies = ( Layer.mergeAll( Layer.mock(ProjectionSnapshotQuery)({ getThreadShellById: () => Effect.succeed(Option.fromNullishOr(threadShell)), - getProjectShellById: () => Effect.succeed(Option.some(project)), + getProjectShellById: () => Effect.succeedSome(project), }), Layer.mock(OrchestrationEngineService)({ readEvents: () => Stream.empty, diff --git a/apps/server/src/keybindings.test.ts b/apps/server/src/keybindings.test.ts index ec7070809435..fb256f25500a 100644 --- a/apps/server/src/keybindings.test.ts +++ b/apps/server/src/keybindings.test.ts @@ -567,13 +567,13 @@ it.layer(NodeServices.layer)("keybindings", (it) => { ); yield* Effect.gen(function* () { const keybindings = yield* Keybindings.Keybindings; - yield* Effect.all( - commands.map((command, index) => + yield* Effect.forEach( + commands, + (command, index) => keybindings.upsertKeybindingRule({ key: `mod+${String.fromCharCode(97 + index)}`, command, }), - ), { concurrency: "unbounded", discard: true }, ); }); diff --git a/apps/server/src/mcp/McpHttpServer.test.ts b/apps/server/src/mcp/McpHttpServer.test.ts index 15518e47a497..5619f19b2549 100644 --- a/apps/server/src/mcp/McpHttpServer.test.ts +++ b/apps/server/src/mcp/McpHttpServer.test.ts @@ -6,7 +6,6 @@ import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; -import * as Option from "effect/Option"; import * as Path from "effect/Path"; import * as Schema from "effect/Schema"; import * as Stream from "effect/Stream"; @@ -56,7 +55,7 @@ const PullRequestsTestLayer = McpHttpServer.PullRequestsToolkitRegistrationLive. Layer.provide( Layer.mergeAll( Layer.mock(ProjectionSnapshotQuery)({ - getThreadShellById: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, }), Layer.mock(OrchestrationEngineService)({}), NodeServices.layer, @@ -162,21 +161,73 @@ it.effect.each([{}, { includeImage: false }])( Effect.provideService(McpSchema.McpServerClient, client), ); + const message = "Preview automation snapshot failed on client mcp-failure-client."; expect(snapshot.isError).toBe(true); expect(snapshot.content).toEqual([ - { type: "text", text: "Preview snapshot failed: PreviewAutomationExecutionError." }, + { type: "text", text: `Preview snapshot failed: ${message}` }, ]); expect(snapshot.structuredContent).toEqual({ error: { _tag: "PreviewAutomationExecutionError", operation: "snapshot", failureCount: 1, + message, }, }); }), ).pipe(Effect.provide(TestLayer)), ); +it.effect.each([ + { args: {}, advice: "No active preview tab was found for snapshot. Call preview_open first." }, + { + args: { tabId: alternateTabId }, + advice: `Preview tab ${alternateTabId} was not found for snapshot. Omit tabId to use the current tab, or call preview_open.`, + }, +])("tells the agent to open a tab when the snapshot has none $args", ({ args, advice }) => + Effect.scoped( + Effect.gen(function* () { + const broker = yield* PreviewAutomationBroker.PreviewAutomationBroker; + const connected = yield* Deferred.make(); + const events = yield* broker.connect({ clientId: "mcp-no-tab-client", environmentId }); + yield* Stream.runForEach(events, (event) => + event.type === "connected" + ? Deferred.succeed(connected, undefined) + : broker.respond({ + clientId: "mcp-no-tab-client", + connectionId: event.connectionId, + requestId: event.request.requestId, + ok: false, + error: { _tag: "PreviewAutomationTabNotFoundError", message: "no tab" }, + }), + ).pipe(Effect.forkScoped); + yield* Deferred.await(connected); + + const snapshot = yield* callSnapshot(args); + + expect(snapshot.isError).toBe(true); + expect(snapshot.content).toEqual([ + { type: "text", text: `Preview snapshot failed: ${advice}` }, + ]); + }), + ).pipe(Effect.provide(TestLayer)), +); + +it.effect("tells the agent how to fall back when no desktop app can run the snapshot", () => + Effect.gen(function* () { + const snapshot = yield* callSnapshot({}); + + expect(snapshot.isError).toBe(true); + const [text] = snapshot.content; + expect(text?.type === "text" ? text.text : "").toContain( + "use a headless browser from the shell", + ); + expect(snapshot.structuredContent).toMatchObject({ + error: { _tag: "PreviewAutomationNoAvailableHostError" }, + }); + }).pipe(Effect.provide(TestLayer)), +); + it.effect.each([ { mode: "default", input: {}, images: true }, { mode: "explicit image", input: { includeImage: true }, images: true }, @@ -249,7 +300,10 @@ it.effect.each([ const metadata = { ...page, title: `Snapshot ${call}`, screenshot }; const { accessibilityTree: _tree, ...boundedMetadata } = metadata; expect(snapshot.isError).toBe(false); - expect(snapshot.structuredContent).toEqual(metadata); + expect(snapshot.structuredContent).toEqual({ + ...boundedMetadata, + omitted: ["accessibilityTree (use interactiveElements locators or preview_evaluate)"], + }); const [identity, text, ...rest] = snapshot.content; expect(identity?.type === "text" ? decodeJsonText(identity.text) : null).toEqual({ url: page.url, @@ -288,7 +342,8 @@ it.effect.each([ "text", "image", ]); - expect(nextDefault.structuredContent).toEqual({ ...page, title: "Snapshot 7", screenshot }); + expect(nextDefault.structuredContent).toMatchObject({ title: "Snapshot 7", screenshot }); + expect(nextDefault.structuredContent).not.toHaveProperty("accessibilityTree"); expect(requests).toBe(7); }), ).pipe(Effect.provide(TestLayer)), @@ -342,6 +397,15 @@ it.effect("saves the snapshot PNG on request and reports its path", () => const unsaved = yield* callSnapshot({}); expect(unsaved.structuredContent).not.toHaveProperty("screenshotPath"); + + // A save without the image skips the page dump. + const pathOnly = yield* callSnapshot({ save: true, includeImage: false }); + const saved = pathOnly.structuredContent as { readonly screenshotPath: string }; + expect(saved).toEqual({ url: snapshotResult.url, screenshotPath: expect.any(String) }); + expect(Buffer.from(yield* fileSystem.readFile(saved.screenshotPath)).toString()).toBe("png"); + const [only, ...others] = pathOnly.content; + expect(others).toEqual([]); + expect(only?.type === "text" ? decodeJsonText(only.text) : null).toEqual(saved); }), ).pipe(Effect.provide(TestLayer)), ); @@ -461,9 +525,10 @@ it.effect("keeps the snapshot text under the agent's output ceiling", () => expect(parsed.consoleEntries[0]?.text).toBe("entry 60"); expect(notice?.type === "text" ? notice.text : "").toContain("accessibilityTree"); expect(notice?.type === "text" ? notice.text : "").toContain("60 older console entries"); - // The structured result is untouched; only the text the agent reads is bounded. - expect(snapshot.structuredContent).toMatchObject({ - accessibilityTree: oversized.accessibilityTree, + // Claude Code shows the model structuredContent instead of the text, so it is bounded too. + expect(snapshot.structuredContent).toEqual({ + ...parsed, + omitted: expect.arrayContaining(["60 older console entries"]), }); }), ).pipe(Effect.provide(TestLayer)), @@ -501,6 +566,45 @@ it.effect("bounds the snapshot text even when nothing but logs and the title are ).pipe(Effect.provide(TestLayer)), ); +it.effect("bounds page text made of wide characters before dropping locators", () => + Effect.scoped( + Effect.gen(function* () { + // The character caps alone leave 8,000 three-byte characters, about 24 KB. + yield* serveSnapshots("mcp-wide-text-client", { + ...snapshotResult, + visibleText: "界".repeat(9_000), + interactiveElements: Array.from({ length: 20 }, (_, i) => ({ + tag: "button", + role: "button", + name: `Button ${i}`, + selector: `#button-${i}`, + x: 0, + y: 0, + width: 10, + height: 10, + })), + }); + + const snapshot = yield* callSnapshot({ includeImage: false }); + + const [, text, notice] = snapshot.content; + const body = text?.type === "text" ? text.text : ""; + expect(Buffer.byteLength(body, "utf8")).toBeLessThanOrEqual( + McpHttpServer.MAX_SNAPSHOT_TEXT_BYTES, + ); + const parsed = decodeJsonText(body) as { + readonly visibleText: string; + readonly interactiveElements: ReadonlyArray; + }; + expect(parsed.visibleText).toMatch(/^界+…$/); + expect(parsed.interactiveElements).toHaveLength(20); + expect(notice?.type === "text" ? notice.text : "").toContain( + "visibleText after 4000 characters", + ); + }), + ).pipe(Effect.provide(TestLayer)), +); + it.effect("sheds log entries before locators when every list is full", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/mcp/McpHttpServer.ts b/apps/server/src/mcp/McpHttpServer.ts index f9f1a076c85e..ff26dd8b5d68 100644 --- a/apps/server/src/mcp/McpHttpServer.ts +++ b/apps/server/src/mcp/McpHttpServer.ts @@ -13,6 +13,7 @@ import * as Stream from "effect/Stream"; import type * as Types from "effect/Types"; import { McpProtocol, McpSchema, McpServer, Tool } from "effect/unstable/ai"; import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http"; +import { PreviewAutomationError } from "@t3tools/contracts"; import packageJson from "../../package.json" with { type: "json" }; import * as ServerConfig from "../config.ts"; @@ -116,12 +117,15 @@ const McpAuthMiddlewareLive = HttpRouter.middleware<{ }>()(makeMcpAuthMiddleware).layer; /** - * Claude Code drops every MCP result above 25k tokens (~100 KB of text) and - * hands the agent a truncation notice instead, so a snapshot that carries the - * full accessibility tree and 20 KB of page text loses its locators too. Keep - * the text under that ceiling and tell the agent what was cut. + * Claude Code moves an MCP result above its output limit to a file and hands + * the agent a notice instead, so a snapshot that carries the full + * accessibility tree and page text loses its locators too. Claude Code also + * shows the model `structuredContent` in place of the text blocks when a + * result has both, so both carry the same bounded snapshot. Keep it near + * 20 KB and tell the agent what was cut. The short `omitted` notes may go a + * little over; the provider limit is far above this. */ -export const MAX_SNAPSHOT_TEXT_BYTES = 60_000; +export const MAX_SNAPSHOT_TEXT_BYTES = 20_000; const MAX_SNAPSHOT_VISIBLE_TEXT_CHARS = 8_000; const MAX_SNAPSHOT_ELEMENT_NAME_CHARS = 200; const MAX_SNAPSHOT_LOG_ENTRIES = 40; @@ -166,12 +170,11 @@ type SnapshotMetadata = { /** * Drops the accessibility tree, shortens page text, element names, identifiers, * and log strings, keeps only the newest log entries, and finally sheds - * interactive elements until the JSON fits. Returns the text plus notes on - * what is missing so the agent can reach for preview_evaluate. + * interactive elements until the JSON fits. Returns the bounded value, its + * text, and notes on what is missing so the agent can reach for + * preview_evaluate. */ -const boundSnapshotMetadata = ( - metadata: SnapshotMetadata, -): { readonly text: string; readonly omitted: ReadonlyArray } => { +const boundSnapshotMetadata = (metadata: SnapshotMetadata) => { const omitted: Array = []; const { accessibilityTree, ...withoutTree } = metadata; if (accessibilityTree !== undefined) { @@ -200,16 +203,10 @@ const boundSnapshotMetadata = ( ) { omitted.push(`element names longer than ${MAX_SNAPSHOT_ELEMENT_NAME_CHARS} characters`); } - if (metadata.visibleText.length > MAX_SNAPSHOT_VISIBLE_TEXT_CHARS) { - omitted.push( - `visibleText after ${MAX_SNAPSHOT_VISIBLE_TEXT_CHARS} characters (use preview_evaluate for more)`, - ); - } const bounded = { ...withoutTree, url: cutText(metadata.url, MAX_SNAPSHOT_IDENTIFIER_CHARS), title: cutText(metadata.title, MAX_SNAPSHOT_IDENTIFIER_CHARS), - visibleText: cutText(metadata.visibleText, MAX_SNAPSHOT_VISIBLE_TEXT_CHARS), interactiveElements: metadata.interactiveElements.map((element) => ({ ...element, name: cutText(element.name, MAX_SNAPSHOT_ELEMENT_NAME_CHARS), @@ -220,9 +217,10 @@ const boundSnapshotMetadata = ( }; // Per-field caps do not sum below the ceiling: three log arrays of 40 capped - // entries alone can pass 60 KB. Shed the least useful lists first, halving - // one list per round, until the JSON fits. With every list empty the rest - // is bounded by the identifier and visibleText caps, so this terminates. + // entries alone can pass 60 KB, and the caps count characters, not bytes. + // Halve one thing per round until the JSON fits: logs first, then page + // text, then the locators. The identifier caps bound the rest, so this + // terminates. const shedOrder = [ "actionTimeline", "networkEntries", @@ -241,31 +239,51 @@ const boundSnapshotMetadata = ( networkEntries: 0, actionTimeline: 0, }; - let text = encodeJsonText({ ...bounded, ...lists }); + let visibleTextChars = Math.min(metadata.visibleText.length, MAX_SNAPSHOT_VISIBLE_TEXT_CHARS); + const value = () => ({ + ...bounded, + visibleText: cutText(metadata.visibleText, visibleTextChars), + ...lists, + }); + let text = encodeJsonText(value()); while (utf8Length(text) > MAX_SNAPSHOT_TEXT_BYTES) { // Elements carry the locators, so they go last; logs shed newest-last. const key = shedOrder.find( (candidate) => candidate !== "interactiveElements" && lists[candidate].length > 0, - ) ?? (lists.interactiveElements.length > 0 ? "interactiveElements" : undefined); + ) ?? + (visibleTextChars > 0 + ? "visibleText" + : lists.interactiveElements.length > 0 + ? "interactiveElements" + : undefined); if (key === undefined) break; - const keep = Math.floor(lists[key].length / 2); - dropped[key] += lists[key].length - keep; - // slice(-0) keeps everything, so spell out the empty case. - lists[key] = - keep === 0 - ? [] - : key === "interactiveElements" - ? lists[key].slice(0, keep) - : lists[key].slice(-keep); - text = encodeJsonText({ ...bounded, ...lists }); + if (key === "visibleText") { + visibleTextChars = Math.floor(visibleTextChars / 2); + } else { + const keep = Math.floor(lists[key].length / 2); + dropped[key] += lists[key].length - keep; + // slice(-0) keeps everything, so spell out the empty case. + lists[key] = + keep === 0 + ? [] + : key === "interactiveElements" + ? lists[key].slice(0, keep) + : lists[key].slice(-keep); + } + text = encodeJsonText(value()); + } + if (visibleTextChars < metadata.visibleText.length) { + omitted.push( + `visibleText after ${visibleTextChars} characters (use preview_evaluate for more)`, + ); } for (const key of shedOrder) { if (dropped[key] > 0) { omitted.push(`${dropped[key]} of ${bounded[key].length} ${key}`); } } - return { text, omitted }; + return { value: value(), text, omitted }; }; export class PreviewScreenshotSaveError extends Schema.TaggedError()( @@ -313,6 +331,8 @@ const saveScreenshot = Effect.fn("McpHttpServer.saveScreenshot")(function* ( return screenshotPath; }); +const isPreviewAutomationError = Schema.is(PreviewAutomationError); + const previewSnapshotFailure = (cause: Cause.Cause) => { if (Cause.hasInterrupts(cause) || cause.reasons.some(Cause.isDieReason)) { return Effect.failCause(cause).pipe(Effect.orDie); @@ -326,6 +346,9 @@ const previewSnapshotFailure = (cause: Cause.Cause) => { typeof firstFailure._tag === "string" ? firstFailure._tag : "PreviewSnapshotError"; + // Preview errors build their message on the server, never from page output, + // and it tells the agent what to do next, such as falling back to a shell browser. + const message = isPreviewAutomationError(firstFailure) ? firstFailure.message : undefined; const result = new McpSchema.CallToolResult({ isError: true, structuredContent: { @@ -333,10 +356,11 @@ const previewSnapshotFailure = (cause: Cause.Cause) => { _tag: errorTag, operation: "snapshot", failureCount: failures.length, + ...(message === undefined ? {} : { message }), }, }, - // Agents usually see only the text content, so name the tag there too. - content: [{ type: "text", text: `Preview snapshot failed: ${errorTag}.` }], + // Some clients show only the text content and others only structuredContent, so both carry it. + content: [{ type: "text", text: `Preview snapshot failed: ${message ?? `${errorTag}.`}` }], }); return Effect.logWarning("preview snapshot failed", { operation: "snapshot", @@ -398,6 +422,18 @@ const registerPreviewSnapshot = Effect.fn("McpHttpServer.registerPreviewSnapshot const png = new Uint8Array(Buffer.from(screenshot.data, "base64")); const screenshotPath = payload?.save === true ? yield* saveScreenshot(snapshot.url, png) : undefined; + if (screenshotPath !== undefined && payload?.includeImage === false) { + // The agent only wants a file to show the user. The url keeps the site icon on the tool row. + const saved = { + url: cutText(snapshot.url, MAX_SNAPSHOT_IDENTIFIER_CHARS), + screenshotPath, + }; + return new McpSchema.CallToolResult({ + isError: false, + structuredContent: saved, + content: [{ type: "text", text: encodeJsonText(saved) }], + }); + } const metadata = { ...page, screenshot: { @@ -410,7 +446,10 @@ const registerPreviewSnapshot = Effect.fn("McpHttpServer.registerPreviewSnapshot const bounded = boundSnapshotMetadata(metadata); return new McpSchema.CallToolResult({ isError: false, - structuredContent: metadata, + structuredContent: + bounded.omitted.length === 0 + ? bounded.value + : { ...bounded.value, omitted: bounded.omitted }, content: [ // Keep the page identity readable even if a provider truncates the snapshot. { diff --git a/apps/server/src/mcp/McpInvocationContext.test.ts b/apps/server/src/mcp/McpInvocationContext.test.ts index 123944206e37..4314c82e6ddc 100644 --- a/apps/server/src/mcp/McpInvocationContext.test.ts +++ b/apps/server/src/mcp/McpInvocationContext.test.ts @@ -34,7 +34,8 @@ it.effect("reports the scoped credential context when preview capability is unav providerSessionId: invocation.providerSessionId, providerInstanceId: invocation.providerInstanceId, }); - expect(error.message).toBe("MCP credential does not grant the preview capability."); + expect(error.message).toContain("MCP credential does not grant the preview capability"); + expect(error.message).toContain("use a headless browser from the shell"); }); }); diff --git a/apps/server/src/mcp/McpInvocationContext.ts b/apps/server/src/mcp/McpInvocationContext.ts index fba4379d0bd4..06b0b7747c7b 100644 --- a/apps/server/src/mcp/McpInvocationContext.ts +++ b/apps/server/src/mcp/McpInvocationContext.ts @@ -47,9 +47,11 @@ const missingCapability = ( export const requireMcpCapability = ( capability: C, ): Effect.Effect, McpInvocationContext> => - Effect.flatMap(McpInvocationContext, (invocation) => - invocation.capabilities.has(capability) - ? Effect.succeed(invocation) - : // The conditional type narrows what the literal argument decided at runtime. - Effect.fail(missingCapability(invocation, capability) as McpCapabilityError), - ).pipe(Effect.withSpan("mcp.requireCapability")); + McpInvocationContext.pipe( + Effect.filterOrFail( + (invocation) => invocation.capabilities.has(capability), + // The conditional type narrows what the literal argument decided at runtime. + (invocation) => missingCapability(invocation, capability) as McpCapabilityError, + ), + Effect.withSpan("mcp.requireCapability"), + ); diff --git a/apps/server/src/mcp/McpSessionRegistry.ts b/apps/server/src/mcp/McpSessionRegistry.ts index fa0a17d826f9..50441df96a63 100644 --- a/apps/server/src/mcp/McpSessionRegistry.ts +++ b/apps/server/src/mcp/McpSessionRegistry.ts @@ -230,7 +230,7 @@ export const issueActiveMcpCredential = ( ? activeMcpSessionRegistry .revokeThread(request.threadId) .pipe(Effect.andThen(activeMcpSessionRegistry.issue(request))) - : Effect.sync((): McpIssuedCredential | undefined => undefined); + : Effect.undefined; /** * Refreshes the liveness of a thread's MCP credential. Called on every provider diff --git a/apps/server/src/mcp/toolkits/preview/handlers.ts b/apps/server/src/mcp/toolkits/preview/handlers.ts index caa4cbd157cf..b70e68862683 100644 --- a/apps/server/src/mcp/toolkits/preview/handlers.ts +++ b/apps/server/src/mcp/toolkits/preview/handlers.ts @@ -87,7 +87,7 @@ const invoke = Effect.fn("PreviewToolkit.invoke")(function* ( updateCurrentTab: false, ...(statusTabId === undefined ? {} : { tabId: statusTabId }), }) - .pipe(Effect.catch(() => Effect.succeed(null))); + .pipe(Effect.orElseSucceed(() => null)); return { result, ...(page?.url && /^https?:\/\//i.test(page.url) && page.url.length <= 4096 @@ -175,10 +175,11 @@ export const claimPreviewRecording = Effect.fn("PreviewToolkit.claimRecording")( yield* fileSystem.rename(currentPath, finalPath); }).pipe( // Another stop may already have claimed this exact upload for this thread. - Effect.catch((cause) => - cause._tag !== "PreviewAutomationRecordingTransferError" && cause.reason._tag === "NotFound" - ? validateFile(finalPath) - : Effect.fail(cause), + Effect.catchIf( + (cause) => + cause._tag !== "PreviewAutomationRecordingTransferError" && + cause.reason._tag === "NotFound", + () => validateFile(finalPath), ), Effect.mapError((cause) => new PreviewAutomationRecordingTransferError({ threadId, cause })), ); diff --git a/apps/server/src/mcp/toolkits/preview/tools.ts b/apps/server/src/mcp/toolkits/preview/tools.ts index 3f80e84e9a59..1c790e8b643a 100644 --- a/apps/server/src/mcp/toolkits/preview/tools.ts +++ b/apps/server/src/mcp/toolkits/preview/tools.ts @@ -119,7 +119,7 @@ const PreviewSetAppearanceTool = safeBrowserTool( export const PreviewSnapshotTool = readonlyBrowserTool( Tool.make("preview_snapshot", { description: - "Inspect a page before interacting. Pass tabId to inspect a specific tab; omit it to use this agent session's current tab. Returns page state, semantic elements, diagnostics, action history, and a PNG screenshot. Set includeImage=false for text-only output with the same page metadata. Set save=true to also write the PNG to disk and get screenshotPath back; embed that path in your reply as ![alt](screenshotPath) so the user sees it. This is the only way to show the user a screenshot; the image in the tool result is not saved anywhere.", + "Inspect a page before interacting. Pass tabId to inspect a specific tab; omit it to use this agent session's current tab. Returns page state, semantic elements, diagnostics, action history, and a PNG screenshot. The text is capped near 20 KB and lists what it omitted; use preview_evaluate to read more. Set includeImage=false for text-only output with the same page metadata. Set save=true to also write the PNG to disk and get screenshotPath back; with includeImage=false, save=true returns only the url and screenshotPath. Embed that path in your reply as ![alt](screenshotPath) so the user sees it. This is the only way to show the user a screenshot; the image in the tool result is not saved anywhere.", parameters: Schema.Struct({ ...PreviewAutomationTabTargetInput.fields, includeImage: Schema.optional( @@ -131,7 +131,7 @@ export const PreviewSnapshotTool = readonlyBrowserTool( save: Schema.optional( Schema.Boolean.annotate({ description: - "Write the screenshot PNG to disk and return its absolute path as screenshotPath. Defaults to false.", + "Write the screenshot PNG to disk and return its absolute path as screenshotPath. With includeImage=false, return only the url and screenshotPath. Defaults to false.", }), ), }), diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts index 4cfdccfd8f74..7814a9eec9e6 100644 --- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts +++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts @@ -97,12 +97,12 @@ const make = Effect.gen(function* () { const entryRefreshWorker = yield* makeDrainableWorker((cwd: string) => Effect.sync(() => queuedEntryRefreshes.delete(cwd)).pipe( Effect.andThen(workspaceEntries.refresh(cwd)), - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("failed to refresh checkpoint workspace entries", { - cwd, - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + () => + Effect.logWarning("failed to refresh checkpoint workspace entries", { + cwd, + }), ), ), ); @@ -625,15 +625,14 @@ const make = Effect.gen(function* () { branch: checkedOutBranch, }); }).pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logWarning("failed to follow worktree branch drift", { - threadId: input.threadId, - cause: Cause.pretty(cause), - }); - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("failed to follow worktree branch drift", { + threadId: input.threadId, + cause: Cause.pretty(cause), + }), + ), ); }); @@ -643,12 +642,12 @@ const make = Effect.gen(function* () { const statusRefreshWorker = yield* makeDrainableWorker( (event: Extract) => refreshLocalGitStatusFromTurnCompletion(event).pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("failed to refresh git status after turn completion", { - threadId: event.threadId, - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + () => + Effect.logWarning("failed to refresh git status after turn completion", { + threadId: event.threadId, + }), ), ), ); @@ -749,11 +748,7 @@ const make = Effect.gen(function* () { for (const candidate of paths) { const otherCwd = yield* fileSystem .realPath(candidate) - .pipe( - Effect.catch((error) => - error.reason._tag === "NotFound" ? Effect.succeed(null) : Effect.fail(error), - ), - ); + .pipe(Effect.catchReason("PlatformError", "NotFound", () => Effect.succeed(null))); if (otherCwd === null) continue; const isWithin = (parent: string, child: string) => { const relative = path.relative(parent, child); @@ -791,7 +786,7 @@ const make = Effect.gen(function* () { preferSessionRuntime: true, }).pipe( Effect.catch((error) => - event.payload.restoreFiles === false ? Effect.succeed(undefined) : Effect.fail(error), + event.payload.restoreFiles === false ? Effect.undefined : Effect.fail(error), ), ); @@ -1014,16 +1009,15 @@ const make = Effect.gen(function* () { const processInputSafely = (input: ReactorInput) => processInput(input).pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logWarning("checkpoint reactor failed to process input", { - source: input.source, - eventType: input.event.type, - cause: Cause.pretty(cause), - }); - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("checkpoint reactor failed to process input", { + source: input.source, + eventType: input.event.type, + cause: Cause.pretty(cause), + }), + ), ); const worker = yield* makeDrainableWorker(processInputSafely); diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts index 078750967471..f855771b8f01 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts @@ -446,18 +446,18 @@ describe("OrchestrationEngine", () => { Effect.succeed({ snapshotSequence: projectionSnapshot.snapshotSequence }), getCounts: () => Effect.succeed({ projectCount: 1, threadCount: 1 }), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), - getProjectShellById: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, + getProjectShellById: () => Effect.succeedNone, getProjectShells: () => Effect.succeed([]), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), - getFullThreadDiffContext: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeedNone, + getFullThreadDiffContext: () => Effect.succeedNone, getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, + getThreadDetailById: () => Effect.succeedNone, + getThreadDetailSnapshot: () => Effect.succeedNone, searchThreads: () => Effect.succeed({ matches: [] }), }), ), diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts index fb2fadde5e63..9136d080c1c3 100644 --- a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts +++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts @@ -400,7 +400,7 @@ const makeOrchestrationEngine = Effect.gen(function* () { status: "rejected", error: error.message, }) - .pipe(Effect.catch(() => Effect.void)); + .pipe(Effect.ignore); } } diff --git a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts index 3cc12157f1af..62991daadf0e 100644 --- a/apps/server/src/orchestration/Layers/ProjectionPipeline.ts +++ b/apps/server/src/orchestration/Layers/ProjectionPipeline.ts @@ -634,6 +634,7 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti pinnedAt: null, pinOrderKey: null, activeOrderKey: null, + autoSettleDisabledAt: null, titleRegenerationRequestId: null, titleRegenerationStartedAt: null, latestUserMessageAt: null, @@ -783,6 +784,21 @@ const makeOrchestrationProjectionPipeline = Effect.fn("makeOrchestrationProjecti return; } + case "thread.auto-settle-set": { + const existingRow = yield* projectionThreadRepository.getById({ + threadId: event.payload.threadId, + }); + if (Option.isNone(existingRow)) { + return; + } + yield* projectionThreadRepository.upsert({ + ...existingRow.value, + autoSettleDisabledAt: event.payload.autoSettleDisabledAt, + updatedAt: event.payload.updatedAt, + }); + return; + } + case "thread.pin-reordered": { const existingRow = yield* projectionThreadRepository.getById({ threadId: event.payload.threadId, diff --git a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts index 84c0327a2e7e..d6079d4bcff4 100644 --- a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts +++ b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts @@ -486,6 +486,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { pinnedAt: "2026-02-24T00:00:01.000Z", pinOrderKey: "gm", activeOrderKey: "hq", + autoSettleDisabledAt: null, titleRegeneration: null, titleState: null, deletedAt: null, @@ -613,6 +614,7 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { pinnedAt: "2026-02-24T00:00:01.000Z", pinOrderKey: "gm", activeOrderKey: "hq", + autoSettleDisabledAt: null, titleRegeneration: null, titleState: null, session: { diff --git a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts index 5498ee43d502..236d031d7a62 100644 --- a/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts +++ b/apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts @@ -588,6 +588,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", active_order_key AS "activeOrderKey", + auto_settle_disabled_at AS "autoSettleDisabledAt", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", @@ -630,6 +631,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", active_order_key AS "activeOrderKey", + auto_settle_disabled_at AS "autoSettleDisabledAt", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", @@ -704,6 +706,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", active_order_key AS "activeOrderKey", + auto_settle_disabled_at AS "autoSettleDisabledAt", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", @@ -1270,6 +1273,7 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", active_order_key AS "activeOrderKey", + auto_settle_disabled_at AS "autoSettleDisabledAt", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", @@ -2346,6 +2350,7 @@ pending_approval_requests AS ( pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, activeOrderKey: row.activeOrderKey ?? null, + autoSettleDisabledAt: row.autoSettleDisabledAt ?? null, titleRegeneration: mapTitleRegeneration(row), titleState: row.titleState, deletedAt: row.deletedAt, @@ -2592,6 +2597,7 @@ pending_approval_requests AS ( pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, activeOrderKey: row.activeOrderKey ?? null, + autoSettleDisabledAt: row.autoSettleDisabledAt ?? null, titleRegeneration: mapTitleRegeneration(row), titleState: row.titleState, deletedAt: row.deletedAt, @@ -2749,6 +2755,7 @@ pending_approval_requests AS ( pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, activeOrderKey: row.activeOrderKey ?? null, + autoSettleDisabledAt: row.autoSettleDisabledAt ?? null, titleRegeneration: mapTitleRegeneration(row), titleState: row.titleState, session: sessionByThread.get(row.threadId) ?? null, @@ -2913,6 +2920,7 @@ pending_approval_requests AS ( pinnedAt: row.pinnedAt, pinOrderKey: row.pinOrderKey ?? null, activeOrderKey: row.activeOrderKey ?? null, + autoSettleDisabledAt: row.autoSettleDisabledAt ?? null, titleRegeneration: mapTitleRegeneration(row), titleState: row.titleState, session: sessionByThread.get(row.threadId) ?? null, @@ -3270,6 +3278,7 @@ pending_approval_requests AS ( pinnedAt: threadRow.value.pinnedAt, pinOrderKey: threadRow.value.pinOrderKey ?? null, activeOrderKey: threadRow.value.activeOrderKey ?? null, + autoSettleDisabledAt: threadRow.value.autoSettleDisabledAt ?? null, titleRegeneration: mapTitleRegeneration(threadRow.value), titleState: threadRow.value.titleState, session: Option.isSome(sessionRow) ? mapSessionRow(sessionRow.value) : null, @@ -3572,6 +3581,7 @@ pending_approval_requests AS ( pinnedAt: threadRow.value.pinnedAt, pinOrderKey: threadRow.value.pinOrderKey ?? null, activeOrderKey: threadRow.value.activeOrderKey ?? null, + autoSettleDisabledAt: threadRow.value.autoSettleDisabledAt ?? null, titleRegeneration: mapTitleRegeneration(threadRow.value), titleState: threadRow.value.titleState, deletedAt: null, diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts index c7d9417bc75b..254aec4bde7e 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts @@ -55,6 +55,7 @@ import { import { ProviderAuthService } from "../../provider/Services/ProviderAuthService.ts"; import { makeProviderRegistryLayer } from "../../provider/testUtils/providerRegistryMock.ts"; import { TextGeneration } from "../../textGeneration/TextGeneration.ts"; +import { TerminalManager } from "../../terminal/Manager.ts"; import * as RepositoryIdentityResolver from "../../project/RepositoryIdentityResolver.ts"; import { OrchestrationEngineLive } from "./OrchestrationEngine.ts"; import { OrchestrationProjectionPipelineLive } from "./ProjectionPipeline.ts"; @@ -307,6 +308,7 @@ describe("ProviderCommandReactor", () => { }), ); const pruneWorktrees = vi.fn((_: { readonly cwd: string }) => Effect.void); + const closeIdleTerminals = vi.fn((_: { readonly threadId: string }) => Effect.void); const createWorktree = vi.fn( (input: { readonly refName: string; readonly path: string | null }) => Effect.succeed({ worktree: { path: input.path ?? "", refName: input.refName } }), @@ -490,6 +492,7 @@ describe("ProviderCommandReactor", () => { generateThreadTitle, }), ), + Layer.provideMerge(Layer.mock(TerminalManager)({ closeIdle: closeIdleTerminals })), Layer.provideMerge(ServerSettingsService.layerTest()), Layer.provideMerge(SqlitePersistenceMemory), Layer.provideMerge(ServerConfig.layerTest(process.cwd(), baseDir)), @@ -621,6 +624,7 @@ describe("ProviderCommandReactor", () => { renameBranch, pruneWorktrees, createWorktree, + closeIdleTerminals, refreshStatus, generateBranchName, generateThreadTitle, @@ -4339,6 +4343,77 @@ describe("ProviderCommandReactor", () => { expect(thread?.settledOverride).toBe("settled"); expect(thread?.session?.status).toBe("stopped"); expect(thread?.session?.providerInstanceId).toBe(ProviderInstanceId.make("codex_work")); + expect(harness.closeIdleTerminals).toHaveBeenCalledWith({ + threadId: ThreadId.make("thread-1"), + }); }), ); + + effectIt.effect("closes idle terminals when a thread without a session settles", () => + Effect.gen(function* () { + const harness = yield* Effect.promise(() => createHarness()); + const terminalsClosed = yield* Deferred.make(); + harness.closeIdleTerminals.mockImplementation(() => + Deferred.succeed(terminalsClosed, undefined).pipe(Effect.asVoid), + ); + + yield* harness.engine.dispatch({ + type: "thread.settle", + commandId: CommandId.make("cmd-settle-without-session"), + threadId: ThreadId.make("thread-1"), + }); + yield* Deferred.await(terminalsClosed); + yield* Effect.promise(() => harness.drain()); + + expect(harness.closeIdleTerminals).toHaveBeenCalledWith({ + threadId: ThreadId.make("thread-1"), + }); + expect(harness.stopSession).not.toHaveBeenCalled(); + }), + ); + + effectIt.effect( + "keeps terminals when the thread is un-settled before its settle event runs", + () => + Effect.gen(function* () { + const harness = yield* Effect.promise(() => createHarness()); + const threadId = ThreadId.make("thread-1"); + const firstCloseStarted = yield* Deferred.make(); + const releaseFirstClose = yield* Deferred.make(); + harness.closeIdleTerminals.mockImplementationOnce(() => + Deferred.succeed(firstCloseStarted, undefined).pipe( + Effect.andThen(Deferred.await(releaseFirstClose)), + ), + ); + + yield* harness.engine.dispatch({ + type: "thread.settle", + commandId: CommandId.make("cmd-settle-first"), + threadId, + }); + // The reactor is busy with the first settle while the user changes their mind. + yield* Deferred.await(firstCloseStarted); + yield* harness.engine.dispatch({ + type: "thread.unsettle", + commandId: CommandId.make("cmd-unsettle-first"), + threadId, + reason: "user", + }); + yield* harness.engine.dispatch({ + type: "thread.settle", + commandId: CommandId.make("cmd-settle-second"), + threadId, + }); + yield* harness.engine.dispatch({ + type: "thread.unsettle", + commandId: CommandId.make("cmd-unsettle-second"), + threadId, + reason: "user", + }); + yield* Deferred.succeed(releaseFirstClose, undefined); + yield* Effect.promise(() => harness.drain()); + + expect(harness.closeIdleTerminals).toHaveBeenCalledTimes(1); + }), + ); }); diff --git a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts index bdf4fe8e69d9..f6794135a925 100644 --- a/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts +++ b/apps/server/src/orchestration/Layers/ProviderCommandReactor.ts @@ -65,6 +65,7 @@ import { import { resolveProjectSettings } from "@t3tools/shared/projectSettings"; import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts"; import { GitWorkflowService } from "../../git/GitWorkflowService.ts"; +import * as TerminalManager from "../../terminal/Manager.ts"; const isProviderAdapterProcessError = Schema.is(ProviderAdapterProcessError); const isProviderAdapterRequestError = Schema.is(ProviderAdapterRequestError); const isProviderAdapterValidationError = Schema.is(ProviderAdapterValidationError); @@ -222,6 +223,7 @@ const make = Effect.gen(function* () { const vcsStatusBroadcaster = yield* VcsStatusBroadcaster; const textGeneration = yield* TextGeneration; const serverSettingsService = yield* ServerSettingsService; + const terminalManager = yield* TerminalManager.TerminalManager; /** Environment settings with the thread's project overrides applied. */ const projectSettingsForThread = Effect.fnUntraced(function* (threadId: ThreadId) { const settings = yield* serverSettingsService.getSettings; @@ -511,14 +513,14 @@ const make = Effect.gen(function* () { Effect.andThen( gitWorkflow.createWorktree({ cwd, refName: branch, path: worktreePath }, { submodules }), ), - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("provider command reactor failed to recreate worktree", { - threadId: thread.id, - worktreePath, - cause: Cause.pretty(cause), - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("provider command reactor failed to recreate worktree", { + threadId: thread.id, + worktreePath, + cause: Cause.pretty(cause), + }), ), ); }); @@ -1154,15 +1156,14 @@ const make = Effect.gen(function* () { return; } const result = yield* regenerateThreadTitle(event, requestId).pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logWarning("provider command reactor failed to regenerate thread title", { - threadId: event.payload.threadId, - cause: Cause.pretty(cause), - }).pipe(Effect.as({ _tag: "Completed", title: undefined } as const)); - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("provider command reactor failed to regenerate thread title", { + threadId: event.payload.threadId, + cause: Cause.pretty(cause), + }).pipe(Effect.as({ _tag: "Completed", title: undefined } as const)), + ), ); if (result._tag === "Superseded") { return; @@ -1174,34 +1175,26 @@ const make = Effect.gen(function* () { ...(result.title !== undefined ? { title: result.title } : {}), }; yield* dispatchThreadTitleRegenerationCompletion(completion).pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logWarning( - "provider command reactor retrying title regeneration completion", - { + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("provider command reactor retrying title regeneration completion", { threadId: event.payload.threadId, cause: Cause.pretty(cause), - }, - ).pipe(Effect.andThen(dispatchThreadTitleRegenerationCompletion(completion))); - }), + }).pipe(Effect.andThen(dispatchThreadTitleRegenerationCompletion(completion))), + ), ); }, (effect, event) => effect.pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logWarning( - "provider command reactor failed to complete title regeneration", - { + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("provider command reactor failed to complete title regeneration", { threadId: event.payload.threadId, cause: Cause.pretty(cause), - }, - ); - }), + }), + ), ), ); const threadTitleRegenerationWorker = yield* makeDrainableWorker( @@ -1496,7 +1489,7 @@ const make = Effect.gen(function* () { interactionMode: event.payload.interactionMode, createdAt: event.payload.createdAt, }).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchCause((cause) => handleTurnStartFailure(cause).pipe(Effect.as(Option.none()))), ); @@ -1831,11 +1824,14 @@ const make = Effect.gen(function* () { return; case "thread.settled": { const thread = yield* projectionSnapshotQuery.getThreadShellById(event.payload.threadId); - if ( - Option.isNone(thread) || - thread.value.session == null || - thread.value.session.status === "stopped" - ) { + // A thread re-engaged before this event ran keeps its shells and session. + if (Option.isNone(thread) || thread.value.settledOverride !== "settled") { + return; + } + // Idle shells close so they stop holding the worktree. A terminal that + // runs a command (a dev server, an editor) stays for the user to close. + yield* terminalManager.closeIdle({ threadId: event.payload.threadId }); + if (thread.value.session == null || thread.value.session.status === "stopped") { return; } yield* orchestrationEngine.dispatch({ diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts index d61739f72c21..9b5b56309749 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts @@ -5174,4 +5174,59 @@ describe("splitBufferedAssistantText", () => { rest: "```\n- one\n- two\n", }); }); + + it("holds a heading until the block under it is done", () => { + expect(splitBufferedAssistantText("intro\n\n## Setup\n\nInstall it")).toEqual({ + ready: "intro\n\n", + rest: "## Setup\n\nInstall it", + }); + expect( + splitBufferedAssistantText("intro\n\n# Plan\n\n## Setup\n\nInstall it.\n\nNext"), + ).toEqual({ + ready: "intro\n\n# Plan\n\n## Setup\n\nInstall it.\n\n", + rest: "Next", + }); + }); + + it("delivers the paragraph above a heading with no blank line between them", () => { + expect(splitBufferedAssistantText("para\n## Setup\n\nInstall")).toEqual({ + ready: "para\n", + rest: "## Setup\n\nInstall", + }); + // A bold line there continues the paragraph, so both stay buffered. + expect(splitBufferedAssistantText("para\n**Setup**\n\nInstall")).toEqual({ + ready: "", + rest: "para\n**Setup**\n\nInstall", + }); + }); + + it("holds a line of only bold text like a heading", () => { + expect(splitBufferedAssistantText("**Risk by area:**\n\n| a |\n|---|\n")).toEqual({ + ready: "", + rest: "**Risk by area:**\n\n| a |\n|---|\n", + }); + expect(splitBufferedAssistantText("**Use *npm* now**\n\nInstall it")).toEqual({ + ready: "", + rest: "**Use *npm* now**\n\nInstall it", + }); + expect(splitBufferedAssistantText("**Note:** read this.\n\nNext")).toEqual({ + ready: "**Note:** read this.\n\n", + rest: "Next", + }); + }); + + it("delivers a held heading with its first list item or its whole code block", () => { + expect(splitBufferedAssistantText("## Steps\n\n- one\n- tw")).toEqual({ + ready: "## Steps\n\n- one\n", + rest: "- tw", + }); + expect(splitBufferedAssistantText("## Code\n\n```ts\na\n\nb\n")).toEqual({ + ready: "", + rest: "## Code\n\n```ts\na\n\nb\n", + }); + expect(splitBufferedAssistantText("## Code\n\n```ts\na\n```\nafter")).toEqual({ + ready: "## Code\n\n```ts\na\n```\n", + rest: "after", + }); + }); }); diff --git a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts index 0db70e491235..072ac5110b9d 100644 --- a/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts +++ b/apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts @@ -211,6 +211,12 @@ const BLANK_LINE_PATTERN = /^[ \t]*$/; // nested items count. The trailing space is required, so a partial `-` or // `1.` never matches before the model finishes the marker. const LIST_ITEM_START_PATTERN = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]/; +// A section title: an ATX heading, or a line of only bold text, which models +// often use as a heading. +const SECTION_TITLE_PATTERN = /^ {0,3}(?:#{1,6}(?:[ \t]|$)|\*\*(?:[^*]|\*(?!\*))+\*\*:?$)/; +// An unindented ATX heading ends the paragraph or list above it, even with no +// blank line between them. A bold line would continue the paragraph instead. +const TOP_LEVEL_HEADING_PATTERN = /^#{1,6}(?:[ \t]|$)/; /** * Splits buffered assistant text at the last blank line, closing code fence, @@ -221,17 +227,26 @@ const LIST_ITEM_START_PATTERN = /^[ \t]*(?:[-*+]|\d{1,9}[.)])[ \t]/; * never leaks; a list item start is the one lookahead that may sit on the * partial line, since tight lists have no blank lines between items and would * otherwise land all at once. + * + * A section title holds the boundary until a content line follows it, so a + * title never lands alone and waits above a block that is still streaming. */ export function splitBufferedAssistantText(text: string): { ready: string; rest: string } { let openFence: { marker: string; indent: number } | null = null; let boundary = -1; let lineStart = 0; + let titleAwaitingContent = false; for (;;) { const newline = text.indexOf("\n", lineStart); const line = text .slice(lineStart, newline === -1 ? text.length : newline) .replace(/[ \t\r]+$/, ""); - if (openFence === null && lineStart > 0 && LIST_ITEM_START_PATTERN.test(line)) { + if ( + openFence === null && + lineStart > 0 && + !titleAwaitingContent && + LIST_ITEM_START_PATTERN.test(line) + ) { boundary = lineStart; } if (newline === -1) { @@ -243,6 +258,7 @@ export function splitBufferedAssistantText(text: string): { ready: string; rest: const marker = fenceMatch[2]!; if (openFence === null) { openFence = { marker, indent }; + titleAwaitingContent = false; } else if ( marker[0] === openFence.marker[0] && marker.length >= openFence.marker.length && @@ -254,7 +270,14 @@ export function splitBufferedAssistantText(text: string): { ready: string; rest: boundary = newline + 1; } } else if (openFence === null && BLANK_LINE_PATTERN.test(line) && lineStart > 0) { - boundary = newline + 1; + if (!titleAwaitingContent) { + boundary = newline + 1; + } + } else if (openFence === null) { + if (lineStart > 0 && !titleAwaitingContent && TOP_LEVEL_HEADING_PATTERN.test(line)) { + boundary = lineStart; + } + titleAwaitingContent = SECTION_TITLE_PATTERN.test(line); } lineStart = newline + 1; } @@ -2648,17 +2671,16 @@ const make = Effect.gen(function* () { (source: string, event: { readonly eventId: string; readonly type: string }) => (effect: Effect.Effect) => effect.pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logWarning("provider runtime ingestion failed to process event", { - source, - eventId: event.eventId, - eventType: event.type, - cause: Cause.pretty(cause), - }); - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("provider runtime ingestion failed to process event", { + source, + eventId: event.eventId, + eventType: event.type, + cause: Cause.pretty(cause), + }), + ), ); const worker = yield* makeDrainableWorker((input: RuntimeIngestionInput) => diff --git a/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts b/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts index 14a92a5eaef5..092ca1b1d471 100644 --- a/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts +++ b/apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts @@ -27,15 +27,14 @@ export const logCleanupCauseUnlessInterrupted = ({ readonly threadId: ThreadDeletedEvent["payload"]["threadId"]; }): Effect.Effect => effect.pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logDebug(message, { - threadId, - cause: Cause.pretty(cause), - }); - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logDebug(message, { + threadId, + cause: Cause.pretty(cause), + }), + ), ); const make = Effect.gen(function* () { @@ -67,16 +66,15 @@ const make = Effect.gen(function* () { const processThreadDeletedSafely = (event: ThreadDeletedEvent) => processThreadDeleted(event).pipe( - Effect.catchCause((cause) => { - if (Cause.hasInterruptsOnly(cause)) { - return Effect.failCause(cause); - } - return Effect.logWarning("thread deletion reactor failed to process event", { - eventType: event.type, - threadId: event.payload.threadId, - cause: Cause.pretty(cause), - }); - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("thread deletion reactor failed to process event", { + eventType: event.type, + threadId: event.payload.threadId, + cause: Cause.pretty(cause), + }), + ), ); const worker = yield* makeDrainableWorker(processThreadDeletedSafely); diff --git a/apps/server/src/orchestration/PullRequestSyncReactor.ts b/apps/server/src/orchestration/PullRequestSyncReactor.ts index 319ea46ad64b..2bdf3ff9e71d 100644 --- a/apps/server/src/orchestration/PullRequestSyncReactor.ts +++ b/apps/server/src/orchestration/PullRequestSyncReactor.ts @@ -259,12 +259,12 @@ export const make = Effect.gen(function* () { Effect.map((stack) => ({ stack: stack === null ? null : ({ kind: "native", ...stack } as const), })), - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("pull request stack lookup failed", { - key, - }).pipe(Effect.as(null)), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + () => + Effect.logWarning("pull request stack lookup failed", { + key, + }).pipe(Effect.as(null)), ), ) : null; diff --git a/apps/server/src/orchestration/Schemas.ts b/apps/server/src/orchestration/Schemas.ts index 29468dc3f84e..f4fe2e0104f6 100644 --- a/apps/server/src/orchestration/Schemas.ts +++ b/apps/server/src/orchestration/Schemas.ts @@ -16,6 +16,7 @@ import { ThreadPinnedPayload as ContractsThreadPinnedPayloadSchema, ThreadUnpinnedPayload as ContractsThreadUnpinnedPayloadSchema, ThreadPinReorderedPayload as ContractsThreadPinReorderedPayloadSchema, + ThreadAutoSettleSetPayload as ContractsThreadAutoSettleSetPayloadSchema, ThreadPullRequestLinkedPayload as ContractsThreadPullRequestLinkedPayloadSchema, ThreadPullRequestUnlinkedPayload as ContractsThreadPullRequestUnlinkedPayloadSchema, ThreadPullRequestSyncedPayload as ContractsThreadPullRequestSyncedPayloadSchema, @@ -51,6 +52,7 @@ export const ThreadUnsnoozedPayload = ContractsThreadUnsnoozedPayloadSchema; export const ThreadPinnedPayload = ContractsThreadPinnedPayloadSchema; export const ThreadUnpinnedPayload = ContractsThreadUnpinnedPayloadSchema; export const ThreadPinReorderedPayload = ContractsThreadPinReorderedPayloadSchema; +export const ThreadAutoSettleSetPayload = ContractsThreadAutoSettleSetPayloadSchema; export const ThreadPullRequestLinkedPayload = ContractsThreadPullRequestLinkedPayloadSchema; export const ThreadPullRequestUnlinkedPayload = ContractsThreadPullRequestUnlinkedPayloadSchema; export const ThreadPullRequestSyncedPayload = ContractsThreadPullRequestSyncedPayloadSchema; diff --git a/apps/server/src/orchestration/ThreadPullRequestReactor.ts b/apps/server/src/orchestration/ThreadPullRequestReactor.ts index 86026632a6af..17efc74e4f40 100644 --- a/apps/server/src/orchestration/ThreadPullRequestReactor.ts +++ b/apps/server/src/orchestration/ThreadPullRequestReactor.ts @@ -207,16 +207,16 @@ export const make = Effect.gen(function* () { } return { thread, branchPullRequest, replacement }; }).pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("thread pull request discovery failed", { - threadId: thread.id, - cause: Cause.pretty(cause), - }).pipe( - Effect.tap(() => Effect.sync(() => failBackfill([thread]))), - Effect.as(null), - ), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("thread pull request discovery failed", { + threadId: thread.id, + cause: Cause.pretty(cause), + }).pipe( + Effect.tap(() => Effect.sync(() => failBackfill([thread]))), + Effect.as(null), + ), ), ), ); @@ -273,25 +273,25 @@ export const make = Effect.gen(function* () { OrchestrationCommandInvariantError: () => Effect.sync(() => finishBackfill([thread])), }), - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("thread pull request update failed", { - threadId: thread.id, - cause: Cause.pretty(cause), - }).pipe(Effect.tap(() => Effect.sync(() => failBackfill([thread])))), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("thread pull request update failed", { + threadId: thread.id, + cause: Cause.pretty(cause), + }).pipe(Effect.tap(() => Effect.sync(() => failBackfill([thread])))), ), ), { discard: true }, ); }).pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("thread branch pull request lookup failed", { - threadIds: group.map((thread) => thread.id), - cause: Cause.pretty(cause), - }).pipe(Effect.tap(() => Effect.sync(() => failBackfill(group)))), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("thread branch pull request lookup failed", { + threadIds: group.map((thread) => thread.id), + cause: Cause.pretty(cause), + }).pipe(Effect.tap(() => Effect.sync(() => failBackfill(group)))), ), ), { concurrency: 8, discard: true }, @@ -300,12 +300,12 @@ export const make = Effect.gen(function* () { const worker = yield* makeDrainableWorker((request: RefreshRequest) => synchronize(request).pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("thread pull request refresh failed", { - cause: Cause.pretty(cause), - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("thread pull request refresh failed", { + cause: Cause.pretty(cause), + }), ), ), ); diff --git a/apps/server/src/orchestration/ThreadSettlementPolicy.test.ts b/apps/server/src/orchestration/ThreadSettlementPolicy.test.ts index 252b99439400..8a1588b18752 100644 --- a/apps/server/src/orchestration/ThreadSettlementPolicy.test.ts +++ b/apps/server/src/orchestration/ThreadSettlementPolicy.test.ts @@ -171,6 +171,15 @@ describe("resolveAutoSettlementAt", () => { it("blocks pins, snooze, pending work, live sessions, and queued starts", () => { expect(decide(makeThread({ settledOverride: "active" }))).toBe(false); + }); + + it("never settles a thread whose auto-settle is turned off, by inactivity or merge", () => { + const held = makeThread({ autoSettleDisabledAt: "2026-08-21T00:00:00.000Z" }); + expect(decide(held)).toBe(false); + expect( + decide(held, { state: "merged", mergedAt: "2026-08-21T00:00:00.000Z", closedAt: null }), + ).toBe(false); + expect(decide(makeThread({ autoSettleDisabledAt: null }))).toBe(true); expect(decide(makeThread({ snoozedUntil: "2026-08-29T00:00:00.000Z" }))).toBe(false); expect(decide(makeThread({ hasPendingApprovals: true }))).toBe(false); expect(decide(makeThread({ hasPendingUserInput: true }))).toBe(false); @@ -248,6 +257,21 @@ const terminalSnapshot = ( syncedAt: NOW, }); +describe("per-thread auto-settle opt out", () => { + it("blocks both inactivity and merge settlement while auto-settle is off", () => { + const merged = linkedRequest(1, terminalSnapshot("merged", NOW)); + expect(decide(makeThread({ latestUserMessageAt: "2026-08-01T00:00:00.000Z" }))).toBe(true); + expect(decide(makeThread({ pullRequests: [merged] }), null, { days: null })).toBe(true); + const held = { autoSettleDisabledAt: NOW }; + expect(decide(makeThread({ ...held, latestUserMessageAt: "2026-08-01T00:00:00.000Z" }))).toBe( + false, + ); + expect(decide(makeThread({ ...held, pullRequests: [merged] }), null, { days: null })).toBe( + false, + ); + }); +}); + describe("linked request settlement", () => { it.each(["closed", "merged"] as const)( "uses the latest actual %s transition despite later comments on another PR", diff --git a/apps/server/src/orchestration/ThreadSettlementPolicy.ts b/apps/server/src/orchestration/ThreadSettlementPolicy.ts index 92063745eff5..113d68204e59 100644 --- a/apps/server/src/orchestration/ThreadSettlementPolicy.ts +++ b/apps/server/src/orchestration/ThreadSettlementPolicy.ts @@ -117,6 +117,7 @@ export function resolveAutoSettlementAt(input: { /** Cheap checks that run before any source control lookup. */ export function isAutoSettlementCandidate(thread: OrchestrationThreadShell, now: string): boolean { if (thread.archivedAt !== null || thread.settledOverride !== null) return false; + if (thread.autoSettleDisabledAt != null) return false; if (thread.hasPendingApprovals || thread.hasPendingUserInput) return false; if (thread.session?.status === "starting" || thread.session?.status === "running") return false; if (thread.backgroundLiveness != null) return false; diff --git a/apps/server/src/orchestration/ThreadSettlementReactor.ts b/apps/server/src/orchestration/ThreadSettlementReactor.ts index ff6f995df46f..4192896efed5 100644 --- a/apps/server/src/orchestration/ThreadSettlementReactor.ts +++ b/apps/server/src/orchestration/ThreadSettlementReactor.ts @@ -137,13 +137,13 @@ export const make = Effect.gen(function* () { }, (effect, thread) => effect.pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("automatic thread settlement skipped", { - threadId: thread.id, - cause: Cause.pretty(cause), - }).pipe(Effect.as(null)), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("automatic thread settlement skipped", { + threadId: thread.id, + cause: Cause.pretty(cause), + }).pipe(Effect.as(null)), ), ), ); @@ -305,13 +305,13 @@ export const make = Effect.gen(function* () { discard: true, }); }).pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("automatic thread settlement skipped", { - threadIds: group.map((thread) => thread.id), - cause: Cause.pretty(cause), - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("automatic thread settlement skipped", { + threadIds: group.map((thread) => thread.id), + cause: Cause.pretty(cause), + }), ), ), { concurrency: 8, discard: true }, @@ -323,12 +323,12 @@ export const make = Effect.gen(function* () { threadId?: ThreadId, ) => sweep(mergedPullRequest, threadId).pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("automatic thread settlement sweep failed", { - cause: Cause.pretty(cause), - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => + Effect.logWarning("automatic thread settlement sweep failed", { + cause: Cause.pretty(cause), + }), ), ); const worker = yield* makeDrainableWorker((threadId: ThreadId | undefined) => diff --git a/apps/server/src/orchestration/commandInvariants.ts b/apps/server/src/orchestration/commandInvariants.ts index 873eab007bea..244a6a9e9cab 100644 --- a/apps/server/src/orchestration/commandInvariants.ts +++ b/apps/server/src/orchestration/commandInvariants.ts @@ -119,15 +119,13 @@ export function requireThreadArchived(input: { readonly threadId: ThreadId; }): Effect.Effect { return requireThread(input).pipe( - Effect.flatMap((thread) => - thread.archivedAt !== null - ? Effect.succeed(thread) - : Effect.fail( - invariantError( - input.command.type, - `Thread '${input.threadId}' is not archived for command '${input.command.type}'.`, - ), - ), + Effect.filterOrFail( + (thread) => thread.archivedAt !== null, + () => + invariantError( + input.command.type, + `Thread '${input.threadId}' is not archived for command '${input.command.type}'.`, + ), ), ); } @@ -138,15 +136,13 @@ export function requireThreadNotArchived(input: { readonly threadId: ThreadId; }): Effect.Effect { return requireThread(input).pipe( - Effect.flatMap((thread) => - thread.archivedAt === null - ? Effect.succeed(thread) - : Effect.fail( - invariantError( - input.command.type, - `Thread '${input.threadId}' is already archived and cannot handle command '${input.command.type}'.`, - ), - ), + Effect.filterOrFail( + (thread) => thread.archivedAt === null, + () => + invariantError( + input.command.type, + `Thread '${input.threadId}' is already archived and cannot handle command '${input.command.type}'.`, + ), ), ); } diff --git a/apps/server/src/orchestration/decider.autoSettleSet.test.ts b/apps/server/src/orchestration/decider.autoSettleSet.test.ts new file mode 100644 index 000000000000..99657069cc74 --- /dev/null +++ b/apps/server/src/orchestration/decider.autoSettleSet.test.ts @@ -0,0 +1,154 @@ +import { + CommandId, + ProjectId, + ProviderInstanceId, + ThreadId, + type OrchestrationReadModel, +} from "@t3tools/contracts"; +import * as NodeServices from "@effect/platform-node/NodeServices"; +import { expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; + +import { decideOrchestrationCommand } from "./decider.ts"; + +const NOW = "2026-01-01T00:00:00.000Z"; +const DISABLED_AT = "2025-12-30T00:00:00.000Z"; + +function makeReadModel(input: { + readonly autoSettleDisabledAt?: string | null; + readonly settledOverride?: "settled" | "active" | null; +}): OrchestrationReadModel { + return { + snapshotSequence: 0, + projects: [], + threads: [ + { + id: ThreadId.make("thread-1"), + projectId: ProjectId.make("project-1"), + title: "Thread", + modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + pullRequests: [], + latestTurn: null, + createdAt: NOW, + updatedAt: NOW, + archivedAt: null, + settledOverride: input.settledOverride ?? null, + settledAt: input.settledOverride === "settled" ? NOW : null, + autoSettleDisabledAt: input.autoSettleDisabledAt ?? null, + deletedAt: null, + messages: [], + proposedPlans: [], + activities: [], + checkpoints: [], + session: null, + }, + ], + updatedAt: NOW, + }; +} + +const events = (event: Effect.Success>) => + Array.isArray(event) ? event : [event]; + +it.layer(NodeServices.layer)("thread.auto-settle.set decider", (it) => { + it.effect("turning auto-settle off stamps autoSettleDisabledAt and updatedAt together", () => + Effect.gen(function* () { + const [event] = events( + yield* decideOrchestrationCommand({ + command: { + type: "thread.auto-settle.set", + commandId: CommandId.make("cmd-off"), + threadId: ThreadId.make("thread-1"), + enabled: false, + }, + readModel: makeReadModel({}), + }), + ); + expect(event?.type).toBe("thread.auto-settle-set"); + if (event?.type === "thread.auto-settle-set") { + expect(event.payload.autoSettleDisabledAt).toBe(event.payload.updatedAt); + expect(event.payload.updatedAt).not.toBe(NOW); + } + }), + ); + + it.effect("turning it off again keeps the original stamp and updatedAt", () => + Effect.gen(function* () { + const [event] = events( + yield* decideOrchestrationCommand({ + command: { + type: "thread.auto-settle.set", + commandId: CommandId.make("cmd-off-again"), + threadId: ThreadId.make("thread-1"), + enabled: false, + }, + readModel: makeReadModel({ autoSettleDisabledAt: DISABLED_AT }), + }), + ); + expect(event?.type).toBe("thread.auto-settle-set"); + if (event?.type === "thread.auto-settle-set") { + expect(event.payload.autoSettleDisabledAt).toBe(DISABLED_AT); + expect(event.payload.updatedAt).toBe(NOW); + } + }), + ); + + it.effect("turning auto-settle back on clears the stamp", () => + Effect.gen(function* () { + const [event] = events( + yield* decideOrchestrationCommand({ + command: { + type: "thread.auto-settle.set", + commandId: CommandId.make("cmd-on"), + threadId: ThreadId.make("thread-1"), + enabled: true, + }, + readModel: makeReadModel({ autoSettleDisabledAt: DISABLED_AT }), + }), + ); + expect(event?.type).toBe("thread.auto-settle-set"); + if (event?.type === "thread.auto-settle-set") { + expect(event.payload.autoSettleDisabledAt).toBeNull(); + expect(event.payload.updatedAt).not.toBe(NOW); + } + }), + ); + + it.effect("automatic settlement is rejected while auto-settle is off", () => + Effect.gen(function* () { + const result = yield* Effect.exit( + decideOrchestrationCommand({ + command: { + type: "thread.auto-settle", + commandId: CommandId.make("cmd-auto"), + threadId: ThreadId.make("thread-1"), + snapshotSequence: 0, + settledAt: NOW, + }, + readModel: makeReadModel({ autoSettleDisabledAt: DISABLED_AT }), + }), + ); + expect(result._tag).toBe("Failure"); + }), + ); + + it.effect("a manual settle still works while auto-settle is off", () => + Effect.gen(function* () { + const [event] = events( + yield* decideOrchestrationCommand({ + command: { + type: "thread.settle", + commandId: CommandId.make("cmd-manual"), + threadId: ThreadId.make("thread-1"), + }, + readModel: makeReadModel({ autoSettleDisabledAt: DISABLED_AT }), + }), + ); + expect(event?.type).toBe("thread.settled"); + }), + ); +}); diff --git a/apps/server/src/orchestration/decider.pullRequests.test.ts b/apps/server/src/orchestration/decider.pullRequests.test.ts index b79ca01a8dea..7a961af12d5b 100644 --- a/apps/server/src/orchestration/decider.pullRequests.test.ts +++ b/apps/server/src/orchestration/decider.pullRequests.test.ts @@ -295,7 +295,7 @@ it.layer(NodeServices.layer)("pull request link decider", (it) => { for (const planned of events) { const event = { ...planned, sequence: model.snapshotSequence + 1 }; const encoded = yield* Schema.encodeEffect(OrchestrationEvent)(event); - const decoded = yield* Schema.decodeUnknownEffect(OrchestrationEvent)(encoded); + const decoded = yield* Schema.decodeEffect(OrchestrationEvent)(encoded); // Older detail-event unions must never receive the new PR discriminants. expect(isThreadDetailEvent(decoded)).toBe(false); model = yield* projectEvent(model, decoded); diff --git a/apps/server/src/orchestration/decider.ts b/apps/server/src/orchestration/decider.ts index 2f6fbc62ac85..ac18dbb9ce7e 100644 --- a/apps/server/src/orchestration/decider.ts +++ b/apps/server/src/orchestration/decider.ts @@ -485,13 +485,14 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" command, threadId: command.threadId, }); - if (command.type === "thread.auto-settle" && thread.settledOverride !== null) { - return yield* Effect.fail( - new OrchestrationCommandInvariantError({ - commandType: command.type, - detail: `thread ${command.threadId} changed before automatic settlement`, - }), - ); + if ( + command.type === "thread.auto-settle" && + (thread.settledOverride !== null || thread.autoSettleDisabledAt != null) + ) { + return yield* new OrchestrationCommandInvariantError({ + commandType: command.type, + detail: `thread ${command.threadId} changed before automatic settlement`, + }); } // The server owns settle eligibility. A stale command must not settle // a thread whose session is coming alive or working. @@ -643,36 +644,30 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" // structurally just a string): NaN fails every comparison, and an // unparseable snoozedUntil must never persist. if (!(Date.parse(command.snoozedUntil) > Date.parse(occurredAt))) { - return yield* Effect.fail( - new OrchestrationCommandInvariantError({ - commandType: command.type, - detail: `thread ${command.threadId} snooze wake time ${command.snoozedUntil} is not in the future`, - }), - ); + return yield* new OrchestrationCommandInvariantError({ + commandType: command.type, + detail: `thread ${command.threadId} snooze wake time ${command.snoozedUntil} is not in the future`, + }); } // Blocked-on-you work must not be snoozed away: a pending approval or // user-input request is the agent waiting on the user, and hiding it // defeats the request. (A running session IS snoozable — snooze only // affects visibility, never the agent.) if (openRequests(thread).size > 0) { - return yield* Effect.fail( - new OrchestrationCommandInvariantError({ - commandType: command.type, - detail: `thread ${command.threadId} has a pending approval or user-input request and cannot be snoozed`, - }), - ); + return yield* new OrchestrationCommandInvariantError({ + commandType: command.type, + detail: `thread ${command.threadId} has a pending approval or user-input request and cannot be snoozed`, + }); } // A queued turn start — a user message no turn has adopted yet — is // invisible pending work: no session, no pending flags. Snoozing in // that window would hide a just-requested turn exactly the way settle // would. if (hasQueuedTurnStartForThread(thread, occurredAt)) { - return yield* Effect.fail( - new OrchestrationCommandInvariantError({ - commandType: command.type, - detail: `thread ${command.threadId} has a queued turn start and cannot be snoozed`, - }), - ); + return yield* new OrchestrationCommandInvariantError({ + commandType: command.type, + detail: `thread ${command.threadId} has a queued turn start and cannot be snoozed`, + }); } // Re-snoozing an already-snoozed thread to the SAME wake time is a // duplicate (double-click, raced clients): re-emit with the original @@ -834,12 +829,10 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" // (rather than silently pinning) keeps a raced reorder-after-unpin // from resurrecting a pin the user just cleared. if (thread.pinnedAt == null) { - return yield* Effect.fail( - new OrchestrationCommandInvariantError({ - commandType: command.type, - detail: `thread ${command.threadId} is not pinned and cannot be reordered`, - }), - ); + return yield* new OrchestrationCommandInvariantError({ + commandType: command.type, + detail: `thread ${command.threadId} is not pinned and cannot be reordered`, + }); } // Idempotent by re-emission (see thread.settle): a duplicate drop on // the same slot keeps the existing updatedAt so it projects as a no-op. @@ -861,6 +854,37 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" }; } + case "thread.auto-settle.set": { + const thread = yield* requireThreadNotArchived({ + readModel, + command, + threadId: command.threadId, + }); + // Idempotent by re-emission (see thread.unpin): setting the current + // state again keeps the existing timestamps so duplicates do not churn + // ordering. The flag is independent of the settled lifecycle: it only + // gates the automatic paths, so it never blocks a manual settle. + const currentlyDisabledAt = thread.autoSettleDisabledAt ?? null; + const unchanged = command.enabled + ? currentlyDisabledAt === null + : currentlyDisabledAt !== null; + const occurredAt = yield* nowIso; + return { + ...(yield* withEventBase({ + aggregateKind: "thread", + aggregateId: command.threadId, + occurredAt, + commandId: command.commandId, + })), + type: "thread.auto-settle-set", + payload: { + threadId: command.threadId, + autoSettleDisabledAt: command.enabled ? null : (currentlyDisabledAt ?? occurredAt), + updatedAt: unchanged ? thread.updatedAt : occurredAt, + }, + }; + } + case "thread.active.reorder": { const thread = yield* requireThreadNotArchived({ readModel, @@ -920,12 +944,10 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" if (command.linkedPullRequest != null) { const { linkedPullRequest: linked, ...metadata } = command; const project = readModel.projects.find((project) => project.id === thread.projectId); - let host = project?.repositoryIdentity?.canonicalKey.split("/")[0] ?? "unknown"; - try { - host = new URL(linked.url).hostname; - } catch { - // Historical clients can send links without a parseable URL. - } + // Historical clients can send links without a parseable URL. + const host = URL.canParse(linked.url) + ? new URL(linked.url).hostname + : (project?.repositoryIdentity?.canonicalKey.split("/")[0] ?? "unknown"); const hasMetadata = Object.entries(metadata).some( ([key, value]) => !["type", "commandId", "threadId"].includes(key) && value !== undefined, ); @@ -1839,12 +1861,10 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" sessionComingAlive || hasQueuedTurnStartForThread(thread, command.createdAt) ) { - return yield* Effect.fail( - new OrchestrationCommandInvariantError({ - commandType: command.type, - detail: `thread ${command.threadId} was re-engaged after settle; skipping session stop`, - }), - ); + return yield* new OrchestrationCommandInvariantError({ + commandType: command.type, + detail: `thread ${command.threadId} was re-engaged after settle; skipping session stop`, + }); } } return { diff --git a/apps/server/src/orchestration/projector.autoSettleSet.test.ts b/apps/server/src/orchestration/projector.autoSettleSet.test.ts new file mode 100644 index 000000000000..cc12f6905910 --- /dev/null +++ b/apps/server/src/orchestration/projector.autoSettleSet.test.ts @@ -0,0 +1,105 @@ +import { + CommandId, + EventId, + ProjectId, + ThreadId, + type OrchestrationEvent, +} from "@t3tools/contracts"; +import { expect, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; + +import { createEmptyReadModel, projectEvent } from "./projector.ts"; + +function makeEvent(input: { + readonly sequence: number; + readonly type: OrchestrationEvent["type"]; + readonly payload: unknown; +}): OrchestrationEvent { + return { + sequence: input.sequence, + eventId: EventId.make(`event-${input.sequence}`), + type: input.type, + aggregateKind: "thread", + aggregateId: ThreadId.make("thread-1"), + occurredAt: "2026-01-01T00:00:00.000Z", + commandId: CommandId.make(`command-${input.sequence}`), + causationEventId: null, + correlationId: null, + metadata: {}, + payload: input.payload as never, + } as OrchestrationEvent; +} + +it.effect("projects auto-settle opt-out and survives a manual settle", () => + Effect.gen(function* () { + const now = "2026-01-01T00:00:00.000Z"; + const later = "2026-01-02T00:00:00.000Z"; + const created = yield* projectEvent( + createEmptyReadModel(now), + makeEvent({ + sequence: 1, + type: "thread.created", + payload: { + threadId: ThreadId.make("thread-1"), + projectId: ProjectId.make("project-1"), + title: "Thread", + modelSelection: { provider: "codex", model: "gpt-5.4" }, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + createdAt: now, + updatedAt: now, + }, + }), + ); + expect(created.threads[0]?.autoSettleDisabledAt ?? null).toBeNull(); + + const disabled = yield* projectEvent( + created, + makeEvent({ + sequence: 2, + type: "thread.auto-settle-set", + payload: { threadId: ThreadId.make("thread-1"), autoSettleDisabledAt: now, updatedAt: now }, + }), + ); + expect(disabled.threads[0]?.autoSettleDisabledAt).toBe(now); + + // The flag is independent of the settled lifecycle: settling by hand and + // un-settling later must not clear it. + const settled = yield* projectEvent( + disabled, + makeEvent({ + sequence: 3, + type: "thread.settled", + payload: { threadId: ThreadId.make("thread-1"), settledAt: later, updatedAt: later }, + }), + ); + expect(settled.threads[0]?.settledOverride).toBe("settled"); + expect(settled.threads[0]?.autoSettleDisabledAt).toBe(now); + + const unsettled = yield* projectEvent( + settled, + makeEvent({ + sequence: 4, + type: "thread.unsettled", + payload: { threadId: ThreadId.make("thread-1"), reason: "user", updatedAt: later }, + }), + ); + expect(unsettled.threads[0]?.autoSettleDisabledAt).toBe(now); + + const enabled = yield* projectEvent( + unsettled, + makeEvent({ + sequence: 5, + type: "thread.auto-settle-set", + payload: { + threadId: ThreadId.make("thread-1"), + autoSettleDisabledAt: null, + updatedAt: later, + }, + }), + ); + expect(enabled.threads[0]?.autoSettleDisabledAt).toBeNull(); + }), +); diff --git a/apps/server/src/orchestration/projector.test.ts b/apps/server/src/orchestration/projector.test.ts index c2ffb5c3dc48..59ae490ee06c 100644 --- a/apps/server/src/orchestration/projector.test.ts +++ b/apps/server/src/orchestration/projector.test.ts @@ -94,6 +94,7 @@ describe("orchestration projector", () => { updatedAt: now, archivedAt: null, activeOrderKey: null, + autoSettleDisabledAt: null, settledOverride: null, settledAt: null, unsettledAt: null, diff --git a/apps/server/src/orchestration/projector.ts b/apps/server/src/orchestration/projector.ts index a0e92eb1d20c..54166e417192 100644 --- a/apps/server/src/orchestration/projector.ts +++ b/apps/server/src/orchestration/projector.ts @@ -42,6 +42,7 @@ import { ThreadSettledPayload, ThreadPinnedPayload, ThreadPinReorderedPayload, + ThreadAutoSettleSetPayload, ThreadPullRequestLinkedPayload, ThreadPullRequestSyncedPayload, ThreadPullRequestUnlinkedPayload, @@ -441,6 +442,7 @@ export function projectEvent( settledAt: null, unsettledAt: null, activeOrderKey: null, + autoSettleDisabledAt: null, snoozedUntil: null, snoozedAt: null, deletedAt: null, @@ -581,6 +583,17 @@ export function projectEvent( })), ); + case "thread.auto-settle-set": + return decodeForEvent(ThreadAutoSettleSetPayload, event.payload, event.type, "payload").pipe( + Effect.map((payload) => ({ + ...nextBase, + threads: updateThread(nextBase.threads, payload.threadId, { + autoSettleDisabledAt: payload.autoSettleDisabledAt, + updatedAt: payload.updatedAt, + }), + })), + ); + case "thread.pin-reordered": return decodeForEvent(ThreadPinReorderedPayload, event.payload, event.type, "payload").pipe( Effect.map((payload) => ({ diff --git a/apps/server/src/orchestration/workflowScriptQuery.ts b/apps/server/src/orchestration/workflowScriptQuery.ts index 06bbd35ccf62..184abc6147ce 100644 --- a/apps/server/src/orchestration/workflowScriptQuery.ts +++ b/apps/server/src/orchestration/workflowScriptQuery.ts @@ -32,9 +32,10 @@ export const readWorkflowScript = Effect.fn("orchestration.readWorkflowScript")( const requested = input.scriptPath; if (!NodePath.isAbsolute(requested) || NodePath.extname(requested) !== ".js") { - return yield* Effect.fail( - new OrchestrationGetWorkflowScriptError({ reason: "invalid-path", scriptPath: requested }), - ); + return yield* new OrchestrationGetWorkflowScriptError({ + reason: "invalid-path", + scriptPath: requested, + }); } const root = yield* Effect.tryPromise({ @@ -60,14 +61,16 @@ export const readWorkflowScript = Effect.fn("orchestration.readWorkflowScript")( }); if (resolved !== root && !resolved.startsWith(`${root}${NodePath.sep}`)) { - return yield* Effect.fail( - new OrchestrationGetWorkflowScriptError({ reason: "outside-root", scriptPath: resolved }), - ); + return yield* new OrchestrationGetWorkflowScriptError({ + reason: "outside-root", + scriptPath: resolved, + }); } if (NodePath.extname(resolved) !== ".js") { - return yield* Effect.fail( - new OrchestrationGetWorkflowScriptError({ reason: "not-js", scriptPath: resolved }), - ); + return yield* new OrchestrationGetWorkflowScriptError({ + reason: "not-js", + scriptPath: resolved, + }); } // TOCTOU-safe read (review finding): open FIRST, then verify what was diff --git a/apps/server/src/persistence/AuthPairingLinks.ts b/apps/server/src/persistence/AuthPairingLinks.ts index aae55dd2fe06..7ad1d95ed7bc 100644 --- a/apps/server/src/persistence/AuthPairingLinks.ts +++ b/apps/server/src/persistence/AuthPairingLinks.ts @@ -268,7 +268,7 @@ export const make = Effect.gen(function* () { ), Effect.flatMap((rowOption) => Option.match(rowOption, { - onNone: () => Effect.succeed(Option.none()), + onNone: () => Effect.succeedNone, onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe( Effect.mapError((cause) => @@ -278,7 +278,7 @@ export const make = Effect.gen(function* () { { pairingLinkId: row.id }, ), ), - Effect.map(Option.some), + Effect.asSome, ), }), ), @@ -329,7 +329,7 @@ export const make = Effect.gen(function* () { ), Effect.flatMap((rowOption) => Option.match(rowOption, { - onNone: () => Effect.succeed(Option.none()), + onNone: () => Effect.succeedNone, onSome: (row) => decodeAuthPairingLinkDbRow(row).pipe( Effect.mapError((cause) => @@ -339,7 +339,7 @@ export const make = Effect.gen(function* () { { pairingLinkId: row.id }, ), ), - Effect.map(Option.some), + Effect.asSome, ), }), ), diff --git a/apps/server/src/persistence/AuthSessions.ts b/apps/server/src/persistence/AuthSessions.ts index 836b1f8f2e30..bbc8fc75685a 100644 --- a/apps/server/src/persistence/AuthSessions.ts +++ b/apps/server/src/persistence/AuthSessions.ts @@ -422,7 +422,7 @@ export const make = Effect.gen(function* () { ), Effect.flatMap((rowOption) => Option.match(rowOption, { - onNone: () => Effect.succeed(Option.none()), + onNone: () => Effect.succeedNone, onSome: (row) => decodeAuthSessionDbRow(row).pipe( Effect.mapError((cause) => diff --git a/apps/server/src/persistence/Layers/ProjectionThreads.ts b/apps/server/src/persistence/Layers/ProjectionThreads.ts index a1e3cc466fd1..d99eb5a2231a 100644 --- a/apps/server/src/persistence/Layers/ProjectionThreads.ts +++ b/apps/server/src/persistence/Layers/ProjectionThreads.ts @@ -55,6 +55,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () { pinned_at, pin_order_key, active_order_key, + auto_settle_disabled_at, title_regeneration_request_id, title_regeneration_started_at, latest_user_message_at, @@ -88,6 +89,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () { ${row.pinnedAt}, ${row.pinOrderKey ?? null}, ${row.activeOrderKey ?? null}, + ${row.autoSettleDisabledAt ?? null}, ${row.titleRegenerationRequestId ?? null}, ${row.titleRegenerationStartedAt ?? null}, ${row.latestUserMessageAt}, @@ -121,6 +123,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () { pinned_at = excluded.pinned_at, pin_order_key = excluded.pin_order_key, active_order_key = excluded.active_order_key, + auto_settle_disabled_at = excluded.auto_settle_disabled_at, title_regeneration_request_id = excluded.title_regeneration_request_id, title_regeneration_started_at = excluded.title_regeneration_started_at, latest_user_message_at = excluded.latest_user_message_at, @@ -161,6 +164,7 @@ const makeProjectionThreadRepository = Effect.gen(function* () { pinned_at AS "pinnedAt", pin_order_key AS "pinOrderKey", active_order_key AS "activeOrderKey", + auto_settle_disabled_at AS "autoSettleDisabledAt", title_regeneration_request_id AS "titleRegenerationRequestId", title_regeneration_started_at AS "titleRegenerationStartedAt", latest_user_message_at AS "latestUserMessageAt", diff --git a/apps/server/src/persistence/Layers/ProjectionTurns.ts b/apps/server/src/persistence/Layers/ProjectionTurns.ts index bd57a4eaa30a..94443c867f04 100644 --- a/apps/server/src/persistence/Layers/ProjectionTurns.ts +++ b/apps/server/src/persistence/Layers/ProjectionTurns.ts @@ -317,9 +317,8 @@ const makeProjectionTurnRepository = Effect.gen(function* () { ), Effect.flatMap((rowOption) => Option.match(rowOption, { - onNone: () => Effect.succeed(Option.none()), - onSome: (row) => - Effect.succeed(Option.some(row as Schema.Schema.Type)), + onNone: () => Effect.succeedNone, + onSome: (row) => Effect.succeedSome(row as Schema.Schema.Type), }), ), ); diff --git a/apps/server/src/persistence/Layers/Sqlite.ts b/apps/server/src/persistence/Layers/Sqlite.ts index 2fcf579bb6b7..1a2854c3d9cf 100644 --- a/apps/server/src/persistence/Layers/Sqlite.ts +++ b/apps/server/src/persistence/Layers/Sqlite.ts @@ -40,7 +40,7 @@ export const makeSqlitePersistenceLive = Effect.fn("makeSqlitePersistenceLive")( filename: dbPath, spanAttributes: { "db.name": path.basename(dbPath), - "service.name": "t3-server", + "service.name": "t3code-server", }, }), ); diff --git a/apps/server/src/persistence/Migrations.ts b/apps/server/src/persistence/Migrations.ts index 18aae09febf8..c837ae3f4c50 100644 --- a/apps/server/src/persistence/Migrations.ts +++ b/apps/server/src/persistence/Migrations.ts @@ -65,6 +65,7 @@ import Migration0050 from "./Migrations/050_ProjectionThreadPullRequests.ts"; import Migration0051 from "./Migrations/051_ProjectionThreadMessageContext.ts"; import Migration0052 from "./Migrations/052_ProjectionThreadTitleState.ts"; import Migration0053 from "./Migrations/053_PullRequestFilesViewed.ts"; +import Migration0054 from "./Migrations/054_ProjectionThreadsAutoSettleDisabledAt.ts"; /** * Migration loader with all migrations defined inline. @@ -130,6 +131,7 @@ const migrationEntries = [ [51, "ProjectionThreadMessageContext", Migration0051], [52, "ProjectionThreadTitleState", Migration0052], [53, "PullRequestFilesViewed", Migration0053], + [54, "ProjectionThreadsAutoSettleDisabledAt", Migration0054], ] as const; export const migrationManifest = migrationEntries.map(([id, name]) => [id, name] as const); diff --git a/apps/server/src/persistence/Migrations/054_ProjectionThreadsAutoSettleDisabledAt.test.ts b/apps/server/src/persistence/Migrations/054_ProjectionThreadsAutoSettleDisabledAt.test.ts new file mode 100644 index 000000000000..df6403316e5b --- /dev/null +++ b/apps/server/src/persistence/Migrations/054_ProjectionThreadsAutoSettleDisabledAt.test.ts @@ -0,0 +1,41 @@ +import { assert, it } from "@effect/vitest"; +import * as Effect from "effect/Effect"; +import * as SqlClient from "effect/unstable/sql/SqlClient"; +import * as NodeSqliteClient from "@t3tools/shared/nodeSqliteClient"; + +import { runMigrations } from "../Migrations.ts"; +import migrateAutoSettleDisabledAt from "./054_ProjectionThreadsAutoSettleDisabledAt.ts"; + +it.layer(NodeSqliteClient.layer({ filename: ":memory:" }))( + "054_ProjectionThreadsAutoSettleDisabledAt", + (it) => { + it.effect("adds the column with auto-settle left on for existing threads", () => + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + yield* runMigrations({ toMigrationInclusive: 53 }); + const now = "2026-01-01T00:00:00.000Z"; + yield* sql` + INSERT INTO projection_threads ( + thread_id, project_id, title, model_selection_json, runtime_mode, + created_at, updated_at + ) VALUES ( + 'thread-1', 'project-1', 'Existing thread', + '{"instanceId":"codex","model":"gpt-5.4"}', 'full-access', ${now}, ${now} + ) + `; + yield* runMigrations({ toMigrationInclusive: 54 }); + const migrated = yield* sql<{ readonly autoSettleDisabledAt: string | null }>` + SELECT auto_settle_disabled_at AS "autoSettleDisabledAt" FROM projection_threads WHERE thread_id = 'thread-1' + `; + assert.deepEqual(migrated, [{ autoSettleDisabledAt: null }]); + // Re-running against a database that already has the column keeps its value. + yield* sql`UPDATE projection_threads SET auto_settle_disabled_at = ${now} WHERE thread_id = 'thread-1'`; + yield* migrateAutoSettleDisabledAt; + const rows = yield* sql<{ readonly autoSettleDisabledAt: string | null }>` + SELECT auto_settle_disabled_at AS "autoSettleDisabledAt" FROM projection_threads WHERE thread_id = 'thread-1' + `; + assert.deepEqual(rows, [{ autoSettleDisabledAt: now }]); + }), + ); + }, +); diff --git a/apps/server/src/persistence/Migrations/054_ProjectionThreadsAutoSettleDisabledAt.ts b/apps/server/src/persistence/Migrations/054_ProjectionThreadsAutoSettleDisabledAt.ts new file mode 100644 index 000000000000..f91f6d8abdfa --- /dev/null +++ b/apps/server/src/persistence/Migrations/054_ProjectionThreadsAutoSettleDisabledAt.ts @@ -0,0 +1,15 @@ +import * as Effect from "effect/Effect"; +import * as SqlClient from "effect/unstable/sql/SqlClient"; + +export default Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + const columns = yield* sql<{ readonly name: string }>` + PRAGMA table_info(projection_threads) + `; + if (!columns.some((column) => column.name === "auto_settle_disabled_at")) { + yield* sql` + ALTER TABLE projection_threads + ADD COLUMN auto_settle_disabled_at TEXT + `; + } +}); diff --git a/apps/server/src/persistence/ProviderSessionRuntime.ts b/apps/server/src/persistence/ProviderSessionRuntime.ts index 2673512edf10..80588f58858f 100644 --- a/apps/server/src/persistence/ProviderSessionRuntime.ts +++ b/apps/server/src/persistence/ProviderSessionRuntime.ts @@ -398,7 +398,7 @@ export const make = Effect.gen(function* () { ), Effect.flatMap((runtimeRowOption) => Option.match(runtimeRowOption, { - onNone: () => Effect.succeed(Option.none()), + onNone: () => Effect.succeedNone, onSome: (row) => decodeRuntimeRow(row).pipe( Effect.mapError((cause) => @@ -408,7 +408,7 @@ export const make = Effect.gen(function* () { { threadId: input.threadId }, ), ), - Effect.map((runtime) => Option.some(runtime)), + Effect.asSome, ), }), ), @@ -428,7 +428,7 @@ export const make = Effect.gen(function* () { // every consumer that enumerates sessions, such as the reaper. Effect.forEach(rows, (row) => decodeRuntimeRow(row).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catch((cause) => Effect.logWarning("provider.session.runtime.row-skipped", { threadId: row.threadId, diff --git a/apps/server/src/persistence/Services/ProjectionThreads.ts b/apps/server/src/persistence/Services/ProjectionThreads.ts index 275f7feb8bfb..6388f264e03b 100644 --- a/apps/server/src/persistence/Services/ProjectionThreads.ts +++ b/apps/server/src/persistence/Services/ProjectionThreads.ts @@ -51,6 +51,7 @@ export const ProjectionThread = Schema.Struct({ pinnedAt: Schema.NullOr(IsoDateTime), pinOrderKey: Schema.optional(Schema.NullOr(Schema.String)), activeOrderKey: Schema.optional(Schema.NullOr(Schema.String)), + autoSettleDisabledAt: Schema.optional(Schema.NullOr(IsoDateTime)), titleRegenerationRequestId: Schema.optional(Schema.NullOr(CommandId)), titleRegenerationStartedAt: Schema.optional(Schema.NullOr(IsoDateTime)), latestUserMessageAt: Schema.NullOr(IsoDateTime), diff --git a/apps/server/src/project/AgentSessionImporter.test.ts b/apps/server/src/project/AgentSessionImporter.test.ts index 4eb03a5cc036..2438edca8b1b 100644 --- a/apps/server/src/project/AgentSessionImporter.test.ts +++ b/apps/server/src/project/AgentSessionImporter.test.ts @@ -56,6 +56,7 @@ import { makeProviderRegistryLayer } from "../provider/testUtils/providerRegistr import { ServerSettingsService } from "../serverSettings.ts"; import * as AnalyticsService from "../telemetry/AnalyticsService.ts"; import { TextGeneration } from "../textGeneration/TextGeneration.ts"; +import { TerminalManager } from "../terminal/Manager.ts"; import { VcsStatusBroadcaster } from "../vcs/VcsStatusBroadcaster.ts"; import * as RepositoryIdentityResolver from "./RepositoryIdentityResolver.ts"; import { importRecentAgentThreads } from "./AgentSessionImporter.ts"; @@ -232,7 +233,7 @@ it.layer(NodeServices.layer)("AgentSessionImporter", (it) => { upsert: (binding) => Effect.sync(() => void bindings.push(binding)), getProvider: () => Effect.die("unused"), recordImportedTranscript: () => Effect.void, - getBinding: () => Effect.succeed(Option.none()), + getBinding: () => Effect.succeedNone, listThreadIds: () => Effect.die("unused"), listBindings: () => Effect.die("unused"), }); @@ -456,7 +457,7 @@ it.layer(NodeServices.layer)("AgentSessionImporter", (it) => { upsert: () => Effect.die("must not replace an active binding"), getProvider: () => Effect.die("unused"), recordImportedTranscript: () => Effect.void, - getBinding: () => Effect.succeed(Option.some(runningBinding)), + getBinding: () => Effect.succeedSome(runningBinding), listThreadIds: () => Effect.die("unused"), listBindings: () => Effect.die("unused"), }); @@ -511,7 +512,7 @@ it.layer(NodeServices.layer)("AgentSessionImporter", (it) => { upsert: () => Effect.die("must not bind malformed or wrong-project sessions"), getProvider: () => Effect.die("unused"), recordImportedTranscript: () => Effect.die("unused"), - getBinding: () => Effect.succeed(Option.none()), + getBinding: () => Effect.succeedNone, listThreadIds: () => Effect.die("unused"), listBindings: () => Effect.die("unused"), }); @@ -931,6 +932,7 @@ it.layer(integrationLayer)("AgentSessionImporter integration", (it) => { Layer.provide(Layer.mock(GitWorkflowService)({})), Layer.provide(Layer.mock(VcsStatusBroadcaster)({})), Layer.provide(Layer.mock(TextGeneration)({})), + Layer.provide(Layer.mock(TerminalManager)({ closeIdle: () => Effect.void })), Layer.provide(ServerSettingsService.layerTest()), ); diff --git a/apps/server/src/project/AgentSessionImporter.ts b/apps/server/src/project/AgentSessionImporter.ts index 5ebb41a1bb54..bf9eb702ebe5 100644 --- a/apps/server/src/project/AgentSessionImporter.ts +++ b/apps/server/src/project/AgentSessionImporter.ts @@ -87,6 +87,7 @@ function hasImportBlockingActivity( thread.snoozedAt != null || thread.pinnedAt != null || thread.pinOrderKey != null || + thread.autoSettleDisabledAt != null || thread.titleRegeneration != null || thread.linkedPullRequest != null || thread.unsettledAt != null || diff --git a/apps/server/src/project/AgentSessionScanner.ts b/apps/server/src/project/AgentSessionScanner.ts index 975192e70033..8a8f3b310e03 100644 --- a/apps/server/src/project/AgentSessionScanner.ts +++ b/apps/server/src/project/AgentSessionScanner.ts @@ -662,7 +662,7 @@ export const make = Effect.gen(function* () { fileSystem.readDirectory(directory).pipe(Effect.orElseSucceed((): ReadonlyArray => [])); const statOption = (target: string) => - fileSystem.stat(target).pipe(Effect.map(Option.some), Effect.orElseSucceed(Option.none)); + fileSystem.stat(target).pipe(Effect.asSome, Effect.orElseSucceed(Option.none)); /** Match directory aliases without assuming the host volume is case-insensitive. */ const directoryIdentity = Effect.fn("AgentSessionScanner.directoryIdentity")(function* ( diff --git a/apps/server/src/project/ProjectFaviconResolver.ts b/apps/server/src/project/ProjectFaviconResolver.ts index 4fd36cb67b94..90cbcb574e81 100644 --- a/apps/server/src/project/ProjectFaviconResolver.ts +++ b/apps/server/src/project/ProjectFaviconResolver.ts @@ -143,7 +143,7 @@ const optionOnNotFound = ( effect: Effect.Effect, ): Effect.Effect, PlatformError.PlatformError, R> => effect.pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ PlatformError: (error) => error.reason._tag === "NotFound" ? Effect.succeed(Option.none()) : Effect.fail(error), @@ -175,7 +175,7 @@ export const make = Effect.gen(function* () { relativePath, }) ).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ WorkspacePathOutsideRootError: () => Effect.succeed( diff --git a/apps/server/src/project/ProjectSetupScriptRunner.test.ts b/apps/server/src/project/ProjectSetupScriptRunner.test.ts index dd341a7f7859..650f304e8481 100644 --- a/apps/server/src/project/ProjectSetupScriptRunner.test.ts +++ b/apps/server/src/project/ProjectSetupScriptRunner.test.ts @@ -58,7 +58,7 @@ const makeProjectionSnapshotQueryLayer = (project: OrchestrationProject) => }); type TerminalOverrides = Pick & - Partial>; + Partial>; const makeTerminalManagerLayer = (overrides: TerminalOverrides) => Layer.succeed(TerminalManager.TerminalManager, { @@ -67,6 +67,7 @@ const makeTerminalManagerLayer = (overrides: TerminalOverrides) => clear: () => Effect.void, restart: () => Effect.die(new Error("unused")), close: () => Effect.void, + closeIdle: () => Effect.void, subscribe: () => Effect.succeed(() => undefined), subscribeMetadata: () => Effect.succeed(() => undefined), ...overrides, @@ -262,6 +263,7 @@ describe("ProjectSetupScriptRunner", () => { listener = null; }); }); + const closeIdle = vi.fn(() => Effect.void); const project = makeProject([ { id: "setup", @@ -329,14 +331,82 @@ describe("ProjectSetupScriptRunner", () => { ]); // The subscription is torn down once the sentinel arrives. expect(listener).toBeNull(); + // A failed run keeps its shell open for a look. + expect(closeIdle).not.toHaveBeenCalled(); }).pipe( - Effect.provide(testLayer(project, { open, write, subscribe })), + Effect.provide(testLayer(project, { open, write, subscribe, closeIdle })), Effect.provideService(HostProcessPlatform, "linux"), Effect.provideService(HostProcessEnvironment, { SHELL: "/bin/zsh" }), ); }, ); + it.effect("closes the idle setup shell after a clean exit", () => { + const open = vi.fn(() => + Effect.succeed({ + threadId: "thread-1", + terminalId: "setup-setup", + cwd: "/repo/worktrees/a", + worktreePath: "/repo/worktrees/a", + status: "running" as const, + pid: 123, + history: "", + exitCode: null, + exitSignal: null, + label: "setup-setup", + updatedAt: "2026-01-01T00:00:00.000Z", + }), + ); + let written = ""; + const write = vi.fn((input: { data: string }) => + Effect.sync(() => void (written = input.data)), + ); + let listener: ((event: TerminalEvent) => Effect.Effect) | null = null; + const subscribe = vi.fn((next: (event: TerminalEvent) => Effect.Effect) => { + listener = next; + return Effect.succeed(() => { + listener = null; + }); + }); + const closeIdle = vi.fn(() => Effect.void); + const project = makeProject([ + { + id: "setup", + name: "Setup", + command: "bun install", + icon: "configure", + runOnWorktreeCreate: true, + }, + ]); + + return Effect.gen(function* () { + const runner = yield* ProjectSetupScriptRunner.ProjectSetupScriptRunner; + const result = yield* runner.runForThread({ + threadId: "thread-1", + projectCwd: "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/repo/project", + worktreePath: "/repo/worktrees/a", + observeCompletion: {}, + }); + if (result.status !== "started" || !result.completion) { + return yield* Effect.die("expected an observed setup run"); + } + const sentinel = /__T3_SETUP_DONE___[0-9a-f]{32}:/.exec(written)?.[0]; + yield* listener!({ + threadId: "thread-1", + terminalId: "setup-setup", + type: "output", + data: `${sentinel}0\r\n`, + }); + + expect((yield* result.completion).exitCode).toBe(0); + expect(closeIdle).toHaveBeenCalledWith({ threadId: "thread-1", terminalId: "setup-setup" }); + }).pipe( + Effect.provide(testLayer(project, { open, write, subscribe, closeIdle })), + Effect.provideService(HostProcessPlatform, "linux"), + Effect.provideService(HostProcessEnvironment, { SHELL: "/bin/zsh" }), + ); + }); + it.effect("unsubscribes from terminal output when the command cannot be written", () => { const open = vi.fn(() => Effect.succeed({ diff --git a/apps/server/src/project/ProjectSetupScriptRunner.ts b/apps/server/src/project/ProjectSetupScriptRunner.ts index 16cbfaa59496..74bc41e5e8cd 100644 --- a/apps/server/src/project/ProjectSetupScriptRunner.ts +++ b/apps/server/src/project/ProjectSetupScriptRunner.ts @@ -36,6 +36,7 @@ export interface ProjectSetupScriptRunnerResultStarted { * Resolves when the script's shell prints the completion sentinel. The * exit code is null when the terminal exited or was closed before the * sentinel arrived. Only present when `observeCompletion` was requested. + * An exit code of 0 closes the setup shell if it has nothing left running. */ readonly completion?: Effect.Effect; } @@ -412,6 +413,16 @@ export const make = Effect.gen(function* () { Effect.tapError(() => Effect.sync(() => observed?.unsubscribe())), ); + // A clean run leaves only an idle prompt behind; its output stays in the + // terminal history. A failed run keeps its shell open for a look. + const completion = observed?.completion.pipe( + Effect.tap(({ exitCode }) => + exitCode === 0 + ? terminalManager.closeIdle({ threadId: input.threadId, terminalId }) + : Effect.void, + ), + ); + return { status: "started", scriptId: script.id, @@ -420,7 +431,7 @@ export const make = Effect.gen(function* () { terminalId, cwd, async: script.async !== false, - ...(observed ? { completion: observed.completion } : {}), + ...(completion ? { completion } : {}), } as const; }); diff --git a/apps/server/src/project/RepositoryIdentityResolver.ts b/apps/server/src/project/RepositoryIdentityResolver.ts index 88ecb4186f67..2d7f5d02d02e 100644 --- a/apps/server/src/project/RepositoryIdentityResolver.ts +++ b/apps/server/src/project/RepositoryIdentityResolver.ts @@ -141,6 +141,7 @@ export const make = Effect.fn("RepositoryIdentityResolver.make")(function* ( ) { const processRunner = yield* ProcessRunner.ProcessRunner; const cacheCapacity = options.cacheCapacity ?? DEFAULT_REPOSITORY_IDENTITY_CACHE_CAPACITY; + const refine = options.refine ?? Effect.succeed; const repositoryRootCache = yield* Cache.makeWith( (cwd) => @@ -161,10 +162,9 @@ export const make = Effect.fn("RepositoryIdentityResolver.make")(function* ( (cacheKey) => resolveRepositoryIdentityFromCacheKey(cacheKey).pipe( Effect.provideService(ProcessRunner.ProcessRunner, processRunner), - Effect.flatMap((identity) => - identity !== null && options.refine - ? options.refine(identity).pipe(Effect.catch(() => Effect.succeed(identity))) - : Effect.succeed(identity), + Effect.filterOrElse( + (identity): identity is null => identity === null, + (identity) => refine(identity).pipe(Effect.orElseSucceed(() => identity)), ), ), { diff --git a/apps/server/src/project/T3ProjectFileLoader.ts b/apps/server/src/project/T3ProjectFileLoader.ts index 105e6b09a317..4ad874b9090b 100644 --- a/apps/server/src/project/T3ProjectFileLoader.ts +++ b/apps/server/src/project/T3ProjectFileLoader.ts @@ -68,7 +68,7 @@ export const make = Effect.gen(function* () { function* (workspaceRoot) { const filePath = path.join(workspaceRoot, T3_PROJECT_FILE_NAME); const raw = yield* fileSystem.readFileString(filePath).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ PlatformError: (error) => error.reason._tag === "NotFound" @@ -87,7 +87,7 @@ export const make = Effect.gen(function* () { return Option.none(); } return yield* decodeT3ProjectFileJson(raw.value).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ SchemaError: (error) => logT3ProjectFileLoadError( diff --git a/apps/server/src/provider/AntigravityInstallation.test.ts b/apps/server/src/provider/AntigravityInstallation.test.ts index e2bab831712a..de72ebcc4e23 100644 --- a/apps/server/src/provider/AntigravityInstallation.test.ts +++ b/apps/server/src/provider/AntigravityInstallation.test.ts @@ -342,6 +342,8 @@ it.layer(NodeServices.layer)("Antigravity installation", (it) => { if (!profile) return yield* Effect.die("Expected a disposable validation profile."); profiles.add(profile); const helper = command.args[0] === "-e"; + // The runtime unpacks straight into the disposable profile. + if (!helper) expect(command.options.env?.TMPDIR).toBe(profile); const output = yield* Queue.unbounded(); const exited = yield* Deferred.make(); const terminate = Deferred.succeed(exited, ChildProcessSpawner.ExitCode(0)).pipe( diff --git a/apps/server/src/provider/AntigravityInstallation.ts b/apps/server/src/provider/AntigravityInstallation.ts index 24eb4e3d6df8..f3c3d2a4d70a 100644 --- a/apps/server/src/provider/AntigravityInstallation.ts +++ b/apps/server/src/provider/AntigravityInstallation.ts @@ -315,7 +315,7 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma ); } const contents = yield* fs.readFileString(filePath); - return yield* Schema.decodeUnknownEffect(Schema.fromJsonString(schema))(contents); + return yield* Schema.decodeEffect(Schema.fromJsonString(schema))(contents); }); const executableFile = Effect.fn("AntigravityInstallation.executableFile")(function* ( @@ -476,6 +476,9 @@ export const makeAntigravityInstallation = Effect.fn("AntigravityInstallation.ma profileDirectory, platform, baseEnv: environment, + // The profile is scoped, so it cleans up the unpack; a shallow + // root keeps it under Windows' path limit. + tempDirectory: profileDirectory, }); const runtime = yield* makeAntigravityAcpRuntime({ spawn: buildAntigravityAcpSpawnInput({ diff --git a/apps/server/src/provider/CodexDeveloperInstructions.ts b/apps/server/src/provider/CodexDeveloperInstructions.ts index 6a7fee351bce..2ed593d159bc 100644 --- a/apps/server/src/provider/CodexDeveloperInstructions.ts +++ b/apps/server/src/provider/CodexDeveloperInstructions.ts @@ -1,23 +1,18 @@ import type { ProviderInteractionMode } from "@t3tools/contracts"; +import type { V2TurnStartParams__AdditionalContextEntry } from "effect-codex-app-server/schema"; import { buildRuntimeInstructions } from "./RuntimeInstructions.ts"; -const T3_CODE_BROWSER_TOOL_INSTRUCTIONS = ` - -## T3 Code collaborative browser +const T3_CODE_BROWSER_TOOL_INSTRUCTIONS = `## T3 Code collaborative browser You are running inside T3 Code. The \`t3-code\` MCP server is the product-native collaborative browser shared with the user. When it exposes \`preview_*\` tools, prefer those tools for browser navigation, inspection, interaction, screenshots, and recordings. For browser work, first call \`preview_status\`. If no automation-capable preview is attached, call \`preview_open\` before concluding that the browser is unavailable. Then use \`preview_navigate\`, \`preview_snapshot\`, and the focused interaction tools. Prefer snapshot-provided locators over coordinates. -Do not switch to global browser skills, Chrome, Node REPL browser automation, standalone Playwright, or agent-browser merely because the preview is initially closed or a first call fails. Use an alternative browser system only when the T3 preview tools are absent, the user explicitly requests another browser, or \`preview_open\` returns an explicit unsupported/unavailable error. A failed T3 preview tool call should be inspected and retried with corrected arguments when the error is actionable. -`; - -const T3_CODE_DEVICE_TOOL_INSTRUCTIONS = ` +Do not switch to global browser skills, Chrome, Node REPL browser automation, standalone Playwright, or agent-browser merely because the preview is initially closed or a first call fails. Use an alternative browser system only when the T3 preview tools are absent, the user explicitly requests another browser, or \`preview_open\` returns an explicit unsupported/unavailable error. A failed T3 preview tool call should be inspected and retried with corrected arguments when the error is actionable.`; -## T3 Code devices +const T3_CODE_DEVICE_TOOL_INSTRUCTIONS = `## T3 Code devices -The \`t3-code\` MCP server also exposes \`device_*\` tools for iOS Simulators and Android Emulators on this environment. For mobile verification, call \`device_list\`, then \`device_open\` so the user can watch the device in their Device panel; its result explains how to drive the device. Driving happens through the \`agent-device\` CLI, which is on PATH. Keep the host config and session flags returned by \`device_open\` on every command so concurrent devices stay independent: prefer \`agent-device snapshot -i\` refs over coordinates, and use \`device_screenshot\` when you need to see the screen. Do not call simctl, adb, xcrun, or serve-sim directly while these tools are present. If \`device_list\` reports a platform as unavailable, say so instead of trying another route. -`; +The \`t3-code\` MCP server also exposes \`device_*\` tools for iOS Simulators and Android Emulators on this environment. For mobile verification, call \`device_list\`, then \`device_open\` so the user can watch the device in their Device panel; its result explains how to drive the device. Driving happens through the \`agent-device\` CLI, which is on PATH. Keep the host config and session flags returned by \`device_open\` on every command so concurrent devices stay independent: prefer \`agent-device snapshot -i\` refs over coordinates, and use \`device_screenshot\` when you need to see the screen. Do not call simctl, adb, xcrun, or serve-sim directly while these tools are present. If \`device_list\` reports a platform as unavailable, say so instead of trying another route.`; export interface T3CodeToolAvailability { readonly browser: boolean; @@ -36,16 +31,17 @@ const normalizeAvailability = ( * from Playwright, agent-browser, and raw simctl/adb, so leaving them in would * talk it out of the only automation it still has. */ -const browserToolInstructions = (availability: boolean | T3CodeToolAvailability): string => { +const toolInstructions = (availability: boolean | T3CodeToolAvailability): string => { const tools = normalizeAvailability(availability); - return `${tools.browser ? T3_CODE_BROWSER_TOOL_INSTRUCTIONS : ""}${ - tools.device ? T3_CODE_DEVICE_TOOL_INSTRUCTIONS : "" - }`; + return [ + tools.browser ? T3_CODE_BROWSER_TOOL_INSTRUCTIONS : "", + tools.device ? T3_CODE_DEVICE_TOOL_INSTRUCTIONS : "", + ] + .filter(Boolean) + .join("\n\n"); }; -const codexPlanModeDeveloperInstructions = ( - browserToolsAvailable: boolean | T3CodeToolAvailability, -): string => `# Plan Mode (Conversational) +const CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS = `# Plan Mode (Conversational) You work in 3 phases, and you should *chat your way* to a great plan before finalizing it. A great plan is very detailed-intent- and implementation-wise-so that it can be handed to another engineer or agent to be implemented right away. It must be **decision complete**, where the implementer does not need to make any decisions. @@ -173,12 +169,9 @@ Do not ask "should I proceed?" in the final output. The user can easily switch o Only produce at most one \`\` block per turn, and only when you are presenting a complete spec. If the user stays in Plan mode and asks for revisions after a prior \`\`, any new \`\` must be a complete replacement. If the user indicates that the prior plan is not acceptable but does not provide enough information to produce a complete replacement, address the concern and continue planning without producing a \`\` block. If the follow-up neither requires changes nor calls the plan into question (e.g. clarifying question), answer it before the block, then reproduce the prior \`\` unchanged. -${browserToolInstructions(browserToolsAvailable)} `; -const codexDefaultModeDeveloperInstructions = ( - browserToolsAvailable: boolean | T3CodeToolAvailability, -): string => `# Collaboration Mode: Default +const CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS = `# Collaboration Mode: Default You are now in Default mode. Any previous instructions for other modes (e.g. Plan mode) are no longer active. @@ -189,29 +182,46 @@ Your active mode changes only when new developer instructions with a different \ Use the \`request_user_input\` tool only when it is listed in the available tools for this turn. In Default mode, strongly prefer making reasonable assumptions and executing the user's request rather than stopping to ask questions. If you absolutely must ask a question because the answer cannot be discovered from local context and a reasonable assumption would be risky, ask the user directly with a concise plain-text question. Never write a multiple choice question as a textual assistant message. -${browserToolInstructions(browserToolsAvailable)} `; export interface CodexRuntimeInfo { readonly model: string; + readonly modelName?: string | undefined; readonly reasoningEffort: string; } -export function buildCodexDeveloperInstructions( - interactionMode: ProviderInteractionMode, +/** Mode prompt for `turn/start.collaborationMode.settings.developer_instructions`. */ +export function buildCodexDeveloperInstructions(interactionMode: ProviderInteractionMode): string { + return interactionMode === "plan" + ? CODEX_PLAN_MODE_DEVELOPER_INSTRUCTIONS + : CODEX_DEFAULT_MODE_DEVELOPER_INSTRUCTIONS; +} + +/** + * T3 Code context for `turn/start.additionalContext`. Codex renders each entry + * as a `value` developer message and resends it only when the value + * changes. + * + * This must stay out of the collaboration mode: when the model catalog ships + * its own text for a mode, as newer models do, Codex uses that text and drops + * the client's `developer_instructions` entirely. + */ +export function buildCodexAdditionalContext( runtime: CodexRuntimeInfo, /** * Whether the `t3-code` MCP server is attached to this turn. Callers derive * it from the session's actual MCP configuration rather than re-reading the * setting, so the prompt cannot claim tools the turn doesn't have. */ - browserToolsAvailable: boolean | T3CodeToolAvailability = true, -): string { - const base = - interactionMode === "plan" - ? codexPlanModeDeveloperInstructions(browserToolsAvailable) - : codexDefaultModeDeveloperInstructions(browserToolsAvailable); - return `${base} - -${buildRuntimeInstructions({ harness: "Codex", ...runtime })}`; + toolsAvailable: boolean | T3CodeToolAvailability = true, +): Record { + const tools = toolInstructions(toolsAvailable); + // Separate keys keep each value under Codex's per-entry token cap. + return { + t3_code_runtime: { + kind: "application", + value: buildRuntimeInstructions({ harness: "Codex", ...runtime }), + }, + ...(tools ? { t3_code_tools: { kind: "application", value: tools } } : {}), + }; } diff --git a/apps/server/src/provider/Drivers/AntigravityDriver.test.ts b/apps/server/src/provider/Drivers/AntigravityDriver.test.ts index a02c086d6ad8..6345a7b80a97 100644 --- a/apps/server/src/provider/Drivers/AntigravityDriver.test.ts +++ b/apps/server/src/provider/Drivers/AntigravityDriver.test.ts @@ -31,8 +31,7 @@ import { } from "../AntigravityInstallation.ts"; import { ANTIGRAVITY_AUTH_STDOUT_PREFIX, - resolveAntigravityProfileDirectory, - resolveAntigravityRuntimeTempDirectory, + resolveAntigravityInstanceDirectories, } from "../antigravityAuthSupport.ts"; import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; import { ProviderSecretResolverPassthroughLayer } from "../Services/ProviderSecretResolver.ts"; @@ -75,7 +74,8 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( new URL("../../../scripts/acp-mock-agent.ts", import.meta.url), ); const requestLog = path.join(root, "requests.jsonl"); - const profileDirectory = resolveAntigravityProfileDirectory(config.stateDir, instanceId); + const directories = yield* resolveAntigravityInstanceDirectories(config.stateDir, instanceId); + const profileDirectory = directories.profile; const instancePath = `${path.join(root, "instance-bin")}:${baseEnv.PATH ?? ""}`; const makeExecutable = Effect.fn("AntigravityDriverTest.makeExecutable")(function* ( @@ -234,6 +234,7 @@ const makeHarness = Effect.fn("makeAntigravityDriverHarness")(function* ( fs, path, profileDirectory, + directories, instancePath, first, second, @@ -477,7 +478,7 @@ it.layer(testLayer)("AntigravityDriver", (it) => { () => Effect.gen(function* () { const h = yield* makeHarness(); - const tempRoot = resolveAntigravityRuntimeTempDirectory(h.profileDirectory); + const tempRoot = h.directories.runtimeTemp; yield* h.refresh(); yield* h.refresh(); const directories = h.launches.flatMap((launch) => @@ -500,12 +501,17 @@ it.layer(testLayer)("AntigravityDriver", (it) => { const path = yield* Path.Path; const config = yield* ServerConfig; const instanceId = ProviderInstanceId.make("antigravity-orphan-sweep"); - const tempRoot = resolveAntigravityRuntimeTempDirectory( - resolveAntigravityProfileDirectory(config.stateDir, instanceId), + const directories = yield* resolveAntigravityInstanceDirectories( + config.stateDir, + instanceId, ); - const orphan = path.join(tempRoot, "run-orphan", "_MEI123", "google3"); - yield* fs.makeDirectory(orphan, { recursive: true }); - yield* fs.writeFileString(path.join(orphan, "payload.bin"), "stale"); + // Older builds unpacked inside the profile. + const legacyRoot = path.join(directories.profile, "antigravity-acp", "tmp"); + for (const root of [directories.runtimeTemp, legacyRoot]) { + const orphan = path.join(root, "run-orphan", "_MEI123", "google3"); + yield* fs.makeDirectory(orphan, { recursive: true }); + yield* fs.writeFileString(path.join(orphan, "payload.bin"), "stale"); + } yield* AntigravityDriver.create({ instanceId, displayName: "Sweep", @@ -521,7 +527,8 @@ it.layer(testLayer)("AntigravityDriver", (it) => { }), ), ); - expect(yield* fs.exists(tempRoot)).toBe(false); + expect(yield* fs.exists(directories.runtimeTemp)).toBe(false); + expect(yield* fs.exists(legacyRoot)).toBe(false); }).pipe(Effect.scoped), ); diff --git a/apps/server/src/provider/Drivers/AntigravityDriver.ts b/apps/server/src/provider/Drivers/AntigravityDriver.ts index 3ae94cd8ec14..b8fc1ff43f56 100644 --- a/apps/server/src/provider/Drivers/AntigravityDriver.ts +++ b/apps/server/src/provider/Drivers/AntigravityDriver.ts @@ -33,8 +33,7 @@ import { buildAntigravityAcpSpawnInput, isAntigravitySignInRequiredError, prepareAntigravityProfile, - resolveAntigravityProfileDirectory, - resolveAntigravityRuntimeTempDirectory, + resolveAntigravityInstanceDirectories, type AntigravityAuthConfig, } from "../antigravityAuthSupport.ts"; import { @@ -108,15 +107,34 @@ export const AntigravityDriver: ProviderDriver + new ProviderDriverError({ + driver: DRIVER, + instanceId, + detail: "Could not resolve the Antigravity profile directory.", + cause, + }), + ), ); + const profileDirectory = directories.profile; // No process of this instance exists yet, so every runtime temp - // directory left under the profile is an orphan from a killed server. - yield* removeAntigravityRuntimeTempDirs( - resolveAntigravityRuntimeTempDirectory(profileDirectory), - ).pipe(Effect.provideService(FileSystem.FileSystem, fileSystem)); + // directory it owns is an orphan from a killed server. Older builds + // unpacked inside the profile. + for (const directory of [ + directories.runtimeTemp, + path.join(profileDirectory, "antigravity-acp", "tmp"), + ]) { + yield* removeAntigravityRuntimeTempDirs(directory).pipe( + Effect.provideService(FileSystem.FileSystem, fileSystem), + ); + } const continuationIdentity = defaultProviderContinuationIdentity({ driverKind: DRIVER, instanceId, @@ -170,6 +188,7 @@ export const AntigravityDriver: ProviderDriver( Effect.catchTags({ PlatformError: (cause) => cause.reason._tag === "NotFound" - ? Effect.succeed(undefined) + ? Effect.undefined : Effect.fail( new AntigravitySkillsProbeError({ reason: "filesystem-error", path, cause }), ), diff --git a/apps/server/src/provider/Drivers/CodexDriver.ts b/apps/server/src/provider/Drivers/CodexDriver.ts index 7a092a10451d..29ac315029db 100644 --- a/apps/server/src/provider/Drivers/CodexDriver.ts +++ b/apps/server/src/provider/Drivers/CodexDriver.ts @@ -177,18 +177,6 @@ export const CodexDriver: ProviderDriver = { ), ); - // `makeCodexAdapter` and `makeCodexTextGeneration` have `never` error - // channels at construction time — their failure modes are all on the - // per-operation closures they return. No `mapError` wrapper is needed - // here; the registry only has to worry about snapshot-build and - // spawner-availability failures surfaced from `checkCodexProviderStatus` - // below. - const adapter = yield* makeCodexAdapter(effectiveConfig, { - instanceId, - environment: processEnv, - ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}), - }); - // Build a managed snapshot whose settings never change — mutations come // in as instance rebuilds from the registry rather than in-place // updates. Pre-provide `ChildProcessSpawner` so the check fits @@ -243,11 +231,20 @@ export const CodexDriver: ProviderDriver = { }), ), ); - const textGeneration = yield* makeCodexTextGeneration( - effectiveConfig, - processEnv, - snapshot.getSnapshot.pipe(Effect.map((value) => value.models)), - ); + const models = snapshot.getSnapshot.pipe(Effect.map((value) => value.models)); + // `makeCodexAdapter` and `makeCodexTextGeneration` have `never` error + // channels at construction time — their failure modes are all on the + // per-operation closures they return. No `mapError` wrapper is needed + // here; the registry only has to worry about snapshot-build and + // spawner-availability failures surfaced from `checkCodexProviderStatus` + // above. + const adapter = yield* makeCodexAdapter(effectiveConfig, { + instanceId, + environment: processEnv, + models, + ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}), + }); + const textGeneration = yield* makeCodexTextGeneration(effectiveConfig, processEnv, models); const snapshotForCwd = (cwd: string) => !effectiveConfig.enabled ? snapshot.getSnapshot diff --git a/apps/server/src/provider/Drivers/CursorDriver.ts b/apps/server/src/provider/Drivers/CursorDriver.ts index 7c7e68d41918..07c3be5d6dca 100644 --- a/apps/server/src/provider/Drivers/CursorDriver.ts +++ b/apps/server/src/provider/Drivers/CursorDriver.ts @@ -145,12 +145,23 @@ export const CursorDriver: ProviderDriver = { processEnv, modelDiscovery.discover, ).pipe( - Effect.flatMap((snapshot) => - effectiveConfig.enabled && snapshot.installed && snapshot.auth.status === "authenticated" - ? readCursorUsageLimits(effectiveConfig, processEnv).pipe( - Effect.map((usageLimits) => ({ ...snapshot, usageLimits })), - ) - : Effect.succeed(snapshot), + Effect.filterOrElse( + (snapshot) => + !( + effectiveConfig.enabled && + snapshot.installed && + snapshot.auth.status === "authenticated" + ), + (snapshot) => + Effect.gen(function* () { + const settings = yield* serverSettings.getSettings; + const usageLimits = yield* readCursorUsageLimits( + effectiveConfig, + processEnv, + settings.cursorKeychainUsageEnabled, + ); + return { ...snapshot, usageLimits }; + }), ), Effect.map(stampIdentity), Effect.provideService(HttpClient.HttpClient, httpClient), diff --git a/apps/server/src/provider/Drivers/GrokDriver.test.ts b/apps/server/src/provider/Drivers/GrokDriver.test.ts new file mode 100644 index 000000000000..f54de5ce35eb --- /dev/null +++ b/apps/server/src/provider/Drivers/GrokDriver.test.ts @@ -0,0 +1,99 @@ +import * as NodeServices from "@effect/platform-node/NodeServices"; +import { expect, it } from "@effect/vitest"; +import { ProviderInstanceId } from "@t3tools/contracts"; +import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; +import { HttpClient } from "effect/unstable/http"; +import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner"; + +import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts"; +import { ServerConfig } from "../../config.ts"; +import { ServerSettingsService } from "../../serverSettings.ts"; +import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; +import { ProviderSecretResolverPassthroughLayer } from "../Services/ProviderSecretResolver.ts"; +import { GrokDriver } from "./GrokDriver.ts"; + +const testLayer = ServerConfig.layerTest(process.cwd(), { + prefix: "t3-grok-driver-update-", +}).pipe( + Layer.provideMerge(NodeServices.layer), + Layer.provideMerge(ServerSettingsService.layerTest()), + Layer.provideMerge( + Layer.mock(BackgroundPolicy.BackgroundPolicy)({ + shouldRunScopeWork: () => Effect.succeed(false), + }), + ), + Layer.provideMerge(Layer.succeed(ProviderEventLoggers, NoOpProviderEventLoggers)), + Layer.provideMerge(ProviderSecretResolverPassthroughLayer), + Layer.provideMerge( + Layer.succeed( + HttpClient.HttpClient, + HttpClient.make(() => Effect.die("Disabled Grok must not make an HTTP request")), + ), + ), +); + +const noSpawner = ChildProcessSpawner.make(() => + Effect.die("Disabled Grok must not spawn a process"), +); + +// The `#!/bin/sh` stub below cannot be resolved as an executable on Windows. +const windowsHost = HostProcessPlatform.defaultValue() === "win32"; + +it.layer(testLayer)("GrokDriver", (it) => { + it.effect.skipIf(windowsHost)("updates through the configured executable's own updater", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-grok-driver-" }); + const grokHome = path.join(tempDir, "Grok Home"); + const binaryPath = path.join(grokHome, "bin", "grok"); + yield* fs.makeDirectory(path.dirname(binaryPath), { recursive: true }); + yield* fs.writeFileString(binaryPath, "#!/bin/sh\n"); + yield* fs.chmod(binaryPath, 0o755); + + const instance = yield* GrokDriver.create({ + instanceId: ProviderInstanceId.make("grok-update"), + displayName: "Grok test", + enabled: false, + environment: [{ name: "GROK_HOME", value: grokHome, sensitive: false }], + config: { ...GrokDriver.defaultConfig(), binaryPath }, + }); + + const capabilities = yield* instance.snapshot.resolveMaintenance(); + expect(capabilities.packageName).toBe("@xai-official/grok"); + expect(capabilities.update).toMatchObject({ + command: `'${binaryPath}' update`, + executable: binaryPath, + args: ["update"], + }); + // `grok update` installs under GROK_HOME, so it must target this instance's home. + expect(capabilities.update?.env?.GROK_HOME).toBe(grokHome); + }).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawner), + Effect.scoped, + ), + ); + + it.effect("stays manual-only when the configured executable does not exist", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-grok-missing-" }); + const instance = yield* GrokDriver.create({ + instanceId: ProviderInstanceId.make("grok-missing"), + displayName: "Grok test", + enabled: false, + environment: [], + config: { ...GrokDriver.defaultConfig(), binaryPath: path.join(tempDir, "grok") }, + }); + expect((yield* instance.snapshot.resolveMaintenance()).update).toBeNull(); + }).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, noSpawner), + Effect.scoped, + ), + ); +}); diff --git a/apps/server/src/provider/Drivers/GrokDriver.ts b/apps/server/src/provider/Drivers/GrokDriver.ts index a9147e3fa480..5f4cdfe8b3bb 100644 --- a/apps/server/src/provider/Drivers/GrokDriver.ts +++ b/apps/server/src/provider/Drivers/GrokDriver.ts @@ -19,7 +19,7 @@ import { enrichGrokSnapshot, } from "../Layers/GrokProvider.ts"; import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts"; -import { readGrokUsageLimits } from "../Layers/grokUsageLimits.ts"; +import { readGrokAccount } from "../Layers/grokUsageLimits.ts"; import { makeManagedServerProvider } from "../makeManagedServerProvider.ts"; import { defaultProviderContinuationIdentity, @@ -30,7 +30,13 @@ import { withInstanceIdentity } from "./instanceIdentity.ts"; import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts"; import { ProviderSecretResolver } from "../Services/ProviderSecretResolver.ts"; import { discoverGrokSkills } from "./GrokSkills.ts"; -import { makeManualOnlyProviderMaintenanceCapabilities } from "../providerMaintenance.ts"; +import { + makeCachedProviderMaintenanceResolution, + makeManualOnlyProviderMaintenanceCapabilities, + makeProviderMaintenanceCapabilities, + type ProviderMaintenanceCapabilitiesResolver, + resolveProviderMaintenanceCapabilitiesEffect, +} from "../providerMaintenance.ts"; import { haveProviderSnapshotSettingsChanged, makeProviderSnapshotSettingsSource, @@ -39,10 +45,32 @@ import { const decodeGrokSettings = Schema.decodeSync(GrokSettings); const DRIVER_KIND = ProviderDriverKind.make("grok"); -const MAINTENANCE_CAPABILITIES = makeManualOnlyProviderMaintenanceCapabilities({ - provider: DRIVER_KIND, - packageName: null, -}); +// npm's `latest` tracks Grok's stable channel, the one `grok update` installs +// by default, so the registry stays the source for "latest". +const GROK_NPM_PACKAGE = "@xai-official/grok"; +// `grok update` finds the installer that owns the binary itself, so the +// resolved executable is its own updater. It installs under `GROK_HOME`, so it +// runs with the instance's environment. No executable means nothing to update, +// not "whatever is on PATH". +const UPDATE: ProviderMaintenanceCapabilitiesResolver = { + resolve: (context) => + Effect.succeed( + context + ? makeProviderMaintenanceCapabilities({ + provider: DRIVER_KIND, + packageName: GROK_NPM_PACKAGE, + updateExecutable: context.resolvedCommandPath, + updateArgs: ["update"], + updateLockKey: "grok", + platform: context.platform, + env: context.env, + }) + : makeManualOnlyProviderMaintenanceCapabilities({ + provider: DRIVER_KIND, + packageName: GROK_NPM_PACKAGE, + }), + ), +}; export type GrokDriverEnv = | BackgroundPolicy.BackgroundPolicy @@ -90,6 +118,16 @@ export const GrokDriver: ProviderDriver = { continuationGroupKey: continuationIdentity.continuationKey, }); const effectiveConfig = { ...config, enabled } satisfies GrokSettings; + const resolveMaintenance = yield* makeCachedProviderMaintenanceResolution( + resolveProviderMaintenanceCapabilitiesEffect(UPDATE, { + binaryPath: effectiveConfig.binaryPath, + env: processEnv, + }).pipe( + Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), + Effect.provideService(FileSystem.FileSystem, fileSystem), + Effect.provideService(Path.Path, path), + ), + ); const adapter = yield* makeGrokAdapter(effectiveConfig, { environment: processEnv, ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}), @@ -98,12 +136,22 @@ export const GrokDriver: ProviderDriver = { const textGeneration = yield* makeGrokTextGeneration(effectiveConfig, processEnv); const checkProvider = checkGrokProviderStatus(effectiveConfig, processEnv, cwd).pipe( - Effect.flatMap((snapshot) => - effectiveConfig.enabled && snapshot.installed && snapshot.auth.status === "authenticated" - ? readGrokUsageLimits(processEnv).pipe( - Effect.map((usageLimits) => ({ ...snapshot, usageLimits })), - ) - : Effect.succeed(snapshot), + Effect.filterOrElse( + (snapshot) => + !( + effectiveConfig.enabled && + snapshot.installed && + snapshot.auth.status === "authenticated" + ), + (snapshot) => + readGrokAccount(processEnv).pipe( + // The email lets clients recognize one account signed in on several environments. + Effect.map(({ email, usageLimits }) => ({ + ...snapshot, + auth: email ? { ...snapshot.auth, email } : snapshot.auth, + usageLimits, + })), + ), ), Effect.map(stampIdentity), Effect.provideService(HttpClient.HttpClient, httpClient), @@ -115,7 +163,7 @@ export const GrokDriver: ProviderDriver = { const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); const snapshot = yield* makeManagedServerProvider>({ - resolveMaintenance: () => Effect.succeed(MAINTENANCE_CAPABILITIES), + resolveMaintenance, getSettings: snapshotSettings.getSettings, streamSettings: snapshotSettings.streamSettings, haveSettingsChanged: haveProviderSnapshotSettingsChanged, @@ -123,13 +171,17 @@ export const GrokDriver: ProviderDriver = { buildInitialGrokProviderSnapshot(settings.provider).pipe(Effect.map(stampIdentity)), checkProvider, enrichSnapshot: ({ settings, snapshot: currentSnapshot, publishSnapshot }) => - enrichGrokSnapshot({ - snapshot: currentSnapshot, - maintenanceCapabilities: MAINTENANCE_CAPABILITIES, - enableProviderUpdateChecks: settings.enableProviderUpdateChecks, - publishSnapshot, - httpClient, - }), + resolveMaintenance().pipe( + Effect.flatMap((maintenanceCapabilities) => + enrichGrokSnapshot({ + snapshot: currentSnapshot, + maintenanceCapabilities, + enableProviderUpdateChecks: settings.enableProviderUpdateChecks, + publishSnapshot, + httpClient, + }), + ), + ), }).pipe( Effect.mapError( (cause) => diff --git a/apps/server/src/provider/Layers/AntigravityAdapter.ts b/apps/server/src/provider/Layers/AntigravityAdapter.ts index 0bab3a98a537..e12a320c4d48 100644 --- a/apps/server/src/provider/Layers/AntigravityAdapter.ts +++ b/apps/server/src/provider/Layers/AntigravityAdapter.ts @@ -844,7 +844,7 @@ export const makeAntigravityAdapter = Effect.fn("makeAntigravityAdapter")(functi const model = yield* applyAntigravityAcpModelSelection({ runtime, model: input.modelSelection?.model, - defaultModel: yield* options.defaultModel ?? Effect.succeed(undefined), + defaultModel: yield* options.defaultModel ?? Effect.undefined, mapError: (cause) => cause, }); yield* runtime.setMode(antigravityPermissionMode(input.runtimeMode)); @@ -1035,7 +1035,7 @@ export const makeAntigravityAdapter = Effect.fn("makeAntigravityAdapter")(functi const model = resolveAntigravityModel({ configOptions, model: requestedModel, - defaultModel: yield* options.defaultModel ?? Effect.succeed(undefined), + defaultModel: yield* options.defaultModel ?? Effect.undefined, }); const availableModels = antigravityModelOptions(configOptions); if (model && !availableModels.some((option) => option.value === model)) { diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 76ace5c55f1c..6609e728816e 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics abortControllerInEffect:off - Tests hand-built AbortSignals to the SDK query stub to exercise cancellation. import { buildRuntimeInstructions } from "../RuntimeInstructions.ts"; // @effect-diagnostics nodeBuiltinImport:off import * as NodeFS from "node:fs"; diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index f33bc141f329..13275a56aae5 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -5379,6 +5379,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( ) => { // SDK history helpers read process.env. Isolate the provider's home instead // of changing the server's environment while other providers are running. + // @effect-diagnostics-next-line runEffectInsideEffect:off - SDK callback runs outside the fiber; the spawn is self-contained const result = await Effect.runPromise( spawnAndCollect( process.execPath, @@ -5569,7 +5570,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( for (const result of results) { if (result._tag === "Failure") { - return yield* Effect.fail(result.failure); + return yield* result.failure; } } }); diff --git a/apps/server/src/provider/Layers/CodexAdapter.test.ts b/apps/server/src/provider/Layers/CodexAdapter.test.ts index 9f464bdaa177..8380c2dc1fdd 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.test.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.test.ts @@ -225,7 +225,7 @@ const providerSessionDirectoryTestLayer = Layer.succeed(ProviderSessionDirectory recordImportedTranscript: () => Effect.die("unused"), getProvider: () => Effect.die(new Error("ProviderSessionDirectory.getProvider is not used in test")), - getBinding: () => Effect.succeed(Option.none()), + getBinding: () => Effect.succeedNone, listThreadIds: () => Effect.succeed([]), listBindings: () => Effect.succeed([]), }); @@ -2383,6 +2383,7 @@ lifecycleLayer("CodexAdapterLive lifecycle", (it) => { method: "item/tool/requestUserInput", requestId: ApprovalRequestId.make("req-user-input-1"), payload: { + isBlocking: true, itemId: "item-user-input-1", threadId: "thread-1", turnId: "turn-1", diff --git a/apps/server/src/provider/Layers/CodexAdapter.ts b/apps/server/src/provider/Layers/CodexAdapter.ts index 0ecc9693ab04..baa8d846f7c6 100644 --- a/apps/server/src/provider/Layers/CodexAdapter.ts +++ b/apps/server/src/provider/Layers/CodexAdapter.ts @@ -22,6 +22,7 @@ import { type ToolActivityNativeAppReference, type ToolActivitySource, type ProviderUserInputAnswers, + type ServerProviderModel, RuntimeItemId, RuntimeRequestId, RuntimeTaskId, @@ -89,6 +90,8 @@ const PROVIDER = ProviderDriverKind.make("codex"); export interface CodexAdapterLiveOptions { readonly instanceId?: ProviderInstanceId; readonly environment?: NodeJS.ProcessEnv; + /** The provider's model list; supplies model display names for runtime info. */ + readonly models?: Effect.Effect>; readonly makeRuntime?: ( options: CodexSessionRuntimeOptions, ) => Effect.Effect< @@ -2277,6 +2280,7 @@ export const makeCodexAdapter = Effect.fn("makeCodexAdapter")(function* ( providerInstanceId: boundInstanceId, cwd: input.cwd ?? process.cwd(), binaryPath: codexConfig.binaryPath, + ...(options?.models ? { models: options.models } : {}), launchArgs: resolveCodexLaunchArgs(codexConfig.launchArgs, options?.environment), ...(options?.environment ? { environment: options.environment } : {}), ...(codexConfig.homePath ? { homePath: codexConfig.homePath } : {}), diff --git a/apps/server/src/provider/Layers/CodexCollabRuntime.integration.test.ts b/apps/server/src/provider/Layers/CodexCollabRuntime.integration.test.ts index 2a9fb56c186a..5405169926f5 100644 --- a/apps/server/src/provider/Layers/CodexCollabRuntime.integration.test.ts +++ b/apps/server/src/provider/Layers/CodexCollabRuntime.integration.test.ts @@ -868,3 +868,76 @@ describe("CodexSessionRuntime collab integration", () => { ); } }); + +describe("CodexSessionRuntime compaction", () => { + it.effect("restores T3 context after the root thread compacts", () => + Effect.gen(function* () { + const compacted = (threadId: string) => ({ + method: "item/completed", + params: { + threadId, + turnId: `${threadId}-turn`, + completedAtMs: 0, + item: { type: "contextCompaction", id: `compaction-${threadId}` }, + }, + }); + const script = { + rootThreadId: ROOT, + recordRequests: true, + // A child's compaction must not inject into the root thread. + notifications: [compacted(CHILD_A), compacted(ROOT)], + }; + // @effect-diagnostics-next-line preferSchemaOverJson:off + NodeFS.writeFileSync(scriptPath, JSON.stringify(script), "utf8"); + NodeFS.rmSync(`${scriptPath}.requests`, { force: true }); + yield* Effect.addFinalizer(() => + Effect.sync(() => { + NodeFS.rmSync(scriptPath, { force: true }); + NodeFS.rmSync(`${scriptPath}.requests`, { force: true }); + }), + ); + + const runtime = yield* makeCodexSessionRuntime({ + threadId: ThreadId.make("thread-compaction-context"), + binaryPath: peerPath, + cwd: NodeOS.tmpdir(), + runtimeMode: "full-access", + environment: { ...process.env, T3_CODEX_COLLAB_SCRIPT: scriptPath }, + models: Effect.succeed([ + { slug: "gpt-5.6-sol", name: "GPT-5.6 Sol", isCustom: false, capabilities: null }, + ]), + }); + const completedFiber = yield* runtime.events.pipe( + Stream.filter((event) => event.method === "turn/completed"), + Stream.take(1), + Stream.runCollect, + Effect.forkScoped, + ); + + yield* runtime.start(); + yield* runtime.sendTurn({ input: "keep going", interactionMode: "default" }); + yield* Fiber.join(completedFiber); + + // The restore is awaited before later notifications, so it has landed. + const requests = readRecordedRequests(); + assert.lengthOf(requests, 1); + const [inject] = requests; + assert.isDefined(inject); + assert.equal(inject.method, "thread/inject_items"); + assert.equal(inject.params.threadId, ROOT); + const texts = ( + inject.params.items as ReadonlyArray<{ role: string; content: [{ text: string }] }> + ).map((item) => { + assert.equal(item.role, "developer"); + return item.content[0].text; + }); + assert.lengthOf(texts, 1); + assert.match( + texts[0] ?? "", + /^.*as GPT-5\.6 Sol \(model slug: gpt-5\.6-sol\).*<\/t3_code_runtime>$/s, + ); + + yield* runtime.close; + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), + ); +}); diff --git a/apps/server/src/provider/Layers/CodexProvider.ts b/apps/server/src/provider/Layers/CodexProvider.ts index cdf40f73b1bd..d99b97150c5c 100644 --- a/apps/server/src/provider/Layers/CodexProvider.ts +++ b/apps/server/src/provider/Layers/CodexProvider.ts @@ -441,7 +441,7 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun requestAllCodexModels(client), // Usage is an enrichment: a failure or a slow answer degrades to "no // usage this probe" rather than costing the account and models. - client.request("account/rateLimits/read", undefined).pipe( + client.request("account/rateLimits/read", null).pipe( Effect.map((response): CodexRateLimitsProbe => ({ snapshot: response.rateLimits, rateLimitsByLimitId: response.rateLimitsByLimitId, diff --git a/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts b/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts index ec113ab7c521..e315c9ab20fe 100644 --- a/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts +++ b/apps/server/src/provider/Layers/CodexSessionRuntime.test.ts @@ -9,7 +9,10 @@ import * as CodexErrors from "effect-codex-app-server/errors"; import * as CodexRpc from "effect-codex-app-server/rpc"; import * as EffectCodexSchema from "effect-codex-app-server/schema"; -import { buildCodexDeveloperInstructions } from "../CodexDeveloperInstructions.ts"; +import { + buildCodexAdditionalContext, + buildCodexDeveloperInstructions, +} from "../CodexDeveloperInstructions.ts"; import { codexSessionAppServerArgs } from "./codexLaunchArgs.ts"; import { buildTurnStartParams, @@ -90,25 +93,28 @@ describe("Codex thread history", () => { ); } - it.effect("keeps the count-based rollback API for older threads", () => + it.effect("surfaces Codex rejecting a revert of a legacy thread", () => Effect.gen(function* () { + const rejection = CodexErrors.CodexAppServerRequestError.invalidRequest( + "thread/revert only supports paginated threads", + ); const client: Parameters[0] = { - raw: { request: () => Effect.succeed({ thread: {} }) }, - request: ( - method: M, - params: CodexRpc.ClientRequestParamsByMethod[M], - ) => { - NodeAssert.equal(method, "thread/rollback"); - NodeAssert.deepEqual(params, { threadId: "legacy-thread", numTurns: 2 }); + raw: { + request: (method) => { + if (method === "thread/read") return Effect.succeed({ thread: {} }); + if (method === "thread/revert") return Effect.fail(rejection); + return Effect.die(`Unexpected raw request: ${method}`); + }, + }, + request: (method: M) => { + NodeAssert.equal(method, "thread/read"); return Effect.succeed({ - thread: { id: "legacy-thread", turns: [] }, + thread: { id: "legacy-thread", turns: [{ id: "turn-1", items: [] }] }, } as unknown as CodexRpc.ClientRequestResponsesByMethod[M]); }, }; - NodeAssert.deepEqual(yield* rollbackCodexThread(client, "legacy-thread", 2), { - threadId: "legacy-thread", - turns: [], - }); + const error = yield* Effect.flip(rollbackCodexThread(client, "legacy-thread", 1)); + NodeAssert.strictEqual(error, rejection); }), ); }); @@ -229,12 +235,13 @@ describe("buildTurnStartParams", () => { settings: { model: "gpt-5.3-codex", reasoning_effort: "medium", - developer_instructions: buildCodexDeveloperInstructions("plan", { - model: "gpt-5.3-codex", - reasoningEffort: "medium", - }), + developer_instructions: buildCodexDeveloperInstructions("plan"), }, }, + additionalContext: buildCodexAdditionalContext({ + model: "gpt-5.3-codex", + reasoningEffort: "medium", + }), }); }); @@ -278,12 +285,13 @@ describe("buildTurnStartParams", () => { settings: { model: "gpt-5.3-codex", reasoning_effort: "medium", - developer_instructions: buildCodexDeveloperInstructions("default", { - model: "gpt-5.3-codex", - reasoningEffort: "medium", - }), + developer_instructions: buildCodexDeveloperInstructions("default"), }, }, + additionalContext: buildCodexAdditionalContext({ + model: "gpt-5.3-codex", + reasoningEffort: "medium", + }), }); }); @@ -300,9 +308,29 @@ describe("buildTurnStartParams", () => { const settings = params.collaborationMode?.settings; NodeAssert.equal(settings?.model, DEFAULT_MODEL); NodeAssert.equal(settings?.reasoning_effort, "medium"); - NodeAssert.ok(settings?.developer_instructions?.includes(`as ${DEFAULT_MODEL} with medium`)); + NodeAssert.ok( + params.additionalContext?.t3_code_runtime?.value.includes(`as ${DEFAULT_MODEL} with medium`), + ); }); + it.effect("names the model by display name and slug in the runtime context", () => + Effect.gen(function* () { + const params = yield* buildTurnStartParams({ + threadId: "provider-thread-1", + runtimeMode: "full-access", + model: "gpt-5.3-codex", + modelName: "GPT-5.3-Codex", + effort: "high", + interactionMode: "plan", + }); + + NodeAssert.match( + params.additionalContext?.t3_code_runtime?.value ?? "", + /as GPT-5\.3-Codex \(model slug: gpt-5\.3-codex\) with high reasoning effort/, + ); + }), + ); + it.effect("routes approvals to the auto reviewer in auto mode", () => Effect.gen(function* () { const params = yield* buildTurnStartParams({ @@ -557,99 +585,82 @@ describe("Codex MCP elicitation approvals", () => { }); describe("buildCodexDeveloperInstructions", () => { - it("appends runtime info after the mode instructions", () => { - const instructions = buildCodexDeveloperInstructions("default", { - model: "gpt-5.3-codex", - reasoningEffort: "high", - }); - - NodeAssert.match(instructions, /^# Collaboration Mode: Default/); - NodeAssert.match(instructions, /T3 Code/); - NodeAssert.match(instructions, /Codex harness/); - NodeAssert.match(instructions, /as gpt-5\.3-codex with high reasoning effort/); - }); - - it("describes Markdown media support in the runtime context in both modes", () => { + it("keeps T3 context out of the mode prompt, which the model catalog can replace", () => { for (const mode of ["default", "plan"] as const) { - const instructions = buildCodexDeveloperInstructions(mode, { - model: "gpt-5.3-codex", - reasoningEffort: "high", - }); - NodeAssert.match( - instructions, - /.*embed images and videos.*Markdown.*<\/runtime_info>/, - ); + const instructions = buildCodexDeveloperInstructions(mode); + NodeAssert.match(instructions, /^[\s\S]*<\/collaboration_mode>$/); + NodeAssert.doesNotMatch(instructions, /runtime_info|pull_request_linking|preview_|device_/); } }); +}); - it("includes runtime info alongside plan mode instructions", () => { - const instructions = buildCodexDeveloperInstructions("plan", { - model: "gpt-5.3-codex", - reasoningEffort: "medium", - }); +describe("buildCodexAdditionalContext", () => { + const runtime = { model: "gpt-5.3-codex", reasoningEffort: "high" }; + const runtimeValue = (context: ReturnType) => + context.t3_code_runtime?.value ?? ""; - NodeAssert.match(instructions, /^# Plan Mode/); - NodeAssert.match(instructions, /as gpt-5\.3-codex with medium reasoning effort/); + it("describes the harness, model, effort, and Markdown media support", () => { + const context = buildCodexAdditionalContext(runtime); + + NodeAssert.equal(context.t3_code_runtime?.kind, "application"); + NodeAssert.match( + runtimeValue(context), + /.*Codex harness, as gpt-5\.3-codex with high reasoning effort.*embed images and videos.*Markdown.*<\/runtime_info>/, + ); }); it("varies with the model and effort of each turn", () => { - const first = buildCodexDeveloperInstructions("default", { - model: "gpt-5.3-codex", - reasoningEffort: "medium", - }); - const second = buildCodexDeveloperInstructions("default", { - model: "gpt-5.4", - reasoningEffort: "high", - }); - - NodeAssert.notEqual(first, second); + NodeAssert.notEqual( + runtimeValue( + buildCodexAdditionalContext({ model: "gpt-5.3-codex", reasoningEffort: "medium" }), + ), + runtimeValue(buildCodexAdditionalContext({ model: "gpt-5.4", reasoningEffort: "high" })), + ); }); it("flattens multiline metadata into single-line runtime info", () => { - const instructions = buildCodexDeveloperInstructions("default", { - model: "gpt\n5.3\ncodex", - reasoningEffort: " high\neffort ", - }); + const value = runtimeValue( + buildCodexAdditionalContext({ model: "gpt\n5.3\ncodex", reasoningEffort: " high\neffort " }), + ); - NodeAssert.match(instructions, /as gpt 5\.3 codex with high effort reasoning effort/); - NodeAssert.doesNotMatch(instructions, /[^<]*\n/); + NodeAssert.match(value, /as gpt 5\.3 codex with high effort reasoning effort/); + NodeAssert.doesNotMatch(value, /[^<]*\n/); + }); + + it("keeps every entry under Codex's 1,000 token cap per entry", () => { + const context = buildCodexAdditionalContext(runtime, { browser: true, device: true }); + for (const entry of Object.values(context)) { + // Codex estimates 4 bytes per token and truncates the middle of longer values. + NodeAssert.ok(Buffer.byteLength(entry.value) < 4_000); + } }); }); -describe("T3 browser developer instructions", () => { +describe("T3 tool instructions", () => { const runtime = { model: "gpt-5.3-codex", reasoningEffort: "high" }; - it("prefers the product-native preview tools in both collaboration modes", () => { - for (const mode of ["default", "plan"] as const) { - const instructions = buildCodexDeveloperInstructions(mode, runtime, true); - NodeAssert.match(instructions, /t3-code/); - NodeAssert.match(instructions, /preview_status/); - NodeAssert.match(instructions, /preview_open/); - NodeAssert.match(instructions, /Do not switch to global browser skills/); - } + it("prefers the product-native preview tools when they are attached", () => { + const tools = buildCodexAdditionalContext(runtime, true).t3_code_tools?.value ?? ""; + NodeAssert.match(tools, /t3-code/); + NodeAssert.match(tools, /preview_status/); + NodeAssert.match(tools, /preview_open/); + NodeAssert.match(tools, /Do not switch to global browser skills/); + NodeAssert.doesNotMatch(tools, /device_open/); }); - it("omits the browser block entirely when the preview tools are not attached", () => { - for (const mode of ["default", "plan"] as const) { - const instructions = buildCodexDeveloperInstructions(mode, runtime, false); - NodeAssert.doesNotMatch(instructions, /preview_status/); - NodeAssert.doesNotMatch(instructions, /preview_open/); - NodeAssert.doesNotMatch(instructions, /T3 Code collaborative browser/); - // Steering away from other browser automation must go with the tools; - // keeping it would leave the model talked out of its only option. - NodeAssert.doesNotMatch(instructions, /Do not switch to global browser skills/); - // The rest of the collaboration mode is untouched. - NodeAssert.match(instructions, //); - NodeAssert.match(instructions, /<\/collaboration_mode>/); - } + it("describes device tools only when the credential grants them", () => { + const tools = + buildCodexAdditionalContext(runtime, { browser: false, device: true }).t3_code_tools?.value ?? + ""; + NodeAssert.match(tools, /device_open/); + NodeAssert.doesNotMatch(tools, /preview_open/); }); - it("tracks the turn's MCP configuration rather than defaulting to on", () => { - NodeAssert.match(buildCodexDeveloperInstructions("default", runtime, true), /preview_open/); - NodeAssert.doesNotMatch( - buildCodexDeveloperInstructions("default", runtime, false), - /preview_open/, - ); + it("omits the tool entry entirely when no tools are attached", () => { + // Steering away from other browser automation must go with the tools; + // keeping it would leave the model talked out of its only option. + const context = buildCodexAdditionalContext(runtime, false); + NodeAssert.deepStrictEqual(Object.keys(context), ["t3_code_runtime"]); }); }); @@ -680,6 +691,7 @@ function makeThreadStartedNotification( id: threadId, modelProvider: "openai", preview: "", + projectId: null, sessionId: threadId, source, status: { type: "idle" as const }, diff --git a/apps/server/src/provider/Layers/CodexSessionRuntime.ts b/apps/server/src/provider/Layers/CodexSessionRuntime.ts index 674d23327b65..f30baf011e8a 100644 --- a/apps/server/src/provider/Layers/CodexSessionRuntime.ts +++ b/apps/server/src/provider/Layers/CodexSessionRuntime.ts @@ -14,6 +14,7 @@ import { type ProviderTurnStartResult, type ProviderUserInputAnswers, RuntimeMode, + type ServerProviderModel, ThreadId, TurnId, } from "@t3tools/contracts"; @@ -40,6 +41,7 @@ import { buildCodexInitializeParams } from "./CodexProvider.ts"; import { codexSessionAppServerArgs } from "./codexLaunchArgs.ts"; import { expandHomePath } from "../../pathExpansion.ts"; import { + buildCodexAdditionalContext, buildCodexDeveloperInstructions, type T3CodeToolAvailability, } from "../CodexDeveloperInstructions.ts"; @@ -119,7 +121,7 @@ const McpElicitationFormField = Schema.Struct({ type: Schema.optionalKey(NullableMcpElicitationString), title: Schema.optionalKey(NullableMcpElicitationString), description: Schema.optionalKey(NullableMcpElicitationString), - default: Schema.optionalKey(Schema.Unknown), + default: Schema.optionalKey(Schema.Json), enum: Schema.optionalKey(Schema.NullOr(Schema.Array(Schema.String))), enumNames: Schema.optionalKey(Schema.NullOr(Schema.Array(Schema.String))), oneOf: Schema.optionalKey( @@ -141,10 +143,13 @@ const isMcpElicitationMetadata = Schema.is(McpElicitationMetadata); const isMcpElicitationForm = Schema.is(McpElicitationForm); // TODO: Verify `packages/effect-codex-app-server/scripts/generate.ts` so the generated -// `V2TurnStartParams` schema includes `collaborationMode` directly. +// `V2TurnStartParams` schema includes its experimental fields directly. const CodexTurnStartParamsWithCollaborationMode = EffectCodexSchema.V2TurnStartParams.pipe( Schema.fieldsAssign({ collaborationMode: Schema.optionalKey(EffectCodexSchema.V2TurnStartParams__CollaborationMode), + additionalContext: Schema.optionalKey( + Schema.Record(Schema.String, EffectCodexSchema.V2TurnStartParams__AdditionalContextEntry), + ), }), ); const decodeCodexTurnStartParamsWithCollaborationMode = Schema.decodeUnknownEffect( @@ -163,8 +168,7 @@ export type CodexTurnStartParamsWithCollaborationMode = export type CodexResumeCursor = typeof CodexResumeCursorSchema.Type; type CodexServiceTier = NonNullable; type CodexThreadItem = - | EffectCodexSchema.V2ThreadReadResponse["thread"]["turns"][number]["items"][number] - | EffectCodexSchema.V2ThreadRollbackResponse["thread"]["turns"][number]["items"][number]; + EffectCodexSchema.V2ThreadReadResponse["thread"]["turns"][number]["items"][number]; export interface CodexSessionRuntimeOptions { readonly threadId: ThreadId; @@ -179,6 +183,8 @@ export interface CodexSessionRuntimeOptions { readonly serviceTier?: CodexServiceTier | undefined; readonly resumeCursor?: CodexResumeCursor; readonly appServerArgs?: ReadonlyArray; + /** The provider's model list; supplies the display name for runtime info. */ + readonly models?: Effect.Effect>; /** Capabilities the session's `t3-code` MCP credential grants; drives the prompt blocks. */ readonly mcpCapabilities?: ReadonlySet; } @@ -442,7 +448,7 @@ export function toMcpElicitationResponse( ? "always" : undefined; const form = mcpElicitationFormFields(payload); - const content: Record = {}; + const content: Record = {}; for (const [key, field] of Object.entries(form?.properties ?? {})) { const options = mcpElicitationFieldOptions(field); @@ -580,28 +586,31 @@ function runtimeModeToTurnSandboxPolicy( } } -function buildCodexCollaborationMode(input: { +function buildCodexTurnInstructions(input: { readonly interactionMode?: ProviderInteractionMode; readonly model?: string; + readonly modelName?: string; readonly effort?: EffectCodexSchema.V2TurnStartParams__ReasoningEffort; readonly browserToolsAvailable?: boolean | T3CodeToolAvailability; -}): EffectCodexSchema.V2TurnStartParams__CollaborationMode | undefined { +}): Pick { if (input.interactionMode === undefined) { - return undefined; + return {}; } const model = normalizeCodexModelSlug(input.model) ?? DEFAULT_MODEL; const reasoningEffort = input.effort ?? "medium"; return { - mode: input.interactionMode, - settings: { - model, - reasoning_effort: reasoningEffort, - developer_instructions: buildCodexDeveloperInstructions( - input.interactionMode, - { model, reasoningEffort }, - input.browserToolsAvailable ?? true, - ), + collaborationMode: { + mode: input.interactionMode, + settings: { + model, + reasoning_effort: reasoningEffort, + developer_instructions: buildCodexDeveloperInstructions(input.interactionMode), + }, }, + additionalContext: buildCodexAdditionalContext( + { model, modelName: input.modelName, reasoningEffort }, + input.browserToolsAvailable ?? true, + ), }; } @@ -618,6 +627,8 @@ export function buildTurnStartParams(input: { readonly path: string; }>; readonly model?: string; + /** Display name of `model`, for runtime info. */ + readonly modelName?: string; readonly serviceTier?: CodexServiceTier; readonly effort?: EffectCodexSchema.V2TurnStartParams__ReasoningEffort; readonly interactionMode?: ProviderInteractionMode; @@ -639,9 +650,10 @@ export function buildTurnStartParams(input: { } const config = runtimeModeToThreadConfig(input.runtimeMode); - const collaborationMode = buildCodexCollaborationMode({ + const turnInstructions = buildCodexTurnInstructions({ ...(input.interactionMode ? { interactionMode: input.interactionMode } : {}), ...(input.model ? { model: input.model } : {}), + ...(input.modelName ? { modelName: input.modelName } : {}), ...(input.effort ? { effort: input.effort } : {}), browserToolsAvailable: input.browserToolsAvailable ?? true, }); @@ -655,7 +667,7 @@ export function buildTurnStartParams(input: { ...(input.model ? { model: input.model } : {}), ...(input.serviceTier ? { serviceTier: input.serviceTier } : {}), ...(input.effort ? { effort: input.effort } : {}), - ...(collaborationMode ? { collaborationMode } : {}), + ...turnInstructions, }).pipe( Effect.mapError((cause) => CodexErrors.CodexAppServerProtocolParseError.fromSchemaError( @@ -1185,7 +1197,7 @@ function updateSession( } function parseThreadSnapshot( - response: EffectCodexSchema.V2ThreadReadResponse | EffectCodexSchema.V2ThreadRollbackResponse, + response: EffectCodexSchema.V2ThreadReadResponse, ): CodexThreadSnapshot { return { threadId: response.thread.id, @@ -1273,11 +1285,8 @@ export const rollbackCodexThread = Effect.fn("rollbackCodexThread")(function* ( threadId: string, numTurns: number, ): Effect.fn.Return { - if ((yield* readCodexHistoryMode(client, threadId)) !== "paginated") { - return parseThreadSnapshot(yield* client.request("thread/rollback", { threadId, numTurns })); - } - // Paginated threads replace history at a turn boundary instead of supporting - // the legacy count-based rollback endpoint. + // Codex replaces history at a turn boundary. It rejects threads that still + // use legacy history, which have no rollback API since Codex 0.156. const snapshot = yield* readCodexThread(client, threadId); const retainedCount = Math.max(0, snapshot.turns.length - numTurns); const firstRemoved = snapshot.turns[retainedCount]; @@ -1309,6 +1318,9 @@ export const makeCodexSessionRuntime = ( const collabChildLiveTurnsRef = yield* Ref.make(new Map()); const suppressMemoryConsolidationNotification = makeMemoryConsolidationNotificationFilter(); const closedRef = yield* Ref.make(false); + /** The `additionalContext` of the latest `turn/start`, restored after compaction. */ + const lastAdditionalContextRef = + yield* Ref.make(undefined); // `~` is not shell-expanded when env vars are set via // `child_process.spawn`; `expandHomePath` lets a configured @@ -1522,7 +1534,7 @@ export const makeCodexSessionRuntime = ( } }), ), - Effect.catch(() => Effect.void), + Effect.ignore, Effect.forkIn(runtimeScope), ); }); @@ -1860,6 +1872,35 @@ export const makeCodexSessionRuntime = ( } }); + /** + * Compaction rebuilds history from user messages and Codex's own context, + * which drops our `additionalContext` messages. Codex only resends an + * entry when its value changes, so without this the T3 context would stay + * lost until the model or effort changed. Awaited so the context is back + * before later notifications from the same turn are handled. Drop this if + * Codex enables its `retain_client_developer_messages` feature by default. + */ + const restoreAdditionalContext = (threadId: string) => + Effect.gen(function* () { + const context = yield* Ref.get(lastAdditionalContextRef); + if (!context) return; + yield* client.request("thread/inject_items", { + threadId, + items: Object.entries(context).map(([key, entry]) => ({ + type: "message", + role: "developer", + content: [{ type: "input_text", text: `<${key}>${entry.value}` }], + })), + }); + }).pipe( + Effect.timeout("10 seconds"), + Effect.catch((cause) => + Effect.logWarning("Failed to restore Codex additional context after compaction.", { + cause, + }), + ), + ); + const handleRawNotification = (notification: CodexServerNotification) => Effect.gen(function* () { const isMemoryConsolidationNotification = @@ -1946,6 +1987,14 @@ export const makeCodexSessionRuntime = ( return; } + if ( + notification.method === "item/completed" && + notification.params.item.type === "contextCompaction" && + notification.params.threadId === suppressRootId + ) { + yield* restoreAdditionalContext(notification.params.threadId); + } + let requestId: ApprovalRequestId | undefined; let requestKind: ProviderRequestKind | undefined; let turnId = childParentTurnId ?? route.turnId; @@ -2513,12 +2562,15 @@ export const makeCodexSessionRuntime = ( const normalizedModel = normalizeCodexModelSlug( input.model ?? (yield* Ref.get(sessionRef)).model, ); + const models = options.models ? yield* options.models : []; + const modelName = models.find((model) => model.slug === normalizedModel)?.name; const params = yield* buildTurnStartParams({ threadId: providerThreadId, runtimeMode: options.runtimeMode, ...(input.input ? { prompt: input.input } : {}), ...(input.attachments ? { attachments: input.attachments } : {}), ...(normalizedModel ? { model: normalizedModel } : {}), + ...(modelName ? { modelName } : {}), ...(input.serviceTier ? { serviceTier: input.serviceTier } : {}), ...(input.effort ? { effort: input.effort } : {}), ...(input.interactionMode ? { interactionMode: input.interactionMode } : {}), @@ -2530,6 +2582,7 @@ export const makeCodexSessionRuntime = ( options.mcpCapabilities, ), }); + yield* Ref.set(lastAdditionalContextRef, params.additionalContext); const rawResponse = yield* client.raw.request("turn/start", params); const response = yield* decodeV2TurnStartResponse(rawResponse).pipe( Effect.mapError((error) => diff --git a/apps/server/src/provider/Layers/CursorProvider.test.ts b/apps/server/src/provider/Layers/CursorProvider.test.ts index 47826fcc8704..aa5207f3aef2 100644 --- a/apps/server/src/provider/Layers/CursorProvider.test.ts +++ b/apps/server/src/provider/Layers/CursorProvider.test.ts @@ -991,21 +991,21 @@ describe("Cursor usage limits", () => { { id: "totalPercentUsed", kind: "monthly", - label: "Monthly", + label: "Overall", usedPercent: 72.4, resetsAt: "2026-09-20T03:53:06.000Z", }, { id: "autoPercentUsed", kind: "monthly", - label: "Monthly · Auto", + label: "Cursor Models", usedPercent: 69.5, resetsAt: "2026-09-20T03:53:06.000Z", }, { id: "apiPercentUsed", kind: "monthly", - label: "Monthly · API", + label: "Other Models", usedPercent: 100, resetsAt: "2026-09-20T03:53:06.000Z", }, @@ -1019,10 +1019,10 @@ describe("Cursor usage limits", () => { ); expect( cursorUsageResponseToLimits({ planUsage: { totalPercentUsed: 0 } }, checkedAt).windows, - ).toEqual([{ id: "totalPercentUsed", kind: "monthly", label: "Monthly", usedPercent: 0 }]); + ).toEqual([{ id: "totalPercentUsed", kind: "monthly", label: "Overall", usedPercent: 0 }]); expect( cursorUsageResponseToLimits({ planUsage: { totalPercentUsed: 150 } }, checkedAt).windows, - ).toEqual([{ id: "totalPercentUsed", kind: "monthly", label: "Monthly", usedPercent: 100 }]); + ).toEqual([{ id: "totalPercentUsed", kind: "monthly", label: "Overall", usedPercent: 100 }]); }); it("reads the instance's credentials and endpoint even when usage enabled is false", async () => { @@ -1079,6 +1079,10 @@ describe("Cursor usage limits", () => { AGENT_CLI_CREDENTIAL_STORE: platform === "linux" ? "memory" : "default", ...(token ? { CURSOR_AUTH_TOKEN: token } : {}), }, + false, + async () => { + throw new Error("must not read Keychain before opt-in"); + }, ).pipe( Effect.provideService(HostProcessPlatform, platform), Effect.provideService( @@ -1108,6 +1112,71 @@ describe("Cursor usage limits", () => { } }); + it("reads the default macOS Cursor login from Keychain for limits", async () => { + const limits = await runNode( + readCursorUsageLimits({ apiEndpoint: "" }, {}, true, async () => "keychain-token").pipe( + Effect.provideService(HostProcessPlatform, "darwin"), + Effect.provideService( + FileSystem.FileSystem, + FileSystem.makeNoop({ + readFileString: () => Effect.die("must not read a stale credential file"), + }), + ), + Effect.provideService( + HttpClient.HttpClient, + HttpClient.make((request) => { + expect(request.headers.authorization).toBe("Bearer keychain-token"); + return Effect.succeed( + HttpClientResponse.fromWeb( + request, + Response.json({ planUsage: { totalPercentUsed: 42 } }), + ), + ); + }), + ), + ), + ); + expect(limits.windows[0]?.usedPercent).toBe(42); + }); + + it("reports a Keychain initialization failure without failing the provider refresh", async () => { + const limits = await runNode( + readCursorUsageLimits({ apiEndpoint: "" }, {}, true, async () => { + throw new Error("Keychain initialization failed"); + }).pipe( + Effect.provideService(HostProcessPlatform, "darwin"), + Effect.provideService( + HttpClient.HttpClient, + HttpClient.make(() => Effect.die("must not request limits without a login")), + ), + ), + ); + expect(limits.unavailable?.reason).toBe("probeFailed"); + }); + + it("does not read Keychain or send its token to a custom endpoint", async () => { + for (const [apiEndpoint, environment] of [ + ["http://localhost:3000", {}], + ["", { CURSOR_API_ENDPOINT: "http://localhost:3000" }], + ["https://cursor-proxy.example", {}], + ["", { CURSOR_API_ENDPOINT: "https://cursor-proxy.example" }], + ] as const) { + const limits = await runNode( + readCursorUsageLimits({ apiEndpoint }, environment, true, async () => { + throw new Error("must not read Keychain for a custom endpoint"); + }).pipe( + Effect.provideService(HostProcessPlatform, "darwin"), + Effect.provideService( + HttpClient.HttpClient, + HttpClient.make(() => Effect.die("must not send a Keychain credential to a proxy")), + ), + ), + ); + expect(limits.unavailable?.reason).toBe("unsupported"); + expect(limits.unavailable?.message).toContain("default Cursor endpoint"); + } + }); + it("reports failed requests without exposing credentials or response bodies", async () => { const limits = await runNode( readCursorUsageLimits({ apiEndpoint: "" }, { CURSOR_AUTH_TOKEN: "private-token" }).pipe( diff --git a/apps/server/src/provider/Layers/GrokAdapter.ts b/apps/server/src/provider/Layers/GrokAdapter.ts index bec0fc39faa0..601ce9ffa37e 100644 --- a/apps/server/src/provider/Layers/GrokAdapter.ts +++ b/apps/server/src/provider/Layers/GrokAdapter.ts @@ -776,46 +776,45 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte ); }); - const runTurnLivenessWatchdog = Effect.fn("GrokAdapter.runTurnLivenessWatchdog")( - function* (ctx: GrokSessionContext) { - while (true) { - if (ctx.stopped) { - return; - } - const turnId = ctx.livenessTurnId; - if ( - turnId === undefined || - ctx.interruptedTurnIds.has(turnId) || - !isLiveTurn(ctx, turnId) || - hasLivenessPause(ctx) - ) { - yield* Queue.take(ctx.livenessSignals); - continue; - } + const runTurnLivenessWatchdog = Effect.fn("GrokAdapter.runTurnLivenessWatchdog")(function* ( + ctx: GrokSessionContext, + ) { + while (true) { + if (ctx.stopped) { + return; + } + const turnId = ctx.livenessTurnId; + if ( + turnId === undefined || + ctx.interruptedTurnIds.has(turnId) || + !isLiveTurn(ctx, turnId) || + hasLivenessPause(ctx) + ) { + yield* Queue.take(ctx.livenessSignals); + continue; + } - const lastActivityAtNanos = ctx.lastTurnActivityAtNanos; - if (lastActivityAtNanos === undefined) { - yield* Queue.take(ctx.livenessSignals); - continue; - } - const nowNanos = yield* Clock.monotonicTimeNanos; - const remainingNanos = livenessTimeoutFor(ctx).nanos - (nowNanos - lastActivityAtNanos); - if (remainingNanos <= 0n) { - yield* settleStalledTurn(ctx, turnId); - continue; - } + const lastActivityAtNanos = ctx.lastTurnActivityAtNanos; + if (lastActivityAtNanos === undefined) { + yield* Queue.take(ctx.livenessSignals); + continue; + } + const nowNanos = yield* Clock.monotonicTimeNanos; + const remainingNanos = livenessTimeoutFor(ctx).nanos - (nowNanos - lastActivityAtNanos); + if (remainingNanos <= 0n) { + yield* settleStalledTurn(ctx, turnId); + continue; + } - const wakeReason = yield* Effect.raceFirst( - Effect.sleep(Duration.nanos(remainingNanos)).pipe(Effect.as("timeout" as const)), - Queue.take(ctx.livenessSignals).pipe(Effect.as("activity" as const)), - ); - if (wakeReason === "timeout") { - yield* settleStalledTurn(ctx, turnId); - } + const wakeReason = yield* Effect.raceFirst( + Effect.sleep(Duration.nanos(remainingNanos)).pipe(Effect.as("timeout" as const)), + Queue.take(ctx.livenessSignals).pipe(Effect.as("activity" as const)), + ); + if (wakeReason === "timeout") { + yield* settleStalledTurn(ctx, turnId); } - }, - Effect.catch(() => Effect.void), - ); + } + }, Effect.ignore()); const logNative = (threadId: ThreadId, method: string, payload: unknown) => Effect.gen(function* () { @@ -2008,7 +2007,7 @@ export function makeGrokAdapter(grokSettings: GrokSettings, options?: GrokAdapte errorMessage: errorMessage ?? "Grok prompt request failed.", }), ); - }).pipe(Effect.catch(() => Effect.void)), + }).pipe(Effect.ignore), ), ); }); diff --git a/apps/server/src/provider/Layers/GrokProvider.test.ts b/apps/server/src/provider/Layers/GrokProvider.test.ts index 7e5c2104201a..ec6ae243f00a 100644 --- a/apps/server/src/provider/Layers/GrokProvider.test.ts +++ b/apps/server/src/provider/Layers/GrokProvider.test.ts @@ -20,7 +20,7 @@ import { parseGrokModelsCliOutput, } from "./GrokProvider.ts"; import { execScriptSource, writeFakeCli } from "../../testUtils/fakeCli.ts"; -import { grokUsageResponseToLimits, readGrokUsageLimits } from "./grokUsageLimits.ts"; +import { grokUsageResponseToLimits, readGrokAccount } from "./grokUsageLimits.ts"; const decodeGrokSettings = Schema.decodeSync(GrokSettings); const __dirname = NodePath.dirname(NodeURL.fileURLToPath(import.meta.url)); @@ -635,7 +635,10 @@ describe("Grok usage limits", () => { for (const response of [{}, { config: {} }, { config: { creditUsagePercent: NaN } }]) { const limits = grokUsageResponseToLimits(response, checkedAt); expect(limits.windows).toEqual([]); - expect(limits.unavailable?.reason).toBe("unsupported"); + // Nothing metered yet, which xAI reports by omitting the field until + // usage registers. Marking it `unsupported` would drop the account from + // the Limits view for good; leaving the marker off keeps it listed. + expect(limits.unavailable).toBeUndefined(); } expect( grokUsageResponseToLimits({ config: { creditUsagePercent: 0 } }, checkedAt).windows, @@ -654,7 +657,7 @@ describe("Grok usage limits", () => { }); }); -it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { +it.layer(NodeServices.layer)("readGrokAccount", (it) => { it.effect("reads the configured Grok home and prefers the current login scope to legacy", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; @@ -663,7 +666,7 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { NodePath.join(directory, "auth.json"), '{"https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828":{"key":"session-token","auth_mode":"oauth"},"https://accounts.x.ai/sign-in":{"key":"legacy-token"}}', ); - const limits = yield* readGrokUsageLimits({ + const { usageLimits: limits } = yield* readGrokAccount({ GROK_HOME: directory, HOME: "/unrelated-home", }).pipe( @@ -689,7 +692,7 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { it.effect( "uses GROK_AUTH without reading stored credentials and accepts the legacy login scope", () => - readGrokUsageLimits({ + readGrokAccount({ GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"legacy-token"}}', }).pipe( Effect.provideService( @@ -713,7 +716,9 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { ); }), ), - Effect.tap((limits) => Effect.sync(() => expect(limits.windows[0]?.usedPercent).toBe(12))), + Effect.tap(({ usageLimits }) => + Effect.sync(() => expect(usageLimits.windows[0]?.usedPercent).toBe(12)), + ), ), ); @@ -752,7 +757,7 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { GROK_CONFIG_PATH: "/custom-config.toml", }, ]) { - const limits = yield* readGrokUsageLimits({ + const { usageLimits: limits } = yield* readGrokAccount({ HOME: "/definitely/not/a/grok-home", ...environment, }).pipe( @@ -774,7 +779,7 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { const fs = yield* FileSystem.FileSystem; const directory = yield* fs.makeTempDirectoryScoped(); const client = HttpClient.make(() => Effect.die("must not request without credentials")); - const missing = yield* readGrokUsageLimits({ HOME: directory }).pipe( + const { usageLimits: missing } = yield* readGrokAccount({ HOME: directory }).pipe( Effect.provideService(HttpClient.HttpClient, client), ); expect(missing.unavailable?.reason).toBe("unsupported"); @@ -782,7 +787,7 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { "private-token-invalid-json", '{"https://accounts.x.ai/sign-in":{"key":42}}', ]) { - const malformed = yield* readGrokUsageLimits({ + const { usageLimits: malformed } = yield* readGrokAccount({ GROK_HOME: directory, GROK_AUTH: contents, }).pipe(Effect.provideService(HttpClient.HttpClient, client)); @@ -803,7 +808,7 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { '[grok_com_config]\nissuer = "https://custom.example"', 'endpoints.proxy = "https://custom.example"', ]) { - const limits = yield* readGrokUsageLimits({ + const { usageLimits: limits } = yield* readGrokAccount({ GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"stored-token"}}', }).pipe( Effect.provideService( @@ -826,15 +831,16 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { }), ); - it.effect("sanitizes HTTP failures and malformed billing responses", () => + it.effect("sanitizes HTTP failures and malformed billing responses, keeping the account", () => Effect.gen(function* () { for (const response of [ new Response("private response", { status: 401 }), Response.json({ config: { creditUsagePercent: "private-value" } }), ]) { - const limits = yield* readGrokUsageLimits({ + const { email, usageLimits: limits } = yield* readGrokAccount({ HOME: "/definitely/not/a/grok-home", - GROK_AUTH: '{"https://accounts.x.ai/sign-in":{"key":"private-token"}}', + GROK_AUTH: + '{"https://accounts.x.ai/sign-in":{"key":"private-token","email":"someone@example.com"}}', }).pipe( Effect.provideService( HttpClient.HttpClient, @@ -843,6 +849,7 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { ), ), ); + expect(email).toBe("someone@example.com"); expect(limits.windows).toEqual([]); expect(limits.unavailable).toEqual({ reason: "probeFailed", @@ -852,3 +859,58 @@ it.layer(NodeServices.layer)("readGrokUsageLimits", (it) => { }), ); }); + +it.layer(NodeServices.layer)("readGrokAccount email", (it) => { + it.effect("names the account whose limits were read, and nothing for other auth", () => + Effect.gen(function* () { + const current = '"https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828"'; + const cases = [ + [ + { GROK_AUTH: `{${current}:{"key":"t","email":" Someone@Example.com "}}` }, + "Someone@Example.com", + ], + [ + { + GROK_AUTH: `{${current}:{"key":"t","email":"current@example.com"},"https://accounts.x.ai/sign-in":{"key":"t","email":"legacy@example.com"}}`, + }, + "current@example.com", + ], + [{ GROK_AUTH: `{${current}:{"key":"t"}}` }, undefined], + [ + { + GROK_AUTH: `{${current}:{"key":"t","email":"someone@example.com"}}`, + XAI_API_KEY: "api-key", + }, + undefined, + ], + [ + { + GROK_AUTH: `{${current}:{"key":"t","email":"someone@example.com","auth_mode":"api_key"}}`, + }, + undefined, + ], + [{ GROK_AUTH: "not-json" }, undefined], + ] as const; + for (const [environment, expected] of cases) { + const { email, usageLimits } = yield* readGrokAccount({ + HOME: "/definitely/not/a/grok-home", + ...environment, + }).pipe( + Effect.provideService( + HttpClient.HttpClient, + HttpClient.make((request) => + Effect.succeed( + HttpClientResponse.fromWeb( + request, + Response.json({ config: { creditUsagePercent: 25 } }), + ), + ), + ), + ), + ); + expect(email).toBe(expected); + if (expected) expect(usageLimits.windows[0]?.usedPercent).toBe(25); + } + }), + ); +}); diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts index 601917d35864..baded8094ba6 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.test.ts @@ -578,7 +578,7 @@ const providerSessionDirectoryTestLayer = Layer.succeed(ProviderSessionDirectory recordImportedTranscript: () => Effect.die("unused"), getProvider: () => Effect.die(new Error("ProviderSessionDirectory.getProvider is not used in test")), - getBinding: () => Effect.succeed(Option.none()), + getBinding: () => Effect.succeedNone, listThreadIds: () => Effect.succeed([]), listBindings: () => Effect.succeed([]), }); diff --git a/apps/server/src/provider/Layers/OpenCodeAdapter.ts b/apps/server/src/provider/Layers/OpenCodeAdapter.ts index 41bf634c0d3b..04535edbd3af 100644 --- a/apps/server/src/provider/Layers/OpenCodeAdapter.ts +++ b/apps/server/src/provider/Layers/OpenCodeAdapter.ts @@ -822,7 +822,7 @@ const abortOpenCodeDescendants = Effect.fn("abortOpenCodeDescendants")(function* .pipe( Effect.catchIf( (cause) => isOpenCodeNotFound(cause), - () => Effect.void, + () => Effect.undefined, ), Effect.result, ); @@ -1097,7 +1097,7 @@ export function makeOpenCodeAdapter( readonly observedAt: string; readonly event: Record; }, - ) => writeNativeEvent(threadId, event).pipe(Effect.catchCause(() => Effect.void)); + ) => writeNativeEvent(threadId, event).pipe(Effect.ignoreCause); const cancelIdleReconciliation = Effect.fn("cancelIdleReconciliation")(function* ( context: OpenCodeSessionContext, @@ -1262,7 +1262,7 @@ export function makeOpenCodeAdapter( yield* Effect.sleep(`${delayMs} millis`); } }).pipe( - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, Effect.ensuring( Effect.sync(() => { if (context.pendingIdleReconciliation === pending) { @@ -1492,7 +1492,7 @@ export function makeOpenCodeAdapter( } yield* failPromptAdmissionRecovery(context, promptAdmission); }).pipe( - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, Effect.ensuring( Effect.sync(() => { delete promptAdmission.recoveryFiber; @@ -1699,7 +1699,7 @@ export function makeOpenCodeAdapter( }), Effect.catchIf( (cause) => isOpenCodeNotFound(cause), - () => Effect.succeed(undefined), + () => Effect.undefined, ), ); let sessionId: string | undefined = candidateSessionId; @@ -2044,7 +2044,7 @@ export function makeOpenCodeAdapter( yield* Effect.sleep(`${delayMs} millis`); } }).pipe( - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, Effect.ensuring( Effect.sync(() => { if (context.requestRelationRetries.get(requestId) === retry) { @@ -2163,7 +2163,7 @@ export function makeOpenCodeAdapter( return; } }).pipe( - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, Effect.ensuring( Effect.sync(() => { if (context.pendingRequestRecovery === recovery) { @@ -2730,6 +2730,7 @@ export function makeOpenCodeAdapter( // the scope closes (explicit stop, unexpected exit, or layer // shutdown) and cancels the in-flight `event.subscribe` fetch so // the async iterable unwinds cleanly. + // @effect-diagnostics-next-line abortControllerInEffect:off - aborted by a scope finalizer to cancel the SDK's event.subscribe fetch const eventsAbortController = new AbortController(); let lastStreamError: unknown; let warnedAboutDisconnect = false; diff --git a/apps/server/src/provider/Layers/ProviderService.test.ts b/apps/server/src/provider/Layers/ProviderService.test.ts index b9997e1df312..36d884d8ac98 100644 --- a/apps/server/src/provider/Layers/ProviderService.test.ts +++ b/apps/server/src/provider/Layers/ProviderService.test.ts @@ -4894,13 +4894,11 @@ const listThreadIds = vi.fn(() => Effect.succeed([activeSessionThreadId, historicalSessionThreadId]), ); const getBinding = vi.fn((threadId: ThreadId) => - Effect.succeed( - Option.some({ - threadId, - provider: CODEX_DRIVER, - providerInstanceId: codexInstanceId, - }), - ), + Effect.succeedSome({ + threadId, + provider: CODEX_DRIVER, + providerInstanceId: codexInstanceId, + }), ); const boundedListing = makeProviderServiceLayer({ directory: { diff --git a/apps/server/src/provider/Layers/ProviderService.ts b/apps/server/src/provider/Layers/ProviderService.ts index b84e932a7ca8..f6a918cb20f3 100644 --- a/apps/server/src/provider/Layers/ProviderService.ts +++ b/apps/server/src/provider/Layers/ProviderService.ts @@ -2307,7 +2307,7 @@ const makeProviderService = Effect.fn("makeProviderService")(function* ( // Continuation is project-scopable, so decide it per session's project; // without orchestration the environment value is all there is. const stopSettings = yield* serverSettings.getSettings.pipe( - Effect.map(Option.some), + Effect.asSome, Effect.orElseSucceed(() => Option.none()), ); const continueAfterRestartFor = Effect.fn("continueAfterRestartFor")(function* ( diff --git a/apps/server/src/provider/Layers/ProviderSessionDirectory.ts b/apps/server/src/provider/Layers/ProviderSessionDirectory.ts index 29ec8d2ed168..03f1ece0b5e0 100644 --- a/apps/server/src/provider/Layers/ProviderSessionDirectory.ts +++ b/apps/server/src/provider/Layers/ProviderSessionDirectory.ts @@ -93,9 +93,7 @@ const makeProviderSessionDirectory = Effect.gen(function* () { Option.match(runtime, { onNone: () => Effect.succeed(Option.none()), onSome: (value) => - toRuntimeBinding(value, "ProviderSessionDirectory.getBinding").pipe( - Effect.map((binding) => Option.some(binding)), - ), + toRuntimeBinding(value, "ProviderSessionDirectory.getBinding").pipe(Effect.asSome), }), ), ); diff --git a/apps/server/src/provider/Layers/claudeResetCredits.ts b/apps/server/src/provider/Layers/claudeResetCredits.ts index 925a25585491..f02c17214c83 100644 --- a/apps/server/src/provider/Layers/claudeResetCredits.ts +++ b/apps/server/src/provider/Layers/claudeResetCredits.ts @@ -186,7 +186,7 @@ export const readClaudeResetCredits = Effect.fn("readClaudeResetCredits")( ); }, Effect.timeout("10 seconds"), - Effect.catch(() => Effect.succeed(undefined)), + Effect.orElseSucceed(() => undefined), ); /** The CLI keeps the account record beside its settings, or in the home directory by default. */ diff --git a/apps/server/src/provider/Layers/cursorUsageLimits.ts b/apps/server/src/provider/Layers/cursorUsageLimits.ts index 685378b9d79e..e0611e0c50ab 100644 --- a/apps/server/src/provider/Layers/cursorUsageLimits.ts +++ b/apps/server/src/provider/Layers/cursorUsageLimits.ts @@ -1,6 +1,7 @@ import * as NodeOS from "node:os"; import type { CursorSettings, ServerProviderUsageWindow } from "@t3tools/contracts"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; +import { CURSOR_USAGE_WINDOWS } from "@t3tools/shared/usageLimits"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; @@ -13,8 +14,10 @@ import { makeUnavailableUsageLimits, makeUsageLimits, } from "../providerUsageLimits.ts"; +import { readMacCursorAccessToken } from "../cursorCredentialStore.ts"; const CursorCredentials = Schema.Struct({ accessToken: Schema.optional(Schema.String) }); +const DEFAULT_CURSOR_API_ENDPOINT = "https://api2.cursor.sh"; const decodeCredentials = Schema.decodeEffect(Schema.fromJsonString(CursorCredentials)); const CursorUsageResponse = Schema.Struct({ billingCycleEnd: Schema.optional(Schema.Union([Schema.String, Schema.Number])), @@ -39,15 +42,11 @@ export function cursorUsageResponseToLimits( : undefined; const windows: ServerProviderUsageWindow[] = []; if (response.planUsage) { - for (const [key, label] of [ - ["totalPercentUsed", "Monthly"], - ["autoPercentUsed", "Monthly · Auto"], - ["apiPercentUsed", "Monthly · API"], - ] as const) { - const usedPercent = response.planUsage[key]; + for (const { id, label } of CURSOR_USAGE_WINDOWS) { + const usedPercent = response.planUsage[id]; if (usedPercent === undefined || !Number.isFinite(usedPercent)) continue; windows.push({ - id: key, + id, kind: "monthly", label, usedPercent: clampPercent(usedPercent), @@ -63,30 +62,49 @@ export function cursorUsageResponseToLimits( export const readCursorUsageLimits = Effect.fn("readCursorUsageLimits")(function* ( settings: Pick, environment: NodeJS.ProcessEnv = process.env, + allowKeychain = false, + keychainToken: () => Promise = readMacCursorAccessToken, ) { const checkedAt = DateTime.formatIso(yield* DateTime.now); return yield* Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const path = yield* Path.Path; const platform = yield* HostProcessPlatform; + const endpoint = ( + settings.apiEndpoint.trim() || + environment.CURSOR_API_ENDPOINT?.trim() || + DEFAULT_CURSOR_API_ENDPOINT + ).replace(/\/$/, ""); let token = environment.CURSOR_AUTH_TOKEN?.trim(); // An explicit API key can name a different account from the stored login. if (!token && environment.CURSOR_API_KEY?.trim()) { return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }); } const credentialStore = environment.AGENT_CLI_CREDENTIAL_STORE; - if ( - !token && - (credentialStore === "memory" || (platform === "darwin" && credentialStore !== "file")) - ) { - // Cursor's default macOS login lives in the keychain; a leftover file may be another account. + if (!token && credentialStore === "memory") { return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported", - message: "Cursor usage requires a file-based login or CURSOR_AUTH_TOKEN.", + message: "Cursor usage requires a CLI login or CURSOR_AUTH_TOKEN.", }); } - if (!token) { + if (!token && platform === "darwin" && credentialStore !== "file") { + if (!allowKeychain) { + return makeUnavailableUsageLimits({ + checkedAt, + reason: "unsupported", + message: "Enable Cursor account usage in T3 Code to read its Keychain login.", + }); + } + if (endpoint !== DEFAULT_CURSOR_API_ENDPOINT) { + return makeUnavailableUsageLimits({ + checkedAt, + reason: "unsupported", + message: "Cursor account usage requires the default Cursor endpoint when using Keychain.", + }); + } + token = (yield* Effect.tryPromise(keychainToken))?.trim(); + } else if (!token) { const home = (platform === "win32" ? environment.USERPROFILE : environment.HOME) || NodeOS.homedir(); const directory = @@ -106,11 +124,6 @@ export const readCursorUsageLimits = Effect.fn("readCursorUsageLimits")(function } if (!token) return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }); const client = yield* HttpClient.HttpClient; - const endpoint = ( - settings.apiEndpoint.trim() || - environment.CURSOR_API_ENDPOINT?.trim() || - "https://api2.cursor.sh" - ).replace(/\/$/, ""); const response = yield* client.execute( HttpClientRequest.post(`${endpoint}/aiserver.v1.DashboardService/GetCurrentPeriodUsage`).pipe( HttpClientRequest.bearerToken(token), @@ -127,14 +140,12 @@ export const readCursorUsageLimits = Effect.fn("readCursorUsageLimits")(function return cursorUsageResponseToLimits(body, checkedAt); }).pipe( Effect.timeout("10 seconds"), - Effect.catch(() => - Effect.succeed( - makeUnavailableUsageLimits({ - checkedAt, - reason: "probeFailed", - message: "Cursor could not read usage limits.", - }), - ), + Effect.orElseSucceed(() => + makeUnavailableUsageLimits({ + checkedAt, + reason: "probeFailed", + message: "Cursor could not read usage limits.", + }), ), ); }); diff --git a/apps/server/src/provider/Layers/grokUsageLimits.ts b/apps/server/src/provider/Layers/grokUsageLimits.ts index 8c3db6bea80b..7dc561d07ec0 100644 --- a/apps/server/src/provider/Layers/grokUsageLimits.ts +++ b/apps/server/src/provider/Layers/grokUsageLimits.ts @@ -18,6 +18,7 @@ const GrokCredentials = Schema.Record( Schema.Struct({ key: Schema.optional(Schema.String), auth_mode: Schema.optional(Schema.String), + email: Schema.optional(Schema.String), }), ); const decodeCredentials = Schema.decodeEffect(Schema.fromJsonString(GrokCredentials)); @@ -41,7 +42,15 @@ export function grokUsageResponseToLimits( ) { const usedPercent = response.config?.creditUsagePercent; if (usedPercent === undefined || !Number.isFinite(usedPercent)) { - return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }); + // A billing read that succeeded but carries no percentage is an account + // with nothing metered yet, not one that can never report: xAI omits the + // field entirely (rather than sending 0) until usage registers, then fills + // it in. Calling that `unsupported` would strand the account — the Limits + // view drops unsupported entries and deliberately mutes their notice, so a + // freshly signed-in Grok account would vanish with no explanation until it + // happened to be used, and `applyUsageLimitsUpdate` would refuse the + // mid-turn windows that could have recovered it. + return makeUsageLimits({ checkedAt, windows: [] }); } const period = response.config?.currentPeriod; const periodType = period?.type?.replace(/^USAGE_PERIOD_TYPE_/, ""); @@ -57,72 +66,97 @@ export function grokUsageResponseToLimits( return makeUsageLimits({ checkedAt, windows: [window] }); } -export const readGrokUsageLimits = Effect.fn("readGrokUsageLimits")(function* ( +/** + * The grok.com login the CLI uses by default, or undefined when the CLI is + * configured to pick another account, endpoint, or an API key. + */ +const readGrokCredential = Effect.fn("readGrokCredential")(function* ( + environment: NodeJS.ProcessEnv, +) { + // T3's ACP adapter explicitly selects API-key auth when this variable is set. + if (environment.XAI_API_KEY?.trim()) return undefined; + // Alternate auth deployments can select another scope or account from the same file. + if ( + [ + "GROK_OIDC_ISSUER", + "GROK_OIDC_CLIENT_ID", + "GROK_OAUTH2_ISSUER", + "GROK_OAUTH2_CLIENT_ID", + "GROK_OAUTH2_PRINCIPAL_TYPE", + "GROK_OAUTH2_PRINCIPAL_ID", + "GROK_AUTH_PROVIDER_COMMAND", + "GROK_LOCAL_AUTH", + "GROK_CLI_CHAT_PROXY_BASE_URL", + "GROK_MODELS_BASE_URL", + "GROK_CONFIG", + "GROK_CONFIG_PATH", + ].some((name) => environment[name]?.trim()) + ) { + return undefined; + } + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const home = + environment.GROK_HOME?.trim() || + path.join(environment.HOME || environment.USERPROFILE || NodeOS.homedir(), ".grok"); + for (const configPath of [ + path.join(home, "config.toml"), + path.join(home, "managed_config.toml"), + path.join(home, "requirements.toml"), + "/etc/grok/managed_config.toml", + "/etc/grok/requirements.toml", + ]) { + const config = yield* fs.readFileString(configPath).pipe( + Effect.catchTags({ + PlatformError: (error) => + error.reason._tag === "NotFound" ? Effect.succeed("") : Effect.fail(error), + }), + ); + // These sections can change the selected account or endpoint. Leave custom deployments to the CLI. + if (/^\s*(?:\[\[?\s*)?["']?(?:auth|grok_com_config|endpoints)["']?\s*[.\]=]/m.test(config)) { + return undefined; + } + } + const contents = + environment.GROK_AUTH?.trim() || + (yield* fs.readFileString(path.join(home, "auth.json")).pipe( + Effect.catchTags({ + PlatformError: (error) => + error.reason._tag === "NotFound" ? Effect.succeed("{}") : Effect.fail(error), + }), + )); + const credentials = yield* decodeCredentials(contents); + // Never pick an arbitrary account from other deployments stored in the same file. + const credential = + credentials["https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828"] ?? + credentials["https://accounts.x.ai/sign-in"]; + return credential?.auth_mode === "api_key" ? undefined : credential; +}); + +/** + * Reads the default grok.com login once and reports its usage limits along with + * its email, so the email always names the account whose quota was read. + */ +export const readGrokAccount = Effect.fn("readGrokAccount")(function* ( environment: NodeJS.ProcessEnv = process.env, ) { const checkedAt = DateTime.formatIso(yield* DateTime.now); - return yield* Effect.gen(function* () { - // T3's ACP adapter explicitly selects API-key auth when this variable is set. - if (environment.XAI_API_KEY?.trim()) { - return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }); - } - // Alternate auth deployments can select another scope or account from the same file. - if ( - [ - "GROK_OIDC_ISSUER", - "GROK_OIDC_CLIENT_ID", - "GROK_OAUTH2_ISSUER", - "GROK_OAUTH2_CLIENT_ID", - "GROK_OAUTH2_PRINCIPAL_TYPE", - "GROK_OAUTH2_PRINCIPAL_ID", - "GROK_AUTH_PROVIDER_COMMAND", - "GROK_LOCAL_AUTH", - "GROK_CLI_CHAT_PROXY_BASE_URL", - "GROK_MODELS_BASE_URL", - "GROK_CONFIG", - "GROK_CONFIG_PATH", - ].some((name) => environment[name]?.trim()) - ) { - return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }); - } - const fs = yield* FileSystem.FileSystem; - const path = yield* Path.Path; - const home = - environment.GROK_HOME?.trim() || - path.join(environment.HOME || environment.USERPROFILE || NodeOS.homedir(), ".grok"); - for (const configPath of [ - path.join(home, "config.toml"), - path.join(home, "managed_config.toml"), - path.join(home, "requirements.toml"), - "/etc/grok/managed_config.toml", - "/etc/grok/requirements.toml", - ]) { - const config = yield* fs.readFileString(configPath).pipe( - Effect.catchTags({ - PlatformError: (error) => - error.reason._tag === "NotFound" ? Effect.succeed("") : Effect.fail(error), - }), - ); - // These sections can change the selected account or endpoint. Leave custom deployments to the CLI. - if (/^\s*(?:\[\[?\s*)?["']?(?:auth|grok_com_config|endpoints)["']?\s*[.\]=]/m.test(config)) { - return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }); - } - } - const contents = - environment.GROK_AUTH?.trim() || - (yield* fs.readFileString(path.join(home, "auth.json")).pipe( - Effect.catchTags({ - PlatformError: (error) => - error.reason._tag === "NotFound" ? Effect.succeed("{}") : Effect.fail(error), - }), - )); - const credentials = yield* decodeCredentials(contents); - // Never pick an arbitrary account from other deployments stored in the same file. - const credential = - credentials["https://auth.x.ai::b1a00492-073a-47ea-816f-4c329264a828"] ?? - credentials["https://accounts.x.ai/sign-in"]; - const token = credential?.auth_mode === "api_key" ? undefined : credential?.key?.trim(); - if (!token) return makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }); + const probeFailed = makeUnavailableUsageLimits({ + checkedAt, + reason: "probeFailed", + message: "Grok could not read usage limits.", + }); + const credential = yield* Effect.option( + readGrokCredential(environment).pipe(Effect.timeout("10 seconds")), + ); + if (Option.isNone(credential)) return { email: undefined, usageLimits: probeFailed }; + const email = credential.value?.email?.trim() || undefined; + const token = credential.value?.key?.trim(); + if (!token) { + return { email, usageLimits: makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" }) }; + } + // A failed quota request still knows which account it asked about. + const usageLimits = yield* Effect.gen(function* () { const client = yield* HttpClient.HttpClient; const response = yield* client.execute( HttpClientRequest.get("https://cli-chat-proxy.grok.com/v1/billing?format=credits").pipe( @@ -135,14 +169,7 @@ export const readGrokUsageLimits = Effect.fn("readGrokUsageLimits")(function* ( return grokUsageResponseToLimits(body, checkedAt); }).pipe( Effect.timeout("10 seconds"), - Effect.catch(() => - Effect.succeed( - makeUnavailableUsageLimits({ - checkedAt, - reason: "probeFailed", - message: "Grok could not read usage limits.", - }), - ), - ), + Effect.orElseSucceed(() => probeFailed), ); + return { email, usageLimits }; }); diff --git a/apps/server/src/provider/ModelManifest.ts b/apps/server/src/provider/ModelManifest.ts index b698ed6c0c82..92a01336bbbb 100644 --- a/apps/server/src/provider/ModelManifest.ts +++ b/apps/server/src/provider/ModelManifest.ts @@ -408,7 +408,7 @@ export const make = Effect.gen(function* () { fetchedAtMs = now; yield* encodeManifestCache({ fetchedAtMs: now, manifest: fetched }).pipe( Effect.flatMap((serialized) => fileSystem.writeFileString(cachePath, serialized)), - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, ); return manifest; }); diff --git a/apps/server/src/provider/RuntimeInstructions.test.ts b/apps/server/src/provider/RuntimeInstructions.test.ts index e73c50adfd6d..10f8413b5a13 100644 --- a/apps/server/src/provider/RuntimeInstructions.test.ts +++ b/apps/server/src/provider/RuntimeInstructions.test.ts @@ -20,6 +20,15 @@ describe("buildRuntimeInstructions", () => { ).toContain("through the Codex harness, as custom model with high reasoning effort."); }); + it("names the model by display name and slug when they differ", () => { + expect( + buildRuntimeInstructions({ harness: "Codex", model: "gpt-5.4", modelName: "GPT-5.4" }), + ).toContain("through the Codex harness, as GPT-5.4 (model slug: gpt-5.4)."); + expect( + buildRuntimeInstructions({ harness: "Codex", model: "my-model", modelName: "my-model" }), + ).toContain("through the Codex harness, as my-model."); + }); + it.each([undefined, "", "auto", "default"])("omits unresolved model %s", (model) => { const instructions = buildRuntimeInstructions({ harness: "Cursor", model }); expect(instructions).toContain("through the Cursor harness."); diff --git a/apps/server/src/provider/RuntimeInstructions.ts b/apps/server/src/provider/RuntimeInstructions.ts index 5e72586062e5..afaba915d785 100644 --- a/apps/server/src/provider/RuntimeInstructions.ts +++ b/apps/server/src/provider/RuntimeInstructions.ts @@ -2,16 +2,23 @@ const PULL_REQUEST_LINKING_INSTRUCTIONS = ` When the t3-code MCP server exposes link_pull_request, you must use it to register every pull request you create or work on for this thread. Call link_pull_request with the full PR URL immediately after creating a PR or starting work on an existing PR. For a stack, call it for every layer, not just the current branch or the top PR. This applies when creating or updating PRs through gh, gh stack, another CLI, or the host API: those operations do not register the PRs with this thread. Linking an already-linked PR is safe. Before finishing PR work, call list_thread_pull_requests and link any PR from your work that is missing. Do not link unrelated PRs mentioned only as background. If a linking call fails, report that failure instead of claiming the PR is linked. `; -/** Shared runtime context; omit model and effort when the harness manages them dynamically. */ +/** + * Shared runtime context; omit model and effort when the harness manages them dynamically. + * `modelName` is the display name users see in the model picker; `model` is the slug. + */ export function buildRuntimeInstructions(runtime: { readonly harness: string; readonly model?: string | undefined; + readonly modelName?: string | undefined; readonly reasoningEffort?: string | undefined; }): string { const harness = toSingleLine(runtime.harness); const model = toSingleLine(runtime.model ?? ""); + const modelName = toSingleLine(runtime.modelName ?? ""); const effort = toSingleLine(runtime.reasoningEffort ?? ""); - const modelInfo = model && model !== "auto" && model !== "default" ? `, as ${model}` : ""; + const modelLabel = + modelName && modelName !== model ? `${modelName} (model slug: ${model})` : model; + const modelInfo = model && model !== "auto" && model !== "default" ? `, as ${modelLabel}` : ""; const effortInfo = effort ? ` with ${effort} reasoning effort` : ""; return `In case you're asked: you are running in T3 Code through the ${harness} harness${modelInfo}${effortInfo}. No need to mention this otherwise. You can embed images and videos in your response using Markdown with absolute file paths.\n\n${PULL_REQUEST_LINKING_INSTRUCTIONS}`; } diff --git a/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts b/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts index 45a5cadb9a31..dde5979c0f56 100644 --- a/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts +++ b/apps/server/src/provider/acp/AcpJsonRpcConnection.test.ts @@ -824,6 +824,54 @@ describe("AcpSessionRuntime", () => { ), ); + it.effect("keeps one answer when an earlier tool reports progress mid-stream", () => + Effect.gen(function* () { + const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; + yield* runtime.start(); + yield* runtime.prompt({ prompt: [{ type: "text", text: "hi" }] }); + + const notes = Array.from(yield* Stream.runCollect(Stream.take(runtime.getEvents(), 9))); + // The coalesced progress tick emits nothing, and neither the completion + // nor a repeated one splits the markdown table across items. + expect(notes.map((note) => note._tag)).toEqual([ + "ToolCallUpdated", + "AssistantItemStarted", + "ContentDelta", + "ContentDelta", + "ToolCallUpdated", + "ContentDelta", + "ToolCallUpdated", + "ContentDelta", + "AssistantItemCompleted", + ]); + const itemIds = new Set( + notes.flatMap((note) => + note._tag === "ContentDelta" || + note._tag === "AssistantItemStarted" || + note._tag === "AssistantItemCompleted" + ? [note.itemId] + : [], + ), + ); + expect(itemIds.size).toBe(1); + }).pipe( + Effect.provide( + AcpSessionRuntime.layer({ + spawn: { + command: mockAgentCommand, + args: mockAgentArgs, + env: { T3_ACP_EMIT_BACKGROUND_TOOL_DURING_ANSWER: "1" }, + }, + cwd: process.cwd(), + clientInfo: { name: "t3-test", version: "0.0.0" }, + authMethodId: "test", + }), + ), + Effect.scoped, + Effect.provide(NodeServices.layer), + ), + ); + it.effect("emits status-only tool updates through completion", () => Effect.gen(function* () { const runtime = yield* AcpSessionRuntime.AcpSessionRuntime; diff --git a/apps/server/src/provider/acp/AcpSessionRuntime.ts b/apps/server/src/provider/acp/AcpSessionRuntime.ts index 8c9382fd755c..0e78fc491e8e 100644 --- a/apps/server/src/provider/acp/AcpSessionRuntime.ts +++ b/apps/server/src/provider/acp/AcpSessionRuntime.ts @@ -43,6 +43,8 @@ import { type AcpToolCallState, } from "./AcpRuntimeModel.ts"; +const MAX_SHOWN_TOOL_CALL_IDS = 256; + interface AcpToolCallTrackedState { readonly state: AcpToolCallState; readonly lastEmittedDetailLength: number | undefined; @@ -344,6 +346,9 @@ export const make = ( const eventQueue = yield* Queue.unbounded(); const modeStateRef = yield* Ref.make(undefined); const toolCallsRef = yield* Ref.make(new Map()); + // Recently shown tool calls. A late update to a finished call is not a new + // boundary in the answer, although its progress state is gone. + const shownToolCallIds = new Set(); const assistantItemRuntimeId = yield* crypto.randomUUIDv4.pipe( Effect.mapError( (cause) => @@ -580,6 +585,7 @@ export const make = ( modeStateRef, configOptionsRef, toolCallsRef, + shownToolCallIds, assistantSegmentRef, assistantItemRuntimeId, params: notification, @@ -832,17 +838,16 @@ export const make = ( resumePayload, acp.agent.resumeSession(resumePayload).pipe( Effect.timeoutOption(options.sessionLoadTimeout ?? defaultSessionLoadTimeout), - Effect.flatMap((result) => - Option.isSome(result) - ? Effect.succeed(result.value) - : Effect.fail( - new EffectAcpErrors.AcpTransportError({ - operation: "call-rpc", - method: "session/resume", - detail: "session/resume timed out waiting for the agent response.", - cause: undefined, - }), - ), + Effect.flatMap( + Effect.fromOption( + () => + new EffectAcpErrors.AcpTransportError({ + operation: "call-rpc", + method: "session/resume", + detail: "session/resume timed out waiting for the agent response.", + cause: undefined, + }), + ), ), ), ); @@ -886,19 +891,16 @@ export const make = ( ).pipe( Effect.ensuring(Fiber.interrupt(idleFiber).pipe(Effect.ignore)), Effect.timeoutOption(sessionLoadTimeout), - Effect.flatMap((result) => - Option.match(result, { - onNone: () => - Effect.fail( - new EffectAcpErrors.AcpTransportError({ - operation: "call-rpc", - method: "session/load", - detail: "session/load timed out waiting for RPC response or replay idle gap", - cause: undefined, - }), - ), - onSome: Effect.succeed, - }), + Effect.flatMap( + Effect.fromOption( + () => + new EffectAcpErrors.AcpTransportError({ + operation: "call-rpc", + method: "session/load", + detail: "session/load timed out waiting for RPC response or replay idle gap", + cause: undefined, + }), + ), ), Effect.tap((result) => logRequest({ @@ -1143,12 +1145,14 @@ export const make = ( }).pipe(Effect.forkIn(runtimeScope)); return yield* Effect.raceFirst( Fiber.join(activePrompt.fiber).pipe( - Effect.catchCause((cause) => - options.cancelBehavior !== "wait-for-prompt" && Cause.hasInterruptsOnly(cause) - ? Effect.succeed({ - stopReason: "cancelled", - } satisfies EffectAcpSchema.PromptResponse) - : Effect.failCause(cause), + Effect.catchCauseIf( + (cause) => + options.cancelBehavior !== "wait-for-prompt" && + Cause.hasInterruptsOnly(cause), + () => + Effect.succeed({ + stopReason: "cancelled", + } satisfies EffectAcpSchema.PromptResponse), ), ), Fiber.join(stallFiber).pipe( @@ -1293,6 +1297,7 @@ const handleSessionUpdate = ({ modeStateRef, configOptionsRef, toolCallsRef, + shownToolCallIds, assistantSegmentRef, assistantItemRuntimeId, params, @@ -1301,6 +1306,7 @@ const handleSessionUpdate = ({ readonly modeStateRef: Ref.Ref; readonly configOptionsRef: Ref.Ref>; readonly toolCallsRef: Ref.Ref>; + readonly shownToolCallIds: Set; readonly assistantSegmentRef: Ref.Ref; readonly assistantItemRuntimeId: string; readonly params: EffectAcpSchema.SessionNotification; @@ -1317,11 +1323,7 @@ const handleSessionUpdate = ({ } for (const event of parsed.events) { if (event._tag === "ToolCallUpdated") { - yield* closeActiveAssistantSegment({ - queue, - assistantSegmentRef, - }); - const { merged, decision } = yield* Ref.modify(toolCallsRef, (current) => { + const { merged, decision, active } = yield* Ref.modify(toolCallsRef, (current) => { const tracked = current.get(event.toolCall.toolCallId); const previous = tracked?.state; const nextToolCall = mergeToolCallState(previous, event.toolCall); @@ -1343,11 +1345,22 @@ const handleSessionUpdate = ({ skippedSinceEmit: decision.skippedSinceEmit, }); } - return [{ merged: nextToolCall, decision }, next] as const; + return [{ merged: nextToolCall, decision, active: tracked !== undefined }, next] as const; }); if (!decision.emit) { continue; } + // A new tool call is a boundary in the prose. Progress on a call that + // is already shown, such as a background command finishing, is not. + if (!shownToolCallIds.has(merged.toolCallId)) { + shownToolCallIds.add(merged.toolCallId); + // Only recent calls get late updates; keep a long session bounded. + if (shownToolCallIds.size > MAX_SHOWN_TOOL_CALL_IDS) { + shownToolCallIds.delete(shownToolCallIds.values().next().value!); + } + // A call still running is already on screen, even if it aged out. + if (!active) yield* closeActiveAssistantSegment({ queue, assistantSegmentRef }); + } yield* Queue.offer(queue, { _tag: "ToolCallUpdated", toolCall: merged, diff --git a/apps/server/src/provider/acp/AntigravitySessionFiles.ts b/apps/server/src/provider/acp/AntigravitySessionFiles.ts index 07d66065d9ee..f20640bac0a0 100644 --- a/apps/server/src/provider/acp/AntigravitySessionFiles.ts +++ b/apps/server/src/provider/acp/AntigravitySessionFiles.ts @@ -43,10 +43,10 @@ export const removeAntigravitySessionFiles = Effect.fn("removeAntigravitySession ); /** - * Removes every per-process runtime temp directory under the profile. Call - * once when the driver starts, before it launches any process, so a previous - * server that was killed mid-session cannot leave unpacked runtimes behind. - * Only the profile-owned directory is touched. The system temp directory + * Removes every per-process runtime temp directory under an instance's root. + * Call once when the driver starts, before it launches any process, so a + * previous server that was killed mid-session cannot leave unpacked runtimes + * behind. Only T3-owned directories are touched. The system temp directory * belongs to other programs and Windows does not lock data files, so sweeping * it could gut a live extraction. */ diff --git a/apps/server/src/provider/antigravityAuthSupport.test.ts b/apps/server/src/provider/antigravityAuthSupport.test.ts index 01fe516454ea..c2402c87c3ca 100644 --- a/apps/server/src/provider/antigravityAuthSupport.test.ts +++ b/apps/server/src/provider/antigravityAuthSupport.test.ts @@ -1,6 +1,8 @@ // @effect-diagnostics-next-line nodeBuiltinImport:off import * as NodeChildProcess from "node:child_process"; +import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; +import * as NodePath from "@effect/platform-node/NodePath"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { ProviderInstanceId } from "@t3tools/contracts"; import { @@ -11,6 +13,7 @@ import { import { describe, expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as FileSystem from "effect/FileSystem"; +import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; @@ -36,7 +39,7 @@ import { makeAntigravityStdoutTransform, parseAntigravityAuthorizationUrl, prepareAntigravityProfile, - resolveAntigravityProfileDirectory, + resolveAntigravityInstanceDirectories, } from "./antigravityAuthSupport.ts"; const authorizationUrl = @@ -246,20 +249,44 @@ describe("Antigravity process environment", () => { } }); - it("keeps accounts separate even when instance IDs differ only by case", () => { - const first = resolveAntigravityProfileDirectory( - "/userdata", - ProviderInstanceId.make("antigravity"), - ); - const second = resolveAntigravityProfileDirectory( - "/userdata", - ProviderInstanceId.make("Antigravity"), - ); - expect(first.toLowerCase()).not.toBe(second.toLowerCase()); - expect( - resolveAntigravityProfileDirectory("/userdata", ProviderInstanceId.make("antigravity")), - ).toBe(first); - }); + it.effect("keeps accounts separate even when instance IDs differ only by case", () => + Effect.gen(function* () { + const first = yield* resolveAntigravityInstanceDirectories( + "/userdata", + ProviderInstanceId.make("antigravity"), + ); + const second = yield* resolveAntigravityInstanceDirectories( + "/userdata", + ProviderInstanceId.make("Antigravity"), + ); + // Existing sign-ins live at this path; it must not move. + expect(first.profile).toBe( + "/userdata/providers/antigravity/ac0a3dfd6dddb20962cecff6ee5fe65e19d3923be20e52c5ab52ff877f7e4c32", + ); + expect(first.profile.toLowerCase()).not.toBe(second.profile.toLowerCase()); + expect(first.runtimeTemp.toLowerCase()).not.toBe(second.runtimeTemp.toLowerCase()); + }).pipe(Effect.provide(Layer.mergeAll(NodeCrypto.layer, NodePath.layerPosix))), + ); + + it.effect("keeps the unpacked Windows runtime under MAX_PATH for long user names", () => + Effect.gen(function* () { + const path = yield* Path.Path; + // Deepest member of the official agy_acp_server_1.1.1 windows-x86_64 bundle. + const deepestMember = + "google3\\cloud\\developer_experience\\antigravity_extensions\\acp_server\\_private__agy_acp_server_bin.lazy_imports_info.json"; + const directories = yield* resolveAntigravityInstanceDirectories( + "C:\\Users\\a-twenty-char-person\\.t3\\userdata", + ProviderInstanceId.make("antigravity"), + ); + const extracted = (tempDirectory: string) => + path.join(tempDirectory, "run-AbC123", "_MEI000012ab2", deepestMember); + // MAX_PATH is 260 including the terminating NUL. + expect(extracted(directories.runtimeTemp).length).toBeLessThan(260); + expect( + extracted(path.join(directories.profile, "antigravity-acp", "tmp")).length, + ).toBeGreaterThanOrEqual(260); + }).pipe(Effect.provide(Layer.mergeAll(NodeCrypto.layer, NodePath.layerWin32))), + ); }); describe("Antigravity authorization URL", () => { diff --git a/apps/server/src/provider/antigravityAuthSupport.ts b/apps/server/src/provider/antigravityAuthSupport.ts index b48368ad2a1d..6eaa4ec7a497 100644 --- a/apps/server/src/provider/antigravityAuthSupport.ts +++ b/apps/server/src/provider/antigravityAuthSupport.ts @@ -1,13 +1,12 @@ -import * as NodeCrypto from "node:crypto"; // @effect-diagnostics-next-line nodeBuiltinImport:off - Effect's symlink has no type argument, and Windows needs a junction to link without elevation. import * as NodeFSP from "node:fs/promises"; -// @effect-diagnostics-next-line nodeBuiltinImport:off - resolveAntigravityProfileDirectory is a pure sync helper, so it cannot use the Path service. -import * as NodePath from "node:path"; import type { AntigravityAuthMethod, ProviderInstanceId } from "@t3tools/contracts"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import { resolveNodeExecutable, nodeRuntimeUnavailableMessage } from "@t3tools/shared/nodeRuntime"; +import * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; +import * as Encoding from "effect/Encoding"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; import type * as PlatformError from "effect/PlatformError"; @@ -185,19 +184,32 @@ export function isAntigravitySignInRequiredError(error: unknown): boolean { ); } -/** Keeps case-sensitive instance IDs separate on case-insensitive filesystems. */ -export function resolveAntigravityProfileDirectory( - stateDir: string, - instanceId: ProviderInstanceId, -): string { - const directoryName = NodeCrypto.createHash("sha256").update(instanceId).digest("hex"); - return NodePath.join(stateDir, "providers", "antigravity", directoryName); +export interface AntigravityInstanceDirectories { + /** GEMINI_HOME for the agent. Holds the instance's Google sign-in. */ + readonly profile: string; + /** + * Parent of the per-process directories the agent unpacks into. It sits + * beside the profile, not inside it: the agent unpacks members up to 120 + * characters deep, and the profile's longer name would push them past + * Windows' 260-character path limit. + */ + readonly runtimeTemp: string; } -/** Parent of the per-process runtime temp directories inside a profile. */ -export function resolveAntigravityRuntimeTempDirectory(profileDirectory: string): string { - return NodePath.join(profileDirectory, "antigravity-acp", "tmp"); -} +/** Hashes the instance ID so case-only differences stay separate on case-insensitive filesystems. */ +export const resolveAntigravityInstanceDirectories = Effect.fn( + "resolveAntigravityInstanceDirectories", +)(function* (stateDir: string, instanceId: ProviderInstanceId) { + const crypto = yield* Crypto.Crypto; + const path = yield* Path.Path; + const key = Encoding.encodeHex( + yield* crypto.digest("SHA-256", new TextEncoder().encode(instanceId)), + ); + return { + profile: path.join(stateDir, "providers", "antigravity", key), + runtimeTemp: path.join(stateDir, "antigravity-tmp", key.slice(0, 12)), + } satisfies AntigravityInstanceDirectories; +}); function quoteBrowserArgument(value: string): string { return `'${value.replaceAll("'", `'"'"'`)}'`; @@ -265,9 +277,7 @@ const linkAntigravityUserSkills = Effect.fn("linkAntigravityUserSkills")(functio yield* Effect.gen(function* () { const existing = yield* fs.readLink(link).pipe( Effect.map((value): string | undefined => path.resolve(path.dirname(link), value)), - Effect.catch((error) => - error.reason._tag === "NotFound" ? Effect.succeed(undefined) : Effect.fail(error), - ), + Effect.catchReason("PlatformError", "NotFound", () => Effect.undefined), ); if (existing === target) return; if (existing !== undefined) { @@ -300,6 +310,8 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( readonly auth?: AntigravityAuthConfig; /** Home the agent expands `~` against. Defaults to the launch environment's. */ readonly userHome?: string; + /** Parent of per-process temp directories. Defaults to one inside the profile. */ + readonly tempDirectory?: string; }) { const auth = input.auth ?? ANTIGRAVITY_PERSONAL_AUTH; const fs = yield* FileSystem.FileSystem; @@ -337,7 +349,7 @@ export const prepareAntigravityProfile = Effect.fn("prepareAntigravityProfile")( const geminiHome = path.resolve(input.profileDirectory); const acpDirectory = path.join(geminiHome, "antigravity-acp"); - const tempDirectory = resolveAntigravityRuntimeTempDirectory(geminiHome); + const tempDirectory = input.tempDirectory ?? path.join(acpDirectory, "tmp"); const profile: AntigravityProfile = { platform, geminiHome, diff --git a/apps/server/src/provider/cursorCredentialStore.test.ts b/apps/server/src/provider/cursorCredentialStore.test.ts new file mode 100644 index 000000000000..051cb5425c69 --- /dev/null +++ b/apps/server/src/provider/cursorCredentialStore.test.ts @@ -0,0 +1,22 @@ +import { assert, describe, it } from "@effect/vitest"; + +import { makeCachedCursorAccessTokenReader } from "./cursorCredentialStore.ts"; + +describe("Cursor Keychain reader", () => { + it("shares concurrent reads and rechecks after the cache expires", async () => { + let reads = 0; + let time = 0; + const read = makeCachedCursorAccessTokenReader( + async () => { + reads++; + return `token-${reads}`; + }, + () => time, + ); + assert.deepStrictEqual(await Promise.all([read(), read()]), ["token-1", "token-1"]); + assert.strictEqual(await read(), "token-1"); + assert.strictEqual(reads, 1); + time = 5 * 60_000; + assert.strictEqual(await read(), "token-2"); + }); +}); diff --git a/apps/server/src/provider/cursorCredentialStore.ts b/apps/server/src/provider/cursorCredentialStore.ts new file mode 100644 index 000000000000..9d7d1c3bccff --- /dev/null +++ b/apps/server/src/provider/cursorCredentialStore.ts @@ -0,0 +1,33 @@ +import * as NodeModule from "node:module"; + +const CACHE_MS = 5 * 60_000; + +const requireForKeyring = NodeModule.createRequire(import.meta.url); + +/** Share one Keychain request across usage history and limits in this server process. */ +export function makeCachedCursorAccessTokenReader( + read: () => Promise, + now: () => number = Date.now, +): () => Promise { + let cached: { token: string; until: number } | null = null; + let pending: Promise | null = null; + return () => { + if (cached && cached.until > now()) return Promise.resolve(cached.token); + if (pending) return pending; + pending = read() + .then((token) => { + cached = token ? { token, until: now() + CACHE_MS } : null; + return token; + }) + .finally(() => { + pending = null; + }); + return pending; + }; +} + +/** Read the Cursor CLI's default macOS credential without invoking the shared security binary. */ +export const readMacCursorAccessToken = makeCachedCursorAccessTokenReader(async () => { + const { AsyncEntry } = requireForKeyring("@napi-rs/keyring") as typeof import("@napi-rs/keyring"); + return (await new AsyncEntry("cursor-access-token", "cursor-user").getPassword()) ?? null; +}); diff --git a/apps/server/src/provider/makeManagedServerProvider.ts b/apps/server/src/provider/makeManagedServerProvider.ts index c180412ec42d..119c577474c8 100644 --- a/apps/server/src/provider/makeManagedServerProvider.ts +++ b/apps/server/src/provider/makeManagedServerProvider.ts @@ -252,6 +252,7 @@ export const makeManagedServerProvider = Effect.fn("makeManagedServerProvider")( yield* Effect.forever( getRefreshInterval.pipe( Effect.flatMap((refreshInterval) => + // @effect-diagnostics-next-line raceFirstWithSleepToTimeout:off - races the interval against a settings-change signal, not a timeout Effect.raceFirst( Effect.sleep( Duration.toMillis(Duration.fromInputUnsafe(refreshInterval)) <= 0 diff --git a/apps/server/src/provider/model-manifest.json b/apps/server/src/provider/model-manifest.json index 69e51bcb019a..b7531c21c981 100644 --- a/apps/server/src/provider/model-manifest.json +++ b/apps/server/src/provider/model-manifest.json @@ -1,14 +1,15 @@ { "version": 1, - "updatedAt": "2026-09-24T00:11:29Z", + "updatedAt": "2026-09-24T18:40:00Z", "compatibility": [ { "driver": "codex", "t3CodeRange": ">=0.0.42", - "recommendedRange": ">=0.129.0", + "recommendedRange": ">=0.156.0", "ranges": [ - { "range": ">=0.129.0", "status": "supported" }, - { "range": "<0.129.0", "status": "broken" } + { "range": ">=0.156.0", "status": "supported" }, + { "range": ">=0.149.0 <0.156.0", "status": "unsupported" }, + { "range": "<0.149.0", "status": "broken" } ] }, { diff --git a/apps/server/src/provider/providerCompatibility.test.ts b/apps/server/src/provider/providerCompatibility.test.ts index ed3e606126ae..d711e2092064 100644 --- a/apps/server/src/provider/providerCompatibility.test.ts +++ b/apps/server/src/provider/providerCompatibility.test.ts @@ -66,6 +66,24 @@ describe("provider compatibility", () => { } }); + it("supports Codex 0.156 and marks Codex without Thread.projectId broken", () => { + const bundled = ModelManifest.BUNDLED_MODEL_MANIFEST.compatibility; + for (const [t3CodeVersion, codexVersion, expected] of [ + ["0.0.42", "0.148.0", "broken"], + ["0.0.42", "0.149.0", "unsupported"], + ["0.0.42", "0.155.0", "unsupported"], + ["0.0.42", "0.156.0", "supported"], + ["0.0.43-nightly.20260924.2200", "0.153.3", "unsupported"], + ["0.0.43-nightly.20260924.2200", "0.156.1", "supported"], + ] as const) { + assert.strictEqual( + resolveProviderCompatibility(bundled, driver, codexVersion, t3CodeVersion)?.status, + expected, + `T3 Code ${t3CodeVersion} with Codex ${codexVersion}`, + ); + } + }); + it("compares Cursor build dates without treating semver prereleases as stable", () => { const cursor = ProviderDriverKind.make("cursor"); const cursorPolicy: ProviderCompatibilityPolicy = { diff --git a/apps/server/src/provider/providerInstallation.ts b/apps/server/src/provider/providerInstallation.ts index 470109b8e3ba..b47f52a9d5ea 100644 --- a/apps/server/src/provider/providerInstallation.ts +++ b/apps/server/src/provider/providerInstallation.ts @@ -118,7 +118,7 @@ export const makeProviderInstallation = Effect.fn("makeProviderInstallation")(fu env: mergeProviderInstanceEnvironment({ variables: entry.environment, unresolved: [] }), }).pipe( Effect.map((resolved) => [binaryPath, resolved]), - Effect.catch(() => Effect.succeed([binaryPath])), + Effect.orElseSucceed(() => [binaryPath]), ); }); yield* installation diff --git a/apps/server/src/provider/testFixtures/codexCollabMockPeer.mjs b/apps/server/src/provider/testFixtures/codexCollabMockPeer.mjs index 59ebc18c5fcf..5cc18af8b077 100644 --- a/apps/server/src/provider/testFixtures/codexCollabMockPeer.mjs +++ b/apps/server/src/provider/testFixtures/codexCollabMockPeer.mjs @@ -106,6 +106,14 @@ rl.on("line", (line) => { write({ id, result: fixture.responses.threadStart }); return; } + if (method === "thread/inject_items" && script.recordRequests) { + NodeFS.appendFileSync( + `${process.env.T3_CODEX_COLLAB_SCRIPT}.requests`, + `${JSON.stringify({ method, params: message.params })}\n`, + ); + write({ id, result: {} }); + return; + } if (method === "thread/resume") { if (script.recordRequests) { NodeFS.appendFileSync( diff --git a/apps/server/src/provider/testFixtures/codexMultiAgentWire.json b/apps/server/src/provider/testFixtures/codexMultiAgentWire.json index 08316d3b6334..0f183635b17e 100644 --- a/apps/server/src/provider/testFixtures/codexMultiAgentWire.json +++ b/apps/server/src/provider/testFixtures/codexMultiAgentWire.json @@ -20,6 +20,7 @@ "forkedFromId": null, "parentThreadId": null, "preview": "", + "projectId": null, "ephemeral": false, "historyMode": "legacy", "modelProvider": "openai", @@ -389,6 +390,7 @@ "forkedFromId": null, "parentThreadId": null, "preview": "", + "projectId": null, "ephemeral": false, "historyMode": "legacy", "modelProvider": "openai", diff --git a/apps/server/src/pullRequest/GitHubPullRequestCli.ts b/apps/server/src/pullRequest/GitHubPullRequestCli.ts index 615a2258facf..5eebb31656e5 100644 --- a/apps/server/src/pullRequest/GitHubPullRequestCli.ts +++ b/apps/server/src/pullRequest/GitHubPullRequestCli.ts @@ -1601,31 +1601,30 @@ export const make = Effect.gen(function* () { decode: decodePullRequestCoreJson, }), ), - ).pipe( - Effect.flatMap((core) => { - if (!core.checksTruncated) return Effect.succeed(core); - // gh already pages check contexts. Keep its complete, deduplicated result for - // large check suites instead of letting the first 100 checks imply success. - return readLegacyDetail(input).pipe( - Effect.flatMap((detail) => - detail.headSha !== core.headSha - ? Effect.fail( - new GitHubPullRequestReadError({ - command: "gh", - cwd: input.cwd, - operation: "getPullRequestDetail", - cause: new Error("Pull request head changed while reading checks."), - }), - ) - : Effect.succeed({ - ...core, - checks: detail.checks, - checksState: detail.checksState, - checksTruncated: false, + // gh already pages check contexts. Keep its complete, deduplicated result for + // large check suites instead of letting the first 100 checks imply success. + Effect.filterOrElse( + (core) => !core.checksTruncated, + (core) => + readLegacyDetail(input).pipe( + Effect.filterOrFail( + (detail) => detail.headSha === core.headSha, + () => + new GitHubPullRequestReadError({ + command: "gh", + cwd: input.cwd, + operation: "getPullRequestDetail", + cause: new Error("Pull request head changed while reading checks."), }), + ), + Effect.map((detail) => ({ + ...core, + checks: detail.checks, + checksState: detail.checksState, + checksTruncated: false, + })), ), - ); - }), + ), ); }; @@ -1973,10 +1972,9 @@ export const make = Effect.gen(function* () { // the fallback out: an empty answer under one is already the answer. const hasQuery = (input.query?.trim().length ?? 0) > 0; return read(true).pipe( - Effect.flatMap((batch) => - batch.items.length === 0 && input.cursor === undefined && !hasQuery - ? read(false) - : Effect.succeed(batch), + Effect.filterOrElse( + (batch) => batch.items.length > 0 || input.cursor !== undefined || hasQuery, + () => read(false), ), Effect.flatMap((batch) => { // Match the search query's host support, and enrich only rows that survived paging. @@ -2137,6 +2135,7 @@ export const make = Effect.gen(function* () { }), ); }), + // @effect-diagnostics-next-line flatMapConditionalToFilterOrFail:off - the fallback needs a non-null stack, which a predicate that also reads includeDetails cannot refine. Effect.flatMap((stack) => { if (!input.includeDetails || stack === null) return Effect.succeed(stack); return github diff --git a/apps/server/src/pullRequest/GitLabPullRequestCli.ts b/apps/server/src/pullRequest/GitLabPullRequestCli.ts index 5a07bb1ae37c..c3b607002497 100644 --- a/apps/server/src/pullRequest/GitLabPullRequestCli.ts +++ b/apps/server/src/pullRequest/GitLabPullRequestCli.ts @@ -1250,7 +1250,7 @@ export const make = Effect.gen(function* () { getMergeRequestDiffFileContents: (input) => Effect.gen(function* () { if (input.commit !== undefined && !isCommitSha(input.commit)) { - return yield* Effect.fail(new GitLabDiffCommitError({ command: "glab", cwd: input.cwd })); + return yield* new GitLabDiffCommitError({ command: "glab", cwd: input.cwd }); } const refs = yield* input.commit === undefined ? getDiffRefs(input) diff --git a/apps/server/src/pullRequest/PullRequestReadCache.ts b/apps/server/src/pullRequest/PullRequestReadCache.ts index 1b4ded39b953..724fbe490d7f 100644 --- a/apps/server/src/pullRequest/PullRequestReadCache.ts +++ b/apps/server/src/pullRequest/PullRequestReadCache.ts @@ -77,7 +77,7 @@ export const make = Effect.gen(function* () { () => backing .get("revisions") - .pipe(Effect.flatMap((raw) => Schema.decodeUnknownEffect(revisionCodec)(raw ?? "{}"))), + .pipe(Effect.flatMap((raw) => Schema.decodeEffect(revisionCodec)(raw ?? "{}"))), { capacity: 1, timeToLive: (exit) => (Exit.isSuccess(exit) ? Duration.infinity : Duration.zero), diff --git a/apps/server/src/pullRequest/PullRequestService.test.ts b/apps/server/src/pullRequest/PullRequestService.test.ts index 6065fd27fa7b..bb5cc27b8de9 100644 --- a/apps/server/src/pullRequest/PullRequestService.test.ts +++ b/apps/server/src/pullRequest/PullRequestService.test.ts @@ -3382,13 +3382,13 @@ it.effect("shares one cold viewer lookup across distinct concurrent lists", () = ], }); - yield* Effect.all( - ["all", "authored", "reviewing"].map((involvement) => + yield* Effect.forEach( + ["all", "authored", "reviewing"], + (involvement) => service.list({ state: "open", involvement: involvement as "all" | "authored" | "reviewing", }), - ), { concurrency: "unbounded" }, ); @@ -4459,6 +4459,7 @@ it.effect("keeps routed reads separate when the GitHub account changes", () => ], }); const readOperation = (input: Parameters[0]) => + // @effect-diagnostics-next-line unnecessaryEffectGen:off - the generator unifies the per-operation union of Effect types, which Effect.asVoid cannot infer through. Effect.gen(function* () { yield* service[operation](input); }); @@ -4522,6 +4523,7 @@ it.effect("isolates routed caches for two credentials belonging to the same acco ], }); const readOperation = (input: Parameters[0]) => + // @effect-diagnostics-next-line unnecessaryEffectGen:off - the generator unifies the per-operation union of Effect types, which Effect.asVoid cannot infer through. Effect.gen(function* () { yield* service[operation](input); }); diff --git a/apps/server/src/pullRequest/PullRequestService.ts b/apps/server/src/pullRequest/PullRequestService.ts index b2f643ae4e07..dfa54e6daf8d 100644 --- a/apps/server/src/pullRequest/PullRequestService.ts +++ b/apps/server/src/pullRequest/PullRequestService.ts @@ -2795,7 +2795,7 @@ export const make = Effect.gen(function* () { `project:${input.projectId}`, refScope(input), ]); - const decoded = yield* Schema.decodeUnknownEffect(codec)(payload).pipe(Effect.option); + const decoded = yield* Schema.decodeEffect(codec)(payload).pipe(Effect.option); return Option.isSome(decoded) ? decoded.value : yield* lookup; }); const summaryCodec = Schema.fromJsonString(PullRequestSummary); diff --git a/apps/server/src/relay/AgentAwarenessRelay.test.ts b/apps/server/src/relay/AgentAwarenessRelay.test.ts index 32cf68a61e18..db4f1f53b710 100644 --- a/apps/server/src/relay/AgentAwarenessRelay.test.ts +++ b/apps/server/src/relay/AgentAwarenessRelay.test.ts @@ -576,7 +576,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { yield* Deferred.await(releaseThreadShell); return Option.some(thread); }), - getProjectShellById: () => Effect.succeed(Option.some(project)), + getProjectShellById: () => Effect.succeedSome(project), } as unknown as ProjectionSnapshotQueryShape; const descriptor = { @@ -793,8 +793,8 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { threads: [thread], updatedAt: now, } satisfies OrchestrationShellSnapshot), - getThreadShellById: () => Effect.succeed(Option.some(thread)), - getProjectShellById: () => Effect.succeed(Option.some(project)), + getThreadShellById: () => Effect.succeedSome(thread), + getProjectShellById: () => Effect.succeedSome(project), } as unknown as ProjectionSnapshotQueryShape), ); @@ -915,7 +915,7 @@ describe.sequential("signRelayAgentActivityPublishProof", () => { Layer.succeed(OrchestrationEngineService, {} as OrchestrationEngineShape), Layer.succeed(ProjectionSnapshotQuery, { getThreadShellById: () => Effect.sync(() => Option.fromNullishOr(currentThread)), - getProjectShellById: () => Effect.succeed(Option.some(project)), + getProjectShellById: () => Effect.succeedSome(project), } as unknown as ProjectionSnapshotQueryShape), ); diff --git a/apps/server/src/resourceTelemetry/HostResources.ts b/apps/server/src/resourceTelemetry/HostResources.ts index 032832dd4869..0f61ac1899b6 100644 --- a/apps/server/src/resourceTelemetry/HostResources.ts +++ b/apps/server/src/resourceTelemetry/HostResources.ts @@ -60,7 +60,7 @@ export const make = Effect.fn("makeHostResources")(function* () { if (platform === "linux") { const meminfo = yield* fs .readFileString("/proc/meminfo") - .pipe(Effect.catch(() => Effect.succeed(""))); + .pipe(Effect.orElseSucceed(() => "")); const available = /^MemAvailable:\s+(\d+)\s+kB$/m.exec(meminfo)?.[1]; if (available) availableMemoryBytes = Number(available) * 1024; } else if (platform === "darwin") { @@ -68,7 +68,7 @@ export const make = Effect.fn("makeHostResources")(function* () { .string(ChildProcess.make("/usr/bin/vm_stat", [], { stdin: "ignore", stderr: "ignore" })) .pipe( Effect.timeout("1 second"), - Effect.catch(() => Effect.succeed("")), + Effect.orElseSucceed(() => ""), ); availableMemoryBytes = darwinAvailableMemory(output) ?? availableMemoryBytes; } diff --git a/apps/server/src/server.test.ts b/apps/server/src/server.test.ts index 773df3f1d7f0..62cb9e9b4411 100644 --- a/apps/server/src/server.test.ts +++ b/apps/server/src/server.test.ts @@ -492,7 +492,7 @@ const makeBrowserOtlpPayload = (spanName: string) => url: collector.url, exportInterval: "10 millis", resource: { - serviceName: "t3-web", + serviceName: "t3code-web", attributes: { "service.runtime": "t3-web", "service.mode": "browser", @@ -566,6 +566,7 @@ const buildAppUnderTest = (options?: { >; relayClient?: Partial; cloudCliTokenManager?: Partial; + httpClient?: HttpClient.HttpClient; nativeTelemetryClient?: Partial; desktopTelemetryReceiver?: Partial< DesktopTelemetryReceiver.DesktopTelemetryReceiver["Service"] @@ -591,7 +592,6 @@ const buildAppUnderTest = (options?: { otlpTracesExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpMetricsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpLogsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, - otlpServiceName: "t3-server", otelEnvironment: OtelEnvironment.none, mode: "desktop", port: 0, @@ -658,25 +658,25 @@ const buildAppUnderTest = (options?: { get: () => Effect.succeed(defaultVcsDriver), detect: (input) => defaultVcsDriver.detectRepository(input.cwd).pipe( - Effect.flatMap((repository) => - repository - ? Effect.succeed(repository) - : defaultVcsDriver.isInsideWorkTree(input.cwd).pipe( - Effect.map((isInsideWorkTree) => - isInsideWorkTree - ? { - kind: "git" as const, - rootPath: input.cwd, - metadataPath: null, - freshness: { - source: "live-local" as const, - observedAt: TEST_EPOCH, - expiresAt: Option.none(), - }, - } - : null, - ), + Effect.filterOrElse( + (repository) => repository !== null, + () => + defaultVcsDriver.isInsideWorkTree(input.cwd).pipe( + Effect.map((isInsideWorkTree) => + isInsideWorkTree + ? { + kind: "git" as const, + rootPath: input.cwd, + metadataPath: null, + freshness: { + source: "live-local" as const, + observedAt: TEST_EPOCH, + expiresAt: Option.none(), + }, + } + : null, ), + ), ), Effect.map((repository) => repository @@ -819,20 +819,20 @@ const buildAppUnderTest = (options?: { }), searchThreads: () => Effect.succeed({ matches: [] }), getSnapshotSequence: () => Effect.succeed({ snapshotSequence: 0 }), - getProjectShellById: () => Effect.succeed(Option.none()), - getThreadShellById: () => Effect.succeed(Option.none()), - getThreadDetailById: () => Effect.succeed(Option.none()), - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeedNone, + getThreadShellById: () => Effect.succeedNone, + getThreadDetailById: () => Effect.succeedNone, + getThreadDetailSnapshot: () => Effect.succeedNone, getCounts: () => Effect.succeed({ projectCount: 0, threadCount: 0 }), getEventReplayStats: ({ fromSequenceExclusive, toSequenceInclusive }) => Effect.succeed({ eventCount: Math.max(0, toSequenceInclusive - fromSequenceExclusive), payloadBytes: 0, }), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.succeed([]), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeedNone, ...options?.layers?.projectionSnapshotQuery, }); const providerAdapterRegistryLayer = Layer.mock( @@ -926,7 +926,7 @@ const buildAppUnderTest = (options?: { ...options?.layers?.providerAuth, }), Layer.mock(ProviderInstanceRegistry)({ - getInstance: () => Effect.succeed(undefined), + getInstance: () => Effect.undefined, listInstances: Effect.succeed([]), ...options?.layers?.providerInstanceRegistry, }), @@ -936,7 +936,7 @@ const buildAppUnderTest = (options?: { }), Layer.mock(ProviderSessionDirectory.ProviderSessionDirectory)({ upsert: () => Effect.void, - getBinding: () => Effect.succeed(Option.none()), + getBinding: () => Effect.succeedNone, listThreadIds: () => Effect.succeed([]), listBindings: () => Effect.succeed([]), ...options?.layers?.providerSessionDirectory, @@ -953,7 +953,7 @@ const buildAppUnderTest = (options?: { Layer.mergeAll( Layer.mock(ExternalLauncher.ExternalLauncher)({ resolveAvailableEditors: () => Effect.succeed([]), - resolveFileManagerRevealKind: () => Effect.sync((): undefined => undefined), + resolveFileManagerRevealKind: () => Effect.undefined, ...options?.layers?.externalLauncher, }), Layer.mock(RemoteOpenTargets.RemoteOpenTargets)({ @@ -1213,6 +1213,9 @@ const buildAppUnderTest = (options?: { CloudManagedEndpointRuntime.CloudManagedEndpointRuntime, CloudManagedEndpointRuntime.CloudManagedEndpointRuntime.of({ applyConfig: () => Effect.succeed({ status: "disabled" }), + recoveryRequests: Stream.empty, + requestRecovery: () => Effect.void, + withLinkStateLock: (effect) => effect, ...options?.layers?.cloudManagedEndpointRuntime, }), ), @@ -1234,7 +1237,7 @@ const buildAppUnderTest = (options?: { Layer.provide( Layer.mock(CloudCliTokenManager.CloudCliTokenManager)({ get: Effect.die(new Error("Unexpected T3 Connect CLI authorization request.")), - getExisting: Effect.succeed(Option.none()), + getExisting: Effect.succeedNone, hasCredential: Effect.succeed(false), clear: Effect.void, ...options?.layers?.cloudCliTokenManager, @@ -1261,7 +1264,11 @@ const buildAppUnderTest = (options?: { Layer.provideMerge(makeAuthTestLayer()), Layer.provideMerge(ServerSecretStore.layer), Layer.provide(workspaceAndProjectServicesLayer), - Layer.provideMerge(FetchHttpClient.layer), + Layer.provideMerge( + options?.layers?.httpClient === undefined + ? FetchHttpClient.layer + : Layer.succeed(HttpClient.HttpClient, options.layers.httpClient), + ), Layer.provide(GitHubCli.layer.pipe(Layer.provideMerge(VcsProcess.layer))), Layer.provide(layerConfig), ); @@ -3298,6 +3305,68 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); + it.effect("rejects a non-Cloudflare managed endpoint runtime without persisting the link", () => + Effect.gen(function* () { + const appliedRuntimeConfigs: Array = []; + yield* buildAppUnderTest({ + layers: { + cloudManagedEndpointRuntime: { + applyConfig: (config) => + Effect.sync(() => { + appliedRuntimeConfigs.push(config); + return config === null + ? ({ status: "disabled" } as const) + : ({ status: "unsupported", providerKind: config.providerKind } as const); + }), + }, + }, + }); + + const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", { + privateKeyEncoding: { format: "pem", type: "pkcs8" }, + publicKeyEncoding: { format: "pem", type: "spki" }, + }); + const ownerCookie = yield* getAuthenticatedSessionCookieHeader(); + const relayConfigUrl = yield* getHttpServerUrl("/api/connect/relay-config"); + const relayConfigResponse = yield* fetchEffect(relayConfigUrl, { + method: "POST", + headers: { + cookie: ownerCookie, + "content-type": "application/json", + }, + body: jsonRequestBody({ + relayUrl: "https://relay.example.test", + cloudUserId: "user_123", + environmentCredential: "t3env_test_credential", + cloudMintPublicKey: cloudKeyPair.publicKey, + endpointRuntime: { + providerKind: "manual", + connectorToken: "manual-token", + }, + }), + }); + const relayConfigBody = yield* responseJsonEffect<{ + readonly _tag?: string; + readonly endpointRuntimeStatus?: { readonly status?: string }; + }>(relayConfigResponse); + const linkStateUrl = yield* getHttpServerUrl("/api/connect/link-state"); + const linkStateResponse = yield* fetchEffect(linkStateUrl, { + headers: { cookie: ownerCookie }, + }); + const linkStateBody = yield* responseJsonEffect<{ readonly linked?: boolean }>( + linkStateResponse, + ); + + assert.equal(relayConfigResponse.status, 503); + assert.equal(relayConfigBody._tag, "EnvironmentCloudEndpointUnavailableError"); + assert.equal(relayConfigBody.endpointRuntimeStatus?.status, "unsupported"); + // The connector is never touched for a rejected runtime. + assert.deepEqual(appliedRuntimeConfigs, []); + assert.equal(linkStateResponse.status, 200); + assert.equal(linkStateBody.linked, false); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + it.effect("reports local cloud link state from persisted relay config", () => Effect.gen(function* () { yield* buildAppUnderTest(); @@ -3376,6 +3445,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { it.effect("unlinks local cloud state and disables the managed endpoint runtime", () => Effect.gen(function* () { const appliedRuntimeConfigs: Array = []; + const requestedRecoveryConfigs: Array = []; yield* buildAppUnderTest({ layers: { cloudManagedEndpointRuntime: { @@ -3392,7 +3462,14 @@ it.layer(NodeServices.layer)("server router seam", (it) => { ...(config.tunnelName ? { tunnelName: config.tunnelName } : {}), }); }, + requestRecovery: (config) => + Effect.sync(() => { + requestedRecoveryConfigs.push(config); + }), }, + httpClient: HttpClient.make((request) => + Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ status: "ready" }))), + ), }, }); @@ -3458,6 +3535,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { assert.equal(linkStateBody.relayUrl, null); assert.equal(linkStateBody.relayIssuer, null); assert.deepEqual(appliedRuntimeConfigs, [ + null, { providerKind: "cloudflare_tunnel", connectorToken: "connector-token", @@ -3466,6 +3544,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, null, ]); + assert.deepEqual(requestedRecoveryConfigs, []); }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); @@ -3776,19 +3855,169 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); + it.effect("keeps a managed connector stopped when relay registration fails", () => + Effect.gen(function* () { + const appliedRuntimeConfigs: Array = []; + const relayRequests: Array = []; + yield* buildAppUnderTest({ + layers: { + cloudManagedEndpointRuntime: { + applyConfig: (config) => + Effect.sync(() => { + appliedRuntimeConfigs.push(config); + return config === null + ? ({ status: "disabled" } as const) + : ({ status: "running", providerKind: "cloudflare_tunnel", pid: 123 } as const); + }), + }, + httpClient: HttpClient.make((request) => + Effect.sync(() => { + relayRequests.push(request); + return HttpClientResponse.fromWeb( + request, + Response.json({ message: "relay unavailable" }, { status: 503 }), + ); + }), + ), + }, + }); + + const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", { + privateKeyEncoding: { format: "pem", type: "pkcs8" }, + publicKeyEncoding: { format: "pem", type: "spki" }, + }); + const ownerCookie = yield* getAuthenticatedSessionCookieHeader(); + const relayConfigUrl = yield* getHttpServerUrl("/api/connect/relay-config"); + const relayConfigResponse = yield* fetchEffect(relayConfigUrl, { + method: "POST", + headers: { + cookie: ownerCookie, + "content-type": "application/json", + }, + body: jsonRequestBody({ + relayUrl: "https://relay.example.test", + cloudUserId: "user_123", + environmentCredential: "t3env_test_credential", + cloudMintPublicKey: cloudKeyPair.publicKey, + endpointRuntime: { + providerKind: "cloudflare_tunnel", + connectorToken: "connector-token", + tunnelId: "tunnel-1", + }, + }), + }); + const relayConfigBody = yield* responseJsonEffect<{ readonly _tag?: string }>( + relayConfigResponse, + ); + + assert.equal(relayConfigResponse.status, 500); + assert.equal(relayConfigBody._tag, "EnvironmentHttpInternalServerError"); + assert.equal(relayRequests.length, 3); + assert.deepEqual(appliedRuntimeConfigs, [null]); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + + it.effect( + "queues recovery without starting a connector when relay registration requires it", + () => + Effect.gen(function* () { + const appliedRuntimeConfigs: Array = []; + const requestedRecoveryConfigs: Array = []; + const relayRequests: Array = []; + yield* buildAppUnderTest({ + layers: { + cloudManagedEndpointRuntime: { + applyConfig: (config) => + Effect.sync(() => { + appliedRuntimeConfigs.push(config); + return config === null + ? ({ status: "disabled" } as const) + : ({ status: "running", providerKind: "cloudflare_tunnel", pid: 123 } as const); + }), + requestRecovery: (config) => + Effect.sync(() => { + requestedRecoveryConfigs.push(config); + }), + }, + httpClient: HttpClient.make((request) => + Effect.sync(() => { + relayRequests.push(request); + return HttpClientResponse.fromWeb( + request, + Response.json({ status: "recovery_required" }), + ); + }), + ), + }, + }); + + const cloudKeyPair = NodeCrypto.generateKeyPairSync("ed25519", { + privateKeyEncoding: { format: "pem", type: "pkcs8" }, + publicKeyEncoding: { format: "pem", type: "spki" }, + }); + const ownerCookie = yield* getAuthenticatedSessionCookieHeader(); + const relayConfigUrl = yield* getHttpServerUrl("/api/connect/relay-config"); + const relayConfigResponse = yield* fetchEffect(relayConfigUrl, { + method: "POST", + headers: { + cookie: ownerCookie, + "content-type": "application/json", + }, + body: jsonRequestBody({ + relayUrl: "https://relay.example.test", + cloudUserId: "user_123", + environmentCredential: "t3env_test_credential", + cloudMintPublicKey: cloudKeyPair.publicKey, + endpointRuntime: { + providerKind: "cloudflare_tunnel", + connectorToken: "connector-token", + tunnelId: "tunnel-1", + }, + }), + }); + const relayConfigBody = yield* responseJsonEffect<{ + readonly _tag?: string; + readonly endpointRuntimeStatus?: { readonly status?: string }; + }>(relayConfigResponse); + + assert.equal(relayConfigResponse.status, 503); + assert.equal(relayConfigBody._tag, "EnvironmentCloudEndpointUnavailableError"); + assert.equal(relayConfigBody.endpointRuntimeStatus?.status, "disabled"); + assert.equal(relayRequests.length, 1); + assert.deepEqual(appliedRuntimeConfigs, [null]); + assert.deepEqual(requestedRecoveryConfigs, [ + { + providerKind: "cloudflare_tunnel", + connectorToken: "connector-token", + tunnelId: "tunnel-1", + }, + ]); + }).pipe(Effect.provide(NodeHttpServer.layerTest)), + ); + it.effect("fails relay config when the managed endpoint connector cannot start", () => Effect.gen(function* () { + const appliedRuntimeConfigs: Array = []; yield* buildAppUnderTest({ layers: { cloudManagedEndpointRuntime: { - applyConfig: () => - Effect.succeed({ - status: "failed", - providerKind: "cloudflare_tunnel", - reason: "cloudflared missing", - tunnelId: "tunnel-1", + applyConfig: (config) => + Effect.sync(() => { + appliedRuntimeConfigs.push(config); + return config === null + ? ({ status: "disabled" } as const) + : ({ + status: "failed", + providerKind: "cloudflare_tunnel", + failure: "not-installed", + reason: "cloudflared missing", + tunnelId: "tunnel-1", + } as const); }), }, + httpClient: HttpClient.make((request) => + Effect.succeed(HttpClientResponse.fromWeb(request, Response.json({ status: "ready" }))), + ), }, }); @@ -3827,33 +4056,14 @@ it.layer(NodeServices.layer)("server router seam", (it) => { assert.equal(relayConfigBody.message, "Managed endpoint runtime could not be started."); assert.equal(relayConfigBody.endpointRuntimeStatus?.status, "failed"); assert.equal(relayConfigBody.endpointRuntimeStatus?.reason, "cloudflared missing"); - - const now = yield* DateTime.now; - const healthRequest = makeCloudEnvironmentHealthRequest({ - privateKey: cloudKeyPair.privateKey, - environmentId: testEnvironmentDescriptor.environmentId, - nonce: "cloud-health-after-failed-runtime", - issuedAt: DateTime.formatIso(now), - expiresAt: DateTime.formatIso(DateTime.add(now, { minutes: 5 })), - }); - const healthUrl = yield* getHttpServerUrl("/api/t3-connect/health"); - const healthResponse = yield* fetchEffect(healthUrl, { - method: "POST", - headers: { - "content-type": "application/json", + assert.deepEqual(appliedRuntimeConfigs, [ + null, + { + providerKind: "cloudflare_tunnel", + connectorToken: "connector-token", + tunnelId: "tunnel-1", }, - body: jsonRequestBody(healthRequest), - }); - const healthBody = yield* responseJsonEffect<{ - _tag?: string; - message?: string; - }>(healthResponse); - assert.equal(healthResponse.status, 500); - assert.equal(healthBody._tag, "EnvironmentHttpInternalServerError"); - assert.equal( - healthBody.message, - "Cloud mint public key is not installed for this environment.", - ); + ]); }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); @@ -5147,7 +5357,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { attributes: [ { key: "service.name", - value: { stringValue: "t3-web" }, + value: { stringValue: "t3code-web" }, }, ], }, @@ -5289,7 +5499,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { "rpc.method": "server.getSettings", }, resourceAttributes: { - "service.name": "t3-web", + "service.name": "t3code-web", }, scope: { name: "effect", @@ -5423,7 +5633,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { // the stub's utf8 decode even though the surrounding bytes don't. assert.notEqual(forwarded.body[0], "{"); assert.include(forwarded.body, "client.protobuf.test"); - assert.include(forwarded.body, "t3-web"); + assert.include(forwarded.body, "t3code-web"); }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); @@ -5524,7 +5734,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { assert.deepEqual(record.links, []); assert.equal(record.scope.name, scopeSpan.scope.name); assert.deepEqual(record.scope.attributes, {}); - assert.equal(record.resourceAttributes["service.name"], "t3-web"); + assert.equal(record.resourceAttributes["service.name"], "t3code-web"); assert.equal(record.status?.code, String(span.status.code)); }).pipe(Effect.provide(NodeHttpServer.layerTest)), ); @@ -8839,8 +9049,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { yield* buildAppUnderTest({ layers: { projectionSnapshotQuery: { - getThreadDetailSnapshot: () => - Effect.succeed(Option.some({ snapshotSequence: 1, thread })), + getThreadDetailSnapshot: () => Effect.succeedSome({ snapshotSequence: 1, thread }), }, }, }); @@ -9013,16 +9222,13 @@ it.layer(NodeServices.layer)("server router seam", (it) => { streamDomainEvents: Stream.concat(Stream.make(event), Stream.never), }, projectionSnapshotQuery: { - getThreadDetailSnapshot: () => - Effect.succeed(Option.some({ snapshotSequence: 1, thread })), + getThreadDetailSnapshot: () => Effect.succeedSome({ snapshotSequence: 1, thread }), getThreadShellById: (threadId) => - Effect.succeed( - Option.some({ - ...makeDefaultOrchestrationThreadShell(), - id: threadId, - title: "Build complete", - }), - ), + Effect.succeedSome({ + ...makeDefaultOrchestrationThreadShell(), + id: threadId, + title: "Build complete", + }), }, }, }); @@ -9300,7 +9506,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, projectionSnapshotQuery: { getThreadDetailSnapshot: () => - Effect.succeed(Option.some({ snapshotSequence: 100_000, thread })), + Effect.succeedSome({ snapshotSequence: 100_000, thread }), }, }, }); @@ -9618,8 +9824,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }), }, projectionSnapshotQuery: { - getThreadDetailSnapshot: () => - Effect.succeed(Option.some({ snapshotSequence: 5, thread })), + getThreadDetailSnapshot: () => Effect.succeedSome({ snapshotSequence: 5, thread }), }, }, }); @@ -9707,7 +9912,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { projectionSnapshotQuery: { getThreadDetailSnapshot: (_threadId, options) => { requestedTurnLimit = options?.turnLimit; - return Effect.succeed(Option.some({ snapshotSequence: 5, thread })); + return Effect.succeedSome({ snapshotSequence: 5, thread }); }, }, }, @@ -9800,7 +10005,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { readEvents: store.readFromSequence, }, projectionSnapshotQuery: { - getThreadDetailSnapshot: () => Effect.succeed(Option.none()), + getThreadDetailSnapshot: () => Effect.succeedNone, }, }, }); @@ -10030,8 +10235,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { replayStatsCalls += 1; return { eventCount: 5, payloadBytes: 8 * 1024 * 1024 + 1 }; }), - getThreadDetailSnapshot: () => - Effect.succeed(Option.some({ snapshotSequence: 5, thread })), + getThreadDetailSnapshot: () => Effect.succeedSome({ snapshotSequence: 5, thread }), getShellSnapshot: () => Effect.succeed({ snapshotSequence: 5, @@ -10335,7 +10539,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { ]), }, projectionSnapshotQuery: { - getThreadShellById: () => Effect.succeed(Option.none()), + getThreadShellById: () => Effect.succeedNone, }, }, }); @@ -10393,9 +10597,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { detail: "transient failure", }), ) - : Effect.succeed( - Option.some(makeDefaultOrchestrationThreadShell({ id: threadId })), - ); + : Effect.succeedSome(makeDefaultOrchestrationThreadShell({ id: threadId })); }), }, }, @@ -10455,7 +10657,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => { ]), }, projectionSnapshotQuery: { - getProjectShellById: () => Effect.succeed(Option.none()), + getProjectShellById: () => Effect.succeedNone, }, }, }); @@ -10501,22 +10703,20 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, projectionSnapshotQuery: { getThreadShellById: () => - Effect.succeed( - Option.some( - makeDefaultOrchestrationThreadShell({ - id: threadId, + Effect.succeedSome( + makeDefaultOrchestrationThreadShell({ + id: threadId, + updatedAt: now, + session: { + threadId, + status: "ready", + providerName: "claudeAgent", + runtimeMode: "full-access", + activeTurnId: null, + lastError: null, updatedAt: now, - session: { - threadId, - status: "ready", - providerName: "claudeAgent", - runtimeMode: "full-access", - activeTurnId: null, - lastError: null, - updatedAt: now, - }, - }), - ), + }, + }), ), }, }, @@ -10649,8 +10849,8 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, projectionSnapshotQuery: { getThreadShellById: () => - Effect.succeed( - Option.some(makeDefaultOrchestrationThreadShell({ id: threadId, session: null })), + Effect.succeedSome( + makeDefaultOrchestrationThreadShell({ id: threadId, session: null }), ), }, }, @@ -10703,22 +10903,20 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, projectionSnapshotQuery: { getThreadShellById: () => - Effect.succeed( - Option.some( - makeDefaultOrchestrationThreadShell({ - id: threadId, + Effect.succeedSome( + makeDefaultOrchestrationThreadShell({ + id: threadId, + updatedAt: now, + session: { + threadId, + status: "stopped", + providerName: "claudeAgent", + runtimeMode: "full-access", + activeTurnId: null, + lastError: null, updatedAt: now, - session: { - threadId, - status: "stopped", - providerName: "claudeAgent", - runtimeMode: "full-access", - activeTurnId: null, - lastError: null, - updatedAt: now, - }, - }), - ), + }, + }), ), }, }, @@ -10769,22 +10967,20 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, projectionSnapshotQuery: { getThreadShellById: () => - Effect.succeed( - Option.some( - makeDefaultOrchestrationThreadShell({ - id: threadId, + Effect.succeedSome( + makeDefaultOrchestrationThreadShell({ + id: threadId, + updatedAt: now, + session: { + threadId, + status: "ready", + providerName: "claudeAgent", + runtimeMode: "full-access", + activeTurnId: null, + lastError: null, updatedAt: now, - session: { - threadId, - status: "ready", - providerName: "claudeAgent", - runtimeMode: "full-access", - activeTurnId: null, - lastError: null, - updatedAt: now, - }, - }), - ), + }, + }), ), }, }, @@ -10872,22 +11068,20 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, projectionSnapshotQuery: { getThreadShellById: () => - Effect.succeed( - Option.some( - makeDefaultOrchestrationThreadShell({ - id: threadId, + Effect.succeedSome( + makeDefaultOrchestrationThreadShell({ + id: threadId, + updatedAt: now, + session: { + threadId, + status: "ready", + providerName: "claudeAgent", + runtimeMode: "full-access", + activeTurnId: null, + lastError: null, updatedAt: now, - session: { - threadId, - status: "ready", - providerName: "claudeAgent", - runtimeMode: "full-access", - activeTurnId: null, - lastError: null, - updatedAt: now, - }, - }), - ), + }, + }), ), }, }, @@ -10944,22 +11138,20 @@ it.layer(NodeServices.layer)("server router seam", (it) => { }, projectionSnapshotQuery: { getThreadShellById: () => - Effect.succeed( - Option.some( - makeDefaultOrchestrationThreadShell({ - id: threadId, + Effect.succeedSome( + makeDefaultOrchestrationThreadShell({ + id: threadId, + updatedAt: now, + session: { + threadId, + status: "ready", + providerName: "claudeAgent", + runtimeMode: "full-access", + activeTurnId: null, + lastError: null, updatedAt: now, - session: { - threadId, - status: "ready", - providerName: "claudeAgent", - runtimeMode: "full-access", - activeTurnId: null, - lastError: null, - updatedAt: now, - }, - }), - ), + }, + }), ), }, }, diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index d0661e14e33d..0d211fcdeb0c 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -8,12 +8,16 @@ import { ProviderDriverKind, type RepositoryIdentity, } from "@t3tools/contracts"; +import type { RelayManagedEndpointRuntimeConfig } from "@t3tools/contracts/relay"; import * as Cause from "effect/Cause"; +import * as Clock from "effect/Clock"; import * as Duration from "effect/Duration"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Random from "effect/Random"; import * as Schedule from "effect/Schedule"; +import * as Semaphore from "effect/Semaphore"; import * as Stream from "effect/Stream"; import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; import * as HttpApiBuilder from "effect/unstable/httpapi/HttpApiBuilder"; @@ -131,12 +135,21 @@ import * as EnvironmentAuth from "./auth/EnvironmentAuth.ts"; import { connectHttpApiLayer, pendingServiceUpdateExists, - reconcileDesiredCloudLink, + reconcileDesiredCloudLinkIfStillDesired, + recoverManagedCloudTunnel, + registerManagedCloudTunnelRecovery, + startManagedCloudTunnelIfOriginConfirmed, releaseManagedTunnelOnShutdown, } from "./cloud/http.ts"; import { serverRelayBrokerTracingLayer } from "./cloud/relayTracing.ts"; import { shouldRetryCloudLink } from "./cloud/relayResponse.ts"; import * as CloudManagedEndpointRuntime from "./cloud/ManagedEndpointRuntime.ts"; +import { + MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER, + MANAGED_TUNNEL_RECOVERY_COOLDOWN, + managedTunnelStartupAction, + retryManagedTunnelRegistration, +} from "./cloud/managedTunnelStartup.ts"; import * as CloudCliTokenManager from "./cloud/CliTokenManager.ts"; import * as CloudCliState from "./cloud/CliState.ts"; import * as ServerSelfUpdate from "./cloud/selfUpdate.ts"; @@ -759,10 +772,6 @@ const makeServerLayer = Layer.unwrap( : Layer.empty; const cloudDesiredLinkReconcileLayer = Layer.effectDiscard( Effect.gen(function* () { - if (!hasCloudPublicConfig) { - yield* Deferred.succeed(cloudLinkParked, undefined).pipe(Effect.orDie); - return; - } const releaseManagedTunnel = releaseManagedTunnelOnShutdown().pipe( Effect.timeout("10 seconds"), Effect.tap((released) => @@ -791,33 +800,184 @@ const makeServerLayer = Layer.unwrap( if (!cleanupBeforeActivation) { yield* Effect.addFinalizer(() => releaseManagedTunnel); } - if (!(yield* CloudCliState.readCliDesiredCloudLink)) return; const server = yield* HttpServer.HttpServer; const address = server.address; if (typeof address === "string" || !("port" in address)) return; + const localOrigin = `http://127.0.0.1:${address.port}`; + const endpointRuntime = yield* CloudManagedEndpointRuntime.CloudManagedEndpointRuntime; + const recoveryLock = yield* Semaphore.make(1); + let lastRecoveryAtMillis = 0; + const recoverManagedTunnel = (config: RelayManagedEndpointRuntimeConfig) => + recoveryLock.withPermits(1)( + Effect.gen(function* () { + const elapsed = (yield* Clock.currentTimeMillis) - lastRecoveryAtMillis; + const wait = Duration.toMillis(MANAGED_TUNNEL_RECOVERY_COOLDOWN) - elapsed; + if (wait > 0) yield* Effect.sleep(Duration.millis(wait)); + lastRecoveryAtMillis = yield* Clock.currentTimeMillis; + }).pipe( + Effect.andThen( + recoverManagedCloudTunnel(localOrigin, config, { + retryRuntimeFailures: true, + }), + ), + Effect.retry({ + while: (error) => + shouldRetryCloudLink(error) && + error._tag !== "EnvironmentCloudEndpointUnavailableError", + schedule: Schedule.exponential("1 second").pipe( + Schedule.modifyDelay(({ duration }) => + Effect.succeed(Duration.min(duration, Duration.seconds(30))), + ), + Schedule.jittered, + ), + }), + Effect.tap((recovered) => + recovered ? Effect.logInfo("T3 Connect managed tunnel recovered") : Effect.void, + ), + Effect.catchCause((cause) => + Cause.hasInterrupts(cause) + ? Effect.interrupt + : Effect.logWarning("Failed to recover the T3 Connect managed tunnel", { + cause, + }), + ), + ), + ); + yield* endpointRuntime.recoveryRequests.pipe( + Stream.runForEach(recoverManagedTunnel), + Effect.forkScoped, + ); // No settling delay before the first attempt: routes are already // serving by the time activation opens this gate (the startup // sequence awaits routesReady), and the retry schedule below // covers anything this sleep used to hedge against. Every // millisecond here is dead time on the path to remote // reachability after a restart. - yield* reconcileDesiredCloudLink(`http://127.0.0.1:${address.port}`).pipe( - Effect.retry({ - while: shouldRetryCloudLink, - schedule: Schedule.exponential("1 second").pipe( - Schedule.modifyDelay(({ duration }) => - Effect.succeed(Duration.min(duration, Duration.seconds(30))), + const wantsCliLink = hasCloudPublicConfig + ? yield* CloudCliState.readCliDesiredCloudLink.pipe( + Effect.catch((cause) => + Effect.logWarning("Failed to read the desired T3 Connect link", { cause }).pipe( + Effect.as(false), + ), ), - Schedule.upTo({ duration: "10 minutes" }), - ), - }), - Effect.tap(() => Effect.logInfo("T3 Connect desired link reconciled on startup")), + ) + : false; + // A failed read must not end this fiber before it registers + // recovery and starts consuming recovery requests. "managed" is + // what a missing value means, so it is the safe fallback. + const desiredCliLinkMode = wantsCliLink + ? yield* CloudCliState.readCliDesiredLinkMode.pipe( + Effect.catch((cause) => + Effect.logWarning("Failed to read the desired T3 Connect link mode", { + cause, + }).pipe(Effect.as("managed" as const)), + ), + ) + : null; + // A publish-only link must not expose the host, even if a managed + // config from an earlier link is still stored. + const startedConfirmed = + desiredCliLinkMode === "publish_only" + ? false + : yield* startManagedCloudTunnelIfOriginConfirmed(localOrigin).pipe( + Effect.catch((cause) => + Effect.logWarning("Failed to start the confirmed T3 Connect tunnel", { + cause, + }).pipe(Effect.as(false)), + ), + ); + const startStoredManagedTunnel = startManagedCloudTunnelIfOriginConfirmed(localOrigin, { + requireConfirmedOrigin: false, + }).pipe( + Effect.tap((started) => + started + ? Effect.logWarning( + "T3 Connect started the stored tunnel without relay confirmation", + ) + : Effect.void, + ), Effect.catch((cause) => - Effect.logWarning("Failed to reconcile T3 Connect desired link on startup", { - message: cause.message, - }), + Effect.logWarning("Failed to start the stored T3 Connect tunnel", { cause }), ), + Effect.asVoid, ); + const registerManagedTunnel = retryManagedTunnelRegistration( + registerManagedCloudTunnelRecovery(localOrigin, { + retryRuntimeFailures: true, + }), + (error) => + shouldRetryCloudLink(error) && + error._tag !== "EnvironmentCloudEndpointUnavailableError", + startedConfirmed ? Effect.void : startStoredManagedTunnel, + ).pipe( + Effect.tap((result) => + result.status === "ready" + ? Effect.logInfo("T3 Connect managed tunnel recovery registered") + : Effect.void, + ), + Effect.catchCause((cause) => + Cause.hasInterrupts(cause) + ? Effect.interrupt + : Effect.logWarning("Failed to register T3 Connect managed tunnel recovery", { + cause, + }).pipe(Effect.as({ status: "unavailable" as const })), + ), + ); + // A host without a confirmed marker is on its first boot after the + // upgrade. Spread those registrations so an auto-update wave does + // not hit the relay all at once. + if (!startedConfirmed && desiredCliLinkMode !== "publish_only") { + const jitter = yield* Random.nextIntBetween( + 0, + Duration.toMillis(MANAGED_TUNNEL_FIRST_REGISTRATION_JITTER), + ); + yield* Effect.sleep(Duration.millis(jitter)); + } + const registration = + desiredCliLinkMode === "publish_only" + ? { status: "not_linked" as const } + : yield* registerManagedTunnel; + // A terminal registration failure also allows the stored config + // to start. Transient outages use the fallback above and keep + // registration retrying in this scoped startup fiber. + if (registration.status === "unavailable" && !startedConfirmed) { + yield* startStoredManagedTunnel; + } + const startupAction = managedTunnelStartupAction({ wantsCliLink, registration }); + if (startupAction.action === "request_recovery") { + yield* endpointRuntime.requestRecovery(startupAction.config); + } + if (startupAction.action === "reconcile_link") { + const reconciledMode = yield* reconcileDesiredCloudLinkIfStillDesired( + localOrigin, + ).pipe( + Effect.retry({ + while: shouldRetryCloudLink, + schedule: Schedule.exponential("1 second").pipe( + Schedule.modifyDelay(({ duration }) => + Effect.succeed(Duration.min(duration, Duration.seconds(30))), + ), + Schedule.upTo({ duration: "10 minutes" }), + ), + }), + Effect.tap((mode) => + mode === null + ? Effect.void + : Effect.logInfo("T3 Connect desired link reconciled on startup"), + ), + Effect.catch((cause) => + Effect.logWarning("Failed to reconcile T3 Connect desired link on startup", { + cause, + }).pipe(Effect.as(null)), + ), + ); + if (reconciledMode === "managed") { + const afterReconcile = yield* registerManagedTunnel; + if (afterReconcile.status === "recovery_required") { + yield* endpointRuntime.requestRecovery(afterReconcile.config); + } + } + } }), ); yield* Deferred.succeed(cloudLinkParked, undefined).pipe(Effect.orDie); diff --git a/apps/server/src/serverLogger.test.ts b/apps/server/src/serverLogger.test.ts index d7b6397d2b09..2be9915fd05c 100644 --- a/apps/server/src/serverLogger.test.ts +++ b/apps/server/src/serverLogger.test.ts @@ -1,6 +1,7 @@ import * as NodePath from "@effect/platform-node/NodePath"; import { assert, describe, it } from "@effect/vitest"; import * as NodeOS from "node:os"; +import * as ConfigProvider from "effect/ConfigProvider"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Path from "effect/Path"; @@ -56,7 +57,6 @@ const configLayer = (overrides: Partial) = otlpTracesExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpMetricsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, otlpLogsExport: OtelEnvironment.DEFAULT_SIGNAL_EXPORT, - otlpServiceName: "t3-server", otelEnvironment: OtelEnvironment.none, cwd: baseDir, baseDir, @@ -145,11 +145,38 @@ describe("ServerLoggerLive", () => { const [request] = requests; assert.strictEqual(request?.url, "https://collector.example.com/v1/logs"); assert.include(request?.body ?? "", "server logger under test"); - assert.include(request?.body ?? "", "t3-server"); + assert.include(request?.body ?? "", "t3code-server"); assert.include(request?.body ?? "", "service.runtime"); }), ); + it.effect("keeps its service name while OTEL resource attributes add dimensions", () => + Effect.gen(function* () { + const requests = yield* logThrough({ + otlpLogsUrl: "https://collector.example.com/v1/logs", + }).pipe( + Effect.provide( + ConfigProvider.layer( + ConfigProvider.fromEnv({ + env: { + OTEL_SERVICE_NAME: "renamed", + OTEL_RESOURCE_ATTRIBUTES: + "service.name=renamed,service.namespace=renamed,deployment.environment.name=development", + }, + }), + ), + ), + ); + + assert.lengthOf(requests, 1); + const body = requests[0]?.body ?? ""; + assert.include(body, '"stringValue":"t3code-server"'); + assert.include(body, "deployment.environment.name"); + assert.include(body, '"key":"service.namespace","value":{"stringValue":"t3code"}'); + assert.notInclude(body, "renamed"); + }), + ); + it.effect("stays off the network when no logs endpoint is configured", () => Effect.gen(function* () { const requests = yield* logThrough({}); diff --git a/apps/server/src/serverRuntimeStartup.reconcile.test.ts b/apps/server/src/serverRuntimeStartup.reconcile.test.ts index 37fd210ee6da..050650b11515 100644 --- a/apps/server/src/serverRuntimeStartup.reconcile.test.ts +++ b/apps/server/src/serverRuntimeStartup.reconcile.test.ts @@ -399,18 +399,16 @@ it.effect("does not continue archived or deleted marked sessions", () => { directory: { getBinding: (threadId) => { const thread = threadId === archived.id ? archived : deleted; - return Effect.succeed( - Option.some({ - threadId, - provider: ProviderDriverKind.make("codex"), - providerInstanceId, - status: "running" as const, - resumeCursor: { cursor: threadId }, - runtimePayload: { - continueAfterServerUpdate: thread.session.activeTurnId, - }, - }), - ); + return Effect.succeedSome({ + threadId, + provider: ProviderDriverKind.make("codex"), + providerInstanceId, + status: "running" as const, + resumeCursor: { cursor: threadId }, + runtimePayload: { + continueAfterServerUpdate: thread.session.activeTurnId, + }, + }); }, upsert: () => Effect.void, recordImportedTranscript: () => Effect.die("unused"), @@ -456,18 +454,16 @@ it.effect("retries continuation preparation before settling a persistent failure threads: [thread], directory: { getBinding: () => - Effect.succeed( - Option.some({ - threadId: thread.id, - provider: ProviderDriverKind.make("codex"), - providerInstanceId, - status: "running" as const, - resumeCursor: { cursor: thread.id }, - runtimePayload: { - continueAfterServerUpdate: thread.session.activeTurnId, - }, - }), - ), + Effect.succeedSome({ + threadId: thread.id, + provider: ProviderDriverKind.make("codex"), + providerInstanceId, + status: "running" as const, + resumeCursor: { cursor: thread.id }, + runtimePayload: { + continueAfterServerUpdate: thread.session.activeTurnId, + }, + }), upsert: () => Effect.void, recordImportedTranscript: () => Effect.die("unused"), getProvider: () => Effect.die("unused"), @@ -610,16 +606,14 @@ it.effect( directory: { getBinding: (candidate) => candidate === absent.id - ? Effect.succeed(Option.none()) + ? Effect.succeedNone : candidate === corrupt.id ? Effect.fail(corruptFailure) - : Effect.succeed( - Option.some({ - threadId: candidate, - provider: ProviderDriverKind.make("codex"), - providerInstanceId, - }), - ), + : Effect.succeedSome({ + threadId: candidate, + provider: ProviderDriverKind.make("codex"), + providerInstanceId, + }), upsert: () => Effect.fail(writeFailure), recordImportedTranscript: () => Effect.die("unused"), getProvider: () => Effect.die("unused"), @@ -657,7 +651,7 @@ it.effect("retries failed projections and continues after a persistent failure", return runReconciliation({ threads: [transient, persistent, later], directory: { - getBinding: () => Effect.succeed(Option.none()), + getBinding: () => Effect.succeedNone, upsert: () => Effect.void, recordImportedTranscript: () => Effect.die("unused"), getProvider: () => Effect.die("unused"), @@ -758,19 +752,17 @@ for (const scenario of [ continueAfterRestart: scenario !== "disabled", directory: { getBinding: () => - Effect.succeed( - Option.some({ - threadId: thread.id, - provider: ProviderDriverKind.make("codex"), - providerInstanceId, - status: scenario === "stopped binding" ? "stopped" : "running", - ...(scenario.includes("cursor") ? {} : { resumeCursor: { threadId: thread.id } }), - runtimePayload: { - activeTurnId: scenario === "marked superseded turn" ? "another-turn" : turnId, - ...(scenario.startsWith("marked") ? { continueAfterServerUpdate: turnId } : {}), - }, - }), - ), + Effect.succeedSome({ + threadId: thread.id, + provider: ProviderDriverKind.make("codex"), + providerInstanceId, + status: scenario === "stopped binding" ? "stopped" : "running", + ...(scenario.includes("cursor") ? {} : { resumeCursor: { threadId: thread.id } }), + runtimePayload: { + activeTurnId: scenario === "marked superseded turn" ? "another-turn" : turnId, + ...(scenario.startsWith("marked") ? { continueAfterServerUpdate: turnId } : {}), + }, + }), upsert: (binding) => Effect.sync(() => { upserts.push(binding); @@ -944,9 +936,7 @@ it.effect("settles failed opt-in recovery without retrying the provider turn", ( Effect.gen(function* () { sends.push(input); preparedPayloads.push(binding.runtimePayload); - return yield* Effect.fail( - new ProviderSessionNotFoundError({ threadId: input.threadId }), - ); + return yield* new ProviderSessionNotFoundError({ threadId: input.threadId }); }), }, directory: { diff --git a/apps/server/src/serverRuntimeStartup.test.ts b/apps/server/src/serverRuntimeStartup.test.ts index ab654e15c70b..df879a2cf307 100644 --- a/apps/server/src/serverRuntimeStartup.test.ts +++ b/apps/server/src/serverRuntimeStartup.test.ts @@ -174,27 +174,25 @@ it.effect("resolveAutoBootstrapWelcomeTargets returns existing project and threa getCounts: () => Effect.die("unused"), getEventReplayStats: () => Effect.die("unused"), getActiveProjectByWorkspaceRoot: () => - Effect.succeed( - Option.some({ - id: bootstrapProjectId, - title: "Startup Project", - workspaceRoot: "/tmp/startup-project", - defaultModelSelection: { - instanceId: ProviderInstanceId.make("codex"), - model: DEFAULT_MODEL, - }, - scripts: [], - createdAt: "2026-01-01T00:00:00.000Z", - updatedAt: "2026-01-01T00:00:00.000Z", - deletedAt: null, - }), - ), + Effect.succeedSome({ + id: bootstrapProjectId, + title: "Startup Project", + workspaceRoot: "/tmp/startup-project", + defaultModelSelection: { + instanceId: ProviderInstanceId.make("codex"), + model: DEFAULT_MODEL, + }, + scripts: [], + createdAt: "2026-01-01T00:00:00.000Z", + updatedAt: "2026-01-01T00:00:00.000Z", + deletedAt: null, + }), getProjectShells: () => Effect.die("unused"), getProjectShellById: () => Effect.die("unused"), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.some(bootstrapThreadId)), + getFirstActiveThreadIdByProjectId: () => Effect.succeedSome(bootstrapThreadId), getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), - getFullThreadDiffContext: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeedNone, + getFullThreadDiffContext: () => Effect.succeedNone, getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), getThreadShellById: () => Effect.die("unused"), @@ -321,10 +319,10 @@ it.effect.each([ ), getProjectShells: () => Effect.die("unused"), getProjectShellById: () => Effect.die("unused"), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), - getFullThreadDiffContext: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeedNone, + getFullThreadDiffContext: () => Effect.succeedNone, getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), getThreadShellById: () => Effect.die("unused"), @@ -391,13 +389,13 @@ it.effect( getSnapshotSequence: () => Effect.die("unused"), getCounts: () => Effect.die("unused"), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, getProjectShells: () => Effect.die("unused"), getProjectShellById: () => Effect.die("unused"), getFirstActiveThreadIdByProjectId: () => Effect.die("thread lookup failed"), getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), - getFullThreadDiffContext: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeedNone, + getFullThreadDiffContext: () => Effect.succeedNone, getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), getThreadShellById: () => Effect.die("unused"), @@ -456,13 +454,13 @@ it.effect("resolveAutoBootstrapWelcomeTargets preserves typed UUID generation fa getSnapshotSequence: () => Effect.die("unused"), getCounts: () => Effect.die("unused"), getEventReplayStats: () => Effect.die("unused"), - getActiveProjectByWorkspaceRoot: () => Effect.succeed(Option.none()), + getActiveProjectByWorkspaceRoot: () => Effect.succeedNone, getProjectShells: () => Effect.die("unused"), getProjectShellById: () => Effect.die("unused"), - getFirstActiveThreadIdByProjectId: () => Effect.succeed(Option.none()), + getFirstActiveThreadIdByProjectId: () => Effect.succeedNone, getImportedAgentSessionSources: () => Effect.die("unused"), - getThreadCheckpointContext: () => Effect.succeed(Option.none()), - getFullThreadDiffContext: () => Effect.succeed(Option.none()), + getThreadCheckpointContext: () => Effect.succeedNone, + getFullThreadDiffContext: () => Effect.succeedNone, getThreadRuntimeContext: () => Effect.die("unused"), getTurnStartMessage: () => Effect.die("unused"), getThreadShellById: () => Effect.die("unused"), diff --git a/apps/server/src/serverRuntimeStartup.ts b/apps/server/src/serverRuntimeStartup.ts index eed2f861087d..e4f06175e148 100644 --- a/apps/server/src/serverRuntimeStartup.ts +++ b/apps/server/src/serverRuntimeStartup.ts @@ -34,6 +34,7 @@ import * as Schema from "effect/Schema"; import * as Scope from "effect/Scope"; import * as ServerConfig from "./config.ts"; +import { flushCompileCache } from "./compileCache.ts"; import * as Keybindings from "./keybindings.ts"; import * as ExternalLauncher from "./process/externalLauncher.ts"; import * as OrchestrationEngine from "./orchestration/Services/OrchestrationEngine.ts"; @@ -265,13 +266,13 @@ export const resolveAutoBootstrapWelcomeTargets = Effect.gen(function* () { bootstrapThreadId = existingThreadId.value; } }).pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("startup thread auto-bootstrap failed", { - bootstrapProjectId: nextProjectId, - cause, - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => + Effect.logWarning("startup thread auto-bootstrap failed", { + bootstrapProjectId: nextProjectId, + cause, + }), ), ); }); @@ -313,11 +314,9 @@ const resolveStartupBrowserTarget = Effect.gen(function* () { ? `http://${formatHostForUrl(serverConfig.host)}:${serverConfig.port}` : localUrl; const baseTarget = serverConfig.devUrl?.toString() ?? bindUrl; - return yield* Effect.succeed(serverConfig.mode === "desktop" ? baseTarget : undefined).pipe( - Effect.flatMap((target) => - target ? Effect.succeed(target) : serverAuth.issueStartupPairingUrl(baseTarget), - ), - ); + return serverConfig.mode === "desktop" + ? baseTarget + : yield* serverAuth.issueStartupPairingUrl(baseTarget); }); const DEV_BROWSER_OPEN_MARKER_MAX_AGE_MS = 6 * 60 * 60 * 1000; @@ -523,7 +522,7 @@ export const reconcileProviderSessions = Effect.gen(function* () { const query = yield* ProjectionSnapshotQuery.ProjectionSnapshotQuery; const settings = yield* ServerSettings.ServerSettingsService; const restartSettings = yield* settings.getSettings.pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catch((cause) => Effect.logWarning("could not read restart continuation preference", { cause }).pipe( Effect.as(Option.none()), @@ -585,13 +584,13 @@ export const reconcileProviderSessions = Effect.gen(function* () { continue; } const binding = yield* directory.getBinding(thread.id).pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("failed to read orphaned provider session directory binding", { - threadId: thread.id, - cause, - }).pipe(Effect.as(Option.none())), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => + Effect.logWarning("failed to read orphaned provider session directory binding", { + threadId: thread.id, + cause, + }).pipe(Effect.as(Option.none())), ), ); const continuationMarkerPresent = @@ -642,13 +641,13 @@ export const reconcileProviderSessions = Effect.gen(function* () { }); } }).pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning( - "failed to reconcile orphaned provider session directory binding", - { threadId: thread.id, cause }, - ), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => + Effect.logWarning("failed to reconcile orphaned provider session directory binding", { + threadId: thread.id, + cause, + }), ), ); @@ -669,13 +668,13 @@ export const reconcileProviderSessions = Effect.gen(function* () { }); }).pipe( Effect.retry({ times: 1 }), - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("failed to settle orphaned provider session projection", { - threadId: thread.id, - cause, - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => + Effect.logWarning("failed to settle orphaned provider session projection", { + threadId: thread.id, + cause, + }), ), ); }); @@ -775,10 +774,9 @@ export const reconcileProviderSessions = Effect.gen(function* () { yield* settleAsError(ORPHANED_PROVIDER_SESSION_ERROR); } }).pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("provider session startup reconciliation failed", { cause }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => Effect.logWarning("provider session startup reconciliation failed", { cause }), ), ); @@ -849,21 +847,20 @@ export const reconcileWorktreeSetups = Effect.gen(function* () { createdAt: interruptedAt, }) .pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("failed to settle interrupted worktree setup", { - threadId, - cause, - }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => + Effect.logWarning("failed to settle interrupted worktree setup", { + threadId, + cause, + }), ), ); } }).pipe( - Effect.catchCause((cause) => - Cause.hasInterrupts(cause) - ? Effect.failCause(cause) - : Effect.logWarning("worktree setup startup reconciliation failed", { cause }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterrupts(cause), + (cause) => Effect.logWarning("worktree setup startup reconciliation failed", { cause }), ), ); @@ -1114,6 +1111,7 @@ export const make = (options?: StartupOptions) => }), ); yield* Effect.logDebug("startup phase: complete"); + yield* flushCompileCache; }).pipe( Effect.annotateSpans({ "server.mode": serverConfig.mode, diff --git a/apps/server/src/serverRuntimeState.ts b/apps/server/src/serverRuntimeState.ts index 4afe10bd5b65..26535ae348a2 100644 --- a/apps/server/src/serverRuntimeState.ts +++ b/apps/server/src/serverRuntimeState.ts @@ -142,7 +142,7 @@ export const readPersistedServerRuntimeState = (path: string) => cause, }), ), - onSuccess: (contents) => Effect.succeed(Option.some(contents)), + onSuccess: (contents) => Effect.succeedSome(contents), }), ); if (Option.isNone(raw)) { @@ -155,7 +155,7 @@ export const readPersistedServerRuntimeState = (path: string) => } return yield* decodePersistedServerRuntimeState(trimmed).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.mapError( (cause) => new ServerRuntimeStateError({ diff --git a/apps/server/src/sourceControl/GitHubCli.test.ts b/apps/server/src/sourceControl/GitHubCli.test.ts index 083229be63c6..7eeadacd0dac 100644 --- a/apps/server/src/sourceControl/GitHubCli.test.ts +++ b/apps/server/src/sourceControl/GitHubCli.test.ts @@ -155,8 +155,9 @@ describe("GitHubCli.layer", () => { capacity: 2, timeToLive: "1 minute", }); - const results = yield* Effect.all( - ["github.com", "github.example.test"].map((host, index) => + const results = yield* Effect.forEach( + ["github.com", "github.example.test"], + (host, index) => Cache.get(cache, host).pipe( Effect.provideService(GitHubCli.PinnedGitHubCredential, { host, @@ -164,7 +165,6 @@ describe("GitHubCli.layer", () => { credentialFingerprint: `fingerprint-${index}`, }), ), - ), { concurrency: 2 }, ); expect(results.map((result) => result.stdout)).toEqual(["credential-0", "credential-1"]); diff --git a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts index d8893b29e089..422b2c991040 100644 --- a/apps/server/src/sourceControl/SourceControlProviderRegistry.ts +++ b/apps/server/src/sourceControl/SourceControlProviderRegistry.ts @@ -289,14 +289,14 @@ export const makeWithProviders = Effect.fn("makeSourceControlProviderRegistryWit get, resolveHandle, resolve: (input) => resolveHandle(input).pipe(Effect.map((handle) => handle.provider)), - discover: Effect.all( - discoverySpecs.map((spec) => + discover: Effect.forEach( + discoverySpecs, + (spec) => probeSourceControlProvider({ spec, process, cwd: config.cwd, }), - ), { concurrency: "unbounded" }, ), }); diff --git a/apps/server/src/storageCleanup.ts b/apps/server/src/storageCleanup.ts index 72af05dacf22..400ae635018f 100644 --- a/apps/server/src/storageCleanup.ts +++ b/apps/server/src/storageCleanup.ts @@ -419,10 +419,9 @@ export const make = Effect.gen(function* () { }); const worker = yield* makeDrainableWorker(() => sweep().pipe( - Effect.catchCause((cause) => - Cause.hasInterruptsOnly(cause) - ? Effect.failCause(cause) - : Effect.logWarning("storage cleanup failed", { cause }), + Effect.catchCauseIf( + (cause) => !Cause.hasInterruptsOnly(cause), + (cause) => Effect.logWarning("storage cleanup failed", { cause }), ), ), ); diff --git a/apps/server/src/telemetry/Identify.ts b/apps/server/src/telemetry/Identify.ts index 10f5aea0b81d..1a658a1b5043 100644 --- a/apps/server/src/telemetry/Identify.ts +++ b/apps/server/src/telemetry/Identify.ts @@ -139,7 +139,7 @@ const readIdentityFile = ( filePath: string, ) => fileSystem.readFileString(filePath).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ PlatformError: (cause) => isNotFoundError(cause) @@ -290,7 +290,7 @@ export const getTelemetryIdentifierForHome = Effect.fn("getTelemetryIdentifierFo } const anonymousId = yield* upsertAnonymousId.pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catchTags({ TelemetryIdentityReadError: (error) => logTelemetryIdentityError(error).pipe(Effect.as(Option.none())), diff --git a/apps/server/src/terminal/Manager.test.ts b/apps/server/src/terminal/Manager.test.ts index 80ab2c43e42c..f04161cfbd3c 100644 --- a/apps/server/src/terminal/Manager.test.ts +++ b/apps/server/src/terminal/Manager.test.ts @@ -1230,6 +1230,73 @@ it.layer( }), ); + it.effect("closes only a thread's idle shells, ignoring a helper forked from the shell", () => + Effect.gen(function* () { + // FakePtyAdapter assigns pids from 9000 in open order. + const { manager, ptyAdapter } = yield* createManager(5, { + processTable: Effect.succeed([ + { pid: 9000, ppid: 1, name: "zsh" }, + // An async prompt worker: a copy of the shell with no children. + { pid: 100, ppid: 9000, name: "zsh" }, + { pid: 9001, ppid: 1, name: "zsh" }, + { pid: 200, ppid: 9001, name: "node" }, + { pid: 9002, ppid: 1, name: "zsh" }, + // A subshell with a child is real work. + { pid: 300, ppid: 9002, name: "zsh" }, + { pid: 301, ppid: 300, name: "sleep" }, + { pid: 9003, ppid: 1, name: "zsh" }, + ]), + }).pipe(Effect.provide(withHostPlatform("linux"))); + yield* manager.open(openInput({ terminalId: "idle" })); + yield* manager.open(openInput({ terminalId: "dev-server" })); + yield* manager.open(openInput({ terminalId: "subshell" })); + yield* manager.open(openInput({ threadId: "thread-2" })); + + yield* manager.closeIdle({ threadId: "thread-1" }); + + expect(ptyAdapter.processes.map((process) => process.killed)).toEqual([ + true, + false, + false, + false, + ]); + }), + ); + + it.effect("keeps terminals that get input or output while closeIdle checks them", () => + Effect.gen(function* () { + const ptyAdapter = new FakePtyAdapter(); + // The typed command's process misses the snapshot, but its input or echo lands. + let duringCheck: (pid: number) => Effect.Effect = () => Effect.void; + const { manager, getEvents } = yield* createManager(5, { + ptyAdapter, + subprocessPollIntervalMs: 60_000, + subprocessInspector: (pid) => + duringCheck(pid).pipe( + Effect.as({ hasRunningSubprocess: false, childCommand: null, processIds: [] }), + ), + }); + yield* manager.open(openInput({ terminalId: "typed" })); + yield* manager.open(openInput({ terminalId: "echoed" })); + const [typed, echoed] = ptyAdapter.processes; + duringCheck = (pid) => + pid === typed!.pid + ? manager + .write({ threadId: "thread-1", terminalId: "typed", data: "make build\r" }) + .pipe(Effect.orDie) + : Effect.gen(function* () { + echoed!.emitData("make build\r\n"); + yield* waitFor( + Effect.map(getEvents, (events) => events.some((event) => event.type === "output")), + ); + }).pipe(Effect.orDie); + + yield* manager.closeIdle({ threadId: "thread-1" }); + + expect(ptyAdapter.processes.map((process) => process.killed)).toEqual([false, false]); + }), + ); + it.effect("backs off the spawned fallback when the resource monitor snapshot fails", () => Effect.gen(function* () { const fallbackCalls: Array = []; diff --git a/apps/server/src/terminal/Manager.ts b/apps/server/src/terminal/Manager.ts index 430e268dac93..30049ac59db1 100644 --- a/apps/server/src/terminal/Manager.ts +++ b/apps/server/src/terminal/Manager.ts @@ -198,6 +198,17 @@ export class TerminalManager extends Context.Service< */ readonly close: (input: TerminalCloseInput) => Effect.Effect; + /** + * Close a thread's terminals that wait at an idle shell prompt. A terminal + * that runs a command stays open. When `terminalId` is set, only that + * terminal is considered. Used when a thread settles and when a setup + * script finishes. + */ + readonly closeIdle: (input: { + readonly threadId: string; + readonly terminalId?: string; + }) => Effect.Effect; + /** * Subscribe to terminal runtime events with a direct callback. * @@ -275,6 +286,8 @@ interface TerminalSessionState { exitSignal: number | null; updatedAt: string; eventSequence: number; + /** Counts writes, so closeIdle can see input that has not echoed yet. */ + inputCount: number; cols: number; rows: number; process: PtyAdapter.PtyProcess | null; @@ -692,7 +705,17 @@ function deriveSubprocessInspectResult( terminalPid: number, platform: NodeJS.Platform, ): TerminalSubprocessInspectResult { - const childPid = (snapshot.childrenByParent.get(terminalPid) ?? [])[0]; + const commandName = (pid: number) => + normalizeChildCommandName(snapshot.commandById.get(pid) ?? "", platform); + const shellName = commandName(terminalPid); + // Async prompt themes fork the shell into a helper that waits with no + // children of its own. That copy is not a command the user started. + const childPid = (snapshot.childrenByParent.get(terminalPid) ?? []).find( + (pid) => + shellName === null || + commandName(pid) !== shellName || + (snapshot.childrenByParent.get(pid)?.length ?? 0) > 0, + ); if (childPid === undefined) { return { hasRunningSubprocess: false, childCommand: null, processIds: [] }; } @@ -707,7 +730,7 @@ function deriveSubprocessInspectResult( pending.push(pid); } } - const normalized = normalizeChildCommandName(snapshot.commandById.get(childPid) ?? "", platform); + const normalized = commandName(childPid); return { hasRunningSubprocess: true, childCommand: normalized ? truncateTerminalWireLabel(normalized) : null, @@ -1937,16 +1960,7 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func terminalId: string, ): Effect.fn.Return { return yield* Effect.flatMap(getSession(threadId, terminalId), (session) => - Option.match(session, { - onNone: () => - Effect.fail( - new TerminalSessionLookupError({ - threadId, - terminalId, - }), - ), - onSome: Effect.succeed, - }), + Effect.fromOption(session, () => new TerminalSessionLookupError({ threadId, terminalId })), ); }); @@ -2370,7 +2384,7 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func } const inspectorOption = yield* acquireSubprocessInspector.pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catch((reason) => Effect.logWarning("failed to snapshot processes for terminal subprocess polling", { reason, @@ -2396,7 +2410,7 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func ) { const terminalPid = session.pid; const inspectResult = yield* subprocessInspector(terminalPid).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catch((reason) => Effect.logWarning("failed to check terminal subprocess activity", { threadId: session.threadId, @@ -2549,6 +2563,7 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func exitSignal: null, updatedAt: yield* nowIso, eventSequence: 0, + inputCount: 0, cols, rows, process: null, @@ -2889,6 +2904,7 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func terminalId, }); } + session.inputCount += 1; yield* Effect.try({ try: () => process.write(input.data), catch: (cause) => @@ -2970,6 +2986,7 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func exitSignal: null, updatedAt: yield* nowIso, eventSequence: 0, + inputCount: 0, cols, rows, process: null, @@ -3054,6 +3071,52 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func }), ); + const closeIdle: TerminalManager["Service"]["closeIdle"] = (input) => + withThreadLock( + input.threadId, + Effect.gen(function* () { + const running = (yield* sessionsForThread(input.threadId)).filter( + (session): session is TerminalSessionState & { pid: number } => + session.status === "running" && + Number.isInteger(session.pid) && + (input.terminalId === undefined || session.terminalId === input.terminalId), + ); + if (running.length === 0) return; + // A command started during the process check can miss the snapshot, + // but its input or echo still lands. Both counters only grow, so the + // sum changes when either one does. + const activityMark = (session: TerminalSessionState) => + session.eventSequence + session.inputCount; + const marks = new Map( + running.map((session) => [session.terminalId, activityMark(session)]), + ); + // Inspect now instead of trusting the last poll, so a command started + // since then keeps its terminal. + const { inspector } = yield* acquireSubprocessInspector; + yield* Effect.forEach( + running, + (session) => + inspector(session.pid).pipe( + Effect.flatMap((result) => + result.hasRunningSubprocess || + activityMark(session) !== marks.get(session.terminalId) + ? Effect.void + : closeSession(input.threadId, session.terminalId, false), + ), + ), + { discard: true }, + ); + }), + ).pipe( + // The process check failed, so every terminal stays open. + Effect.catch((error) => + Effect.logWarning("failed to close idle terminals", { + threadId: input.threadId, + error: error.message, + }), + ), + ); + return TerminalManager.of({ open, attachStream, @@ -3062,6 +3125,7 @@ export const makeWithOptions = Effect.fn("TerminalManager.makeWithOptions")(func clear, restart, close, + closeIdle, subscribe, subscribeMetadata, }); diff --git a/apps/server/src/textGeneration/AntigravityTextGeneration.ts b/apps/server/src/textGeneration/AntigravityTextGeneration.ts index 6fd59041ddb0..dacc50bf383d 100644 --- a/apps/server/src/textGeneration/AntigravityTextGeneration.ts +++ b/apps/server/src/textGeneration/AntigravityTextGeneration.ts @@ -238,7 +238,7 @@ export const makeAntigravityTextGeneration = Effect.fn("makeAntigravityTextGener yield* applyAntigravityAcpModelSelection({ runtime, model: input.modelSelection.model, - defaultModel: yield* options.defaultModel ?? Effect.succeed(undefined), + defaultModel: yield* options.defaultModel ?? Effect.undefined, mapError: (cause) => new TextGenerationError({ operation, diff --git a/apps/server/src/textGeneration/CodexTextGeneration.test.ts b/apps/server/src/textGeneration/CodexTextGeneration.test.ts index 91c9cb94b5d5..15220fd9d0af 100644 --- a/apps/server/src/textGeneration/CodexTextGeneration.test.ts +++ b/apps/server/src/textGeneration/CodexTextGeneration.test.ts @@ -556,7 +556,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { }), ), ), - Effect.ensuring(fs.remove(imagePath).pipe(Effect.catch(() => Effect.void))), + Effect.ensuring(fs.remove(imagePath).pipe(Effect.ignore)), ); expect(generated.branch).toBe("fix/ui-regression"); @@ -579,7 +579,7 @@ it.layer(CodexTextGenerationTestLayer)("CodexTextGeneration", (it) => { const { attachmentsDir } = yield* ServerConfig.ServerConfig; const missingAttachmentId = "thread-missing-attachment"; const missingPath = path.join(attachmentsDir, `${missingAttachmentId}.png`); - yield* fs.remove(missingPath).pipe(Effect.catch(() => Effect.void)); + yield* fs.remove(missingPath).pipe(Effect.ignore); const result = yield* textGeneration .generateBranchName({ diff --git a/apps/server/src/textGeneration/CodexTextGeneration.ts b/apps/server/src/textGeneration/CodexTextGeneration.ts index 4c9ac59d8422..2410ddbf9be7 100644 --- a/apps/server/src/textGeneration/CodexTextGeneration.ts +++ b/apps/server/src/textGeneration/CodexTextGeneration.ts @@ -96,7 +96,7 @@ export const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(func ); const safeUnlink = (filePath: string): Effect.Effect => - fileSystem.remove(filePath).pipe(Effect.catch(() => Effect.void)); + fileSystem.remove(filePath).pipe(Effect.ignore); const encodeJsonForOperation = ( operation: @@ -263,8 +263,9 @@ export const makeCodexTextGeneration = Effect.fn("makeCodexTextGeneration")(func } }); - const cleanup = Effect.all( - [schemaPath, outputPath, ...cleanupPaths].map((filePath) => safeUnlink(filePath)), + const cleanup = Effect.forEach( + [schemaPath, outputPath, ...cleanupPaths], + (filePath) => safeUnlink(filePath), { concurrency: "unbounded", }, diff --git a/apps/server/src/textGeneration/ThreadTitleLinks.ts b/apps/server/src/textGeneration/ThreadTitleLinks.ts index 1b008bb38094..4e22605682f7 100644 --- a/apps/server/src/textGeneration/ThreadTitleLinks.ts +++ b/apps/server/src/textGeneration/ThreadTitleLinks.ts @@ -14,12 +14,9 @@ export const resolveThreadTitleLinks = Effect.fn("resolveThreadTitleLinks")(func const providers = yield* SourceControlProviderRegistry.SourceControlProviderRegistry; const links = new Map>>(); for (const match of input.message.matchAll(/https:\/\/[^\s<>"')\]`]+/g)) { - let url: URL; - try { - url = new URL(match[0].replace(/[.,;!?]+$/, "")); - } catch { - continue; - } + const candidate = match[0].replace(/[.,;!?]+$/, ""); + if (!URL.canParse(candidate)) continue; + const url = new URL(candidate); url.hash = ""; url.search = ""; if (links.has(url.href)) continue; @@ -40,7 +37,7 @@ export const resolveThreadTitleLinks = Effect.fn("resolveThreadTitleLinks")(func ), Effect.map((summary) => `${url}\n${summary}`), Effect.timeout("3 seconds"), - Effect.catch(() => Effect.succeed(`${url}: unavailable`)), + Effect.orElseSucceed(() => `${url}: unavailable`), ), { concurrency: 2 }, ); diff --git a/apps/server/src/usage/UsageService.test.ts b/apps/server/src/usage/UsageService.test.ts index b391e213ab9a..a80a31f48ae6 100644 --- a/apps/server/src/usage/UsageService.test.ts +++ b/apps/server/src/usage/UsageService.test.ts @@ -3,10 +3,11 @@ import * as NodeFSP from "node:fs/promises"; import * as NodeOS from "node:os"; import * as NodePath from "node:path"; +import * as NodeSqlite from "node:sqlite"; import { assert, describe, it } from "@effect/vitest"; import * as NodeServices from "@effect/platform-node/NodeServices"; -import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; +import { HostProcessEnvironment, HostProcessPlatform } from "@t3tools/shared/hostProcess"; import { mergeUsage } from "@t3tools/shared/usageMerge"; import { EnvironmentId, @@ -31,6 +32,7 @@ import * as ServerConfig from "../config.ts"; import * as ServerSettings from "../serverSettings.ts"; import * as UsageService from "./UsageService.ts"; +const encodeUnknownJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown)); const encodeUnknownJsonString = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown)); function claudeLine(id: number, outputTokens: number, model = "claude-fable-5"): string { @@ -82,9 +84,11 @@ const serviceLayers = (input: { /** Defaults to an unparsable document so every scan retries the fetch. */ readonly ratesDocument?: unknown; readonly environment?: NodeJS.ProcessEnv; + readonly platform?: NodeJS.Platform; }) => ServerConfig.layerTest(process.cwd(), { prefix: input.prefix }).pipe( Layer.provideMerge(NodeServices.layer), + Layer.provideMerge(Layer.succeed(HostProcessPlatform, input.platform ?? "linux")), Layer.provideMerge(ServerSettings.layerTest(input.settings)), Layer.provideMerge( Layer.succeed( @@ -101,7 +105,12 @@ const serviceLayers = (input: { ), Layer.provideMerge( Layer.succeed(HostProcessEnvironment, { + HOME: input.home, GROK_HOME: NodePath.join(input.home, "grok"), + OPENCODE_DATA_DIR: NodePath.join(input.home, "opencode"), + ANTIGRAVITY_DATA_DIR: NodePath.join(input.home, "antigravity"), + XDG_CONFIG_HOME: NodePath.join(input.home, "config"), + APPDATA: NodePath.join(input.home, "config"), ...input.environment, }), ), @@ -112,6 +121,193 @@ function totalOutputTokens(summary: { buckets: readonly { totals: { outputTokens } describe("UsageService", () => { + it.live("does not read the macOS Cursor Keychain before account usage is enabled", () => + Effect.gen(function* () { + const { settings, home } = yield* setup; + const service = yield* UsageService.make.pipe( + Effect.provide( + serviceLayers({ + prefix: "usage-service-cursor-keychain-disabled", + home, + settings, + platform: "darwin", + environment: {}, + }), + ), + ); + const summary = yield* service.readSummary(WINDOW); + const cursor = summary.sources.find((source) => source.fingerprint.provider === "cursor"); + assert.strictEqual(cursor?.status, "missing"); + assert.strictEqual(cursor?.action, "enableCursorKeychain"); + }).pipe(Effect.scoped), + ); + + it.live("ignores stale Cursor file logins when the active credential store differs", () => + Effect.gen(function* () { + const { settings, home } = yield* setup; + for (const [index, testCase] of [ + { + platform: "darwin" as const, + environment: { AGENT_CLI_CREDENTIAL_STORE: "memory" }, + authPath: [".cursor", "auth.json"], + }, + { + platform: "linux" as const, + environment: { AGENT_CLI_CREDENTIAL_STORE: "memory" }, + authPath: ["config", "cursor", "auth.json"], + }, + { + platform: "linux" as const, + environment: { CURSOR_API_KEY: "different-account" }, + authPath: ["config", "cursor", "auth.json"], + }, + ].entries()) { + const authPath = NodePath.join(home, ...testCase.authPath); + yield* Effect.promise(async () => { + await NodeFSP.mkdir(NodePath.dirname(authPath), { recursive: true }); + await NodeFSP.writeFile( + authPath, + encodeUnknownJsonString({ accessToken: "stale-token" }), + ); + }); + const service = yield* UsageService.make.pipe( + Effect.provide( + serviceLayers({ + prefix: `usage-service-cursor-store-${index}`, + home, + settings, + platform: testCase.platform, + environment: testCase.environment, + }), + ), + ); + const summary = yield* service.readSummary(WINDOW); + const cursor = summary.sources.find((source) => source.fingerprint.provider === "cursor"); + assert.strictEqual(cursor?.status, "missing"); + assert.include(cursor?.message ?? "", "Cursor CLI login"); + assert.isFalse(summary.buckets.some((bucket) => bucket.provider === "cursor")); + } + }).pipe(Effect.scoped), + ); + + it.live( + "includes OpenCode history but does not substitute desktop usage for an unavailable Cursor account", + () => + Effect.gen(function* () { + const { settings, home } = yield* setup; + const root = NodePath.join(home, "opencode"); + const message = yield* encodeUnknownJson({ + id: "msg_1", + sessionID: "session-1", + role: "assistant", + modelID: "example-model", + time: { created: Date.parse("2026-08-01T10:00:00Z") }, + tokens: { input: 10, output: 5, reasoning: 2, cache: { read: 20, write: 3 } }, + }); + const bubble = yield* encodeUnknownJson({ + type: 2, + createdAt: "2026-08-01T10:00:00Z", + modelInfo: { modelName: "example-model" }, + tokenCount: { inputTokens: 100, outputTokens: 20 }, + }); + yield* Effect.promise(async () => { + const directory = NodePath.join(root, "storage", "message", "session-1"); + await NodeFSP.mkdir(directory, { recursive: true }); + await NodeFSP.writeFile(NodePath.join(directory, "msg_1.json"), message); + const desktop = NodePath.join(home, "config", "Cursor", "User", "globalStorage"); + await NodeFSP.mkdir(desktop, { recursive: true }); + const db = new NodeSqlite.DatabaseSync(NodePath.join(desktop, "state.vscdb")); + try { + db.exec("CREATE TABLE cursorDiskKV (key TEXT, value TEXT)"); + db.prepare("INSERT INTO cursorDiskKV VALUES (?, ?)").run( + "bubbleId:session:assistant", + bubble, + ); + } finally { + db.close(); + } + }); + const service = yield* UsageService.make.pipe( + Effect.provide(serviceLayers({ prefix: "usage-service-opencode", home, settings })), + ); + const summary = yield* service.readSummary(WINDOW); + assert.strictEqual(summary.buckets[0]?.provider, "opencode"); + assert.isFalse(summary.buckets.some((bucket) => bucket.provider === "cursor")); + assert.strictEqual( + summary.sources.find((source) => source.fingerprint.provider === "cursor")?.status, + "missing", + ); + assert.strictEqual( + summary.buckets[0]?.sourcePath, + yield* Effect.promise(() => NodeFSP.realpath(root)), + ); + assert.strictEqual(summary.buckets[0]?.totals.outputTokens, 7); + assert.strictEqual( + summary.sources.find((source) => source.fingerprint.provider === "opencode") + ?.distinctSessions, + 1, + ); + assert.include( + summary.sources.find((source) => source.fingerprint.provider === "cursor")?.message ?? "", + "Cursor account history needs a Cursor CLI login", + ); + }).pipe(Effect.scoped), + ); + + it.live("counts aliased OpenCode and Antigravity directories once", () => + Effect.gen(function* () { + const { settings, home } = yield* setup; + const opencode = NodePath.join(home, "opencode-store"); + const opencodeAlias = NodePath.join(home, "opencode-alias"); + const conversations = NodePath.join(home, "antigravity-conversations"); + const antigravityA = NodePath.join(home, "antigravity-a"); + const antigravityB = NodePath.join(home, "antigravity-b"); + yield* Effect.promise(async () => { + await NodeFSP.mkdir(opencode); + await NodeFSP.symlink(opencode, opencodeAlias, "junction"); + await NodeFSP.mkdir(conversations); + await NodeFSP.mkdir(antigravityA); + await NodeFSP.mkdir(antigravityB); + await NodeFSP.symlink( + conversations, + NodePath.join(antigravityA, "conversations"), + "junction", + ); + await NodeFSP.symlink( + conversations, + NodePath.join(antigravityB, "conversations"), + "junction", + ); + }); + const service = yield* UsageService.make.pipe( + Effect.provide( + serviceLayers({ + prefix: "usage-service-aliased-roots-test", + home, + settings, + environment: { + OPENCODE_DATA_DIR: `${opencode},${opencodeAlias}`, + ANTIGRAVITY_DATA_DIR: `${antigravityA},${antigravityB}`, + }, + }), + ), + ); + const summary = yield* service.readSummary(WINDOW); + const sourcesFor = (provider: "opencode" | "antigravity") => + summary.sources.filter((source) => source.fingerprint.provider === provider); + assert.strictEqual(sourcesFor("opencode").length, 1); + assert.strictEqual(sourcesFor("antigravity").length, 1); + assert.strictEqual( + sourcesFor("opencode")[0]?.fingerprint.resolvedHomePath, + yield* Effect.promise(() => NodeFSP.realpath(opencode)), + ); + assert.strictEqual( + sourcesFor("antigravity")[0]?.fingerprint.resolvedHomePath, + yield* Effect.promise(() => NodeFSP.realpath(conversations)), + ); + }).pipe(Effect.scoped), + ); + it.live("reads configured and disabled accounts once across shared and aliased homes", () => Effect.gen(function* () { const { transcript, settings, home } = yield* setup; @@ -258,9 +454,12 @@ describe("UsageService", () => { const service = yield* UsageService.make; const first = yield* service.readSummary(WINDOW); assert.strictEqual(totalOutputTokens(first), 7); + const configuredProjects = yield* Effect.promise(() => + NodeFSP.realpath(NodePath.join(configured, "projects")), + ); assert.include( first.sources.map((source) => source.fingerprint.resolvedHomePath), - NodePath.join(configured, "projects"), + configuredProjects, ); yield* settingsService.updateSettings({ providerInstances: { @@ -275,9 +474,12 @@ describe("UsageService", () => { }); const second = yield* service.readSummary(WINDOW); assert.strictEqual(totalOutputTokens(second), 8); + const environmentProjects = yield* Effect.promise(() => + NodeFSP.realpath(NodePath.join(environmentHome, "projects")), + ); assert.include( second.sources.map((source) => source.fingerprint.resolvedHomePath), - NodePath.join(environmentHome, "projects"), + environmentProjects, ); }).pipe( Effect.provide( @@ -505,6 +707,9 @@ describe("UsageService", () => { Effect.gen(function* () { const { transcript, settings, home } = yield* setup; yield* Effect.promise(() => NodeFSP.writeFile(transcript, claudeLine(1, 5, "example-model"))); + const transcriptDir = yield* Effect.promise(() => + NodeFSP.realpath(NodePath.join(home, "claude", "projects")), + ); yield* Effect.gen(function* () { const settingsService = yield* ServerSettings.ServerSettingsService; @@ -519,7 +724,7 @@ describe("UsageService", () => { exists: (path) => fileSystem.exists(path).pipe( Effect.tap(() => { - if (path !== NodePath.join(home, "claude", "projects")) return Effect.void; + if (path !== transcriptDir) return Effect.void; homeProbes += 1; return Deferred.succeed( homeProbes === 1 ? firstScanStarted : secondScanStarted, diff --git a/apps/server/src/usage/UsageService.ts b/apps/server/src/usage/UsageService.ts index 72a258bcf322..d4d7e0786ec6 100644 --- a/apps/server/src/usage/UsageService.ts +++ b/apps/server/src/usage/UsageService.ts @@ -1,14 +1,14 @@ /** * UsageService - scans provider transcripts and returns priced usage buckets. * - * The scan reads the provider CLIs' own session files (Claude Code, Codex, and - * Grok Build) rather than T3 Code's orchestration projections, so usage covers - * turns driven outside T3 Code too. This is the approach `ccusage` takes. + * The scan reads native session files and databases, including work driven + * outside T3 Code. Cursor's local records provide only partial coverage. * - * Transcripts are append-only, so parsed records are memoised per file by + * JSONL transcripts are append-only, so parsed records are memoised per file by * `(size, mtime)`. A cold 30-day scan of ~1.4 GB lands around 2-3 seconds; warm * scans only reparse files that changed, and a file that merely grew resumes * from its cached parse position so only the appended bytes are read. + * SQLite readers query live databases each scan so WAL writes remain visible. * * @module UsageService */ @@ -19,6 +19,7 @@ import { CodexSettings, type ProviderInstanceConfig, USAGE_CONTRACT_VERSION, + ProviderInstanceId, type ServerSettings as ServerSettingsValue, type UsageProviderKind, type UsageSource, @@ -27,10 +28,11 @@ import { type UsageSummaryInput, UsageReadError, } from "@t3tools/contracts"; -import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; +import { HostProcessEnvironment, HostProcessPlatform } from "@t3tools/shared/hostProcess"; import * as Cause from "effect/Cause"; import * as Clock from "effect/Clock"; import * as Context from "effect/Context"; +import * as Crypto from "effect/Crypto"; import * as DateTime from "effect/DateTime"; import * as Deferred from "effect/Deferred"; import * as Effect from "effect/Effect"; @@ -46,7 +48,11 @@ import { ServerConfig } from "../config.ts"; import { expandHomePath } from "../pathExpansion.ts"; import * as ServerSettings from "../serverSettings.ts"; import { resolveCodexHomeLayout } from "../provider/Drivers/CodexHomeLayout.ts"; +import { resolveAntigravityInstanceDirectories } from "../provider/antigravityAuthSupport.ts"; import { mergeProviderInstanceEnvironment } from "../provider/ProviderInstanceEnvironment.ts"; +import { readOpenCodeUsage } from "./opencodeUsageReader.ts"; +import { readAntigravityUsage } from "./antigravityUsageReader.ts"; +import { readCursorAccountUsage } from "./cursorUsageReader.ts"; import { UsageAggregator } from "./usageAggregation.ts"; import { createOverrideRateTable, parseRateTable, type RateTable } from "./usagePricing.ts"; import { @@ -144,12 +150,14 @@ export const layerTest = Layer.succeed( ); export const make = Effect.gen(function* () { + const crypto = yield* Crypto.Crypto; const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const config = yield* ServerConfig; const settingsService = yield* ServerSettings.ServerSettingsService; const httpClient = yield* HttpClient.HttpClient; const hostEnvironment = yield* HostProcessEnvironment; + const platform = yield* HostProcessPlatform; const fileCache: ScanCache = new Map(); const sourceCache = new Map(); @@ -225,7 +233,7 @@ export const make = Effect.gen(function* () { yield* encodeRatesCache({ fetchedAtMs: now, document: fetched }).pipe( Effect.flatMap((serialized) => fileSystem.writeFileString(ratesCachePath, serialized)), - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, ); }); @@ -378,7 +386,7 @@ export const make = Effect.gen(function* () { cacheDirty = false; }), // A cache we cannot write is a slower next start, not a failed read. - Effect.catchCause(() => Effect.void), + Effect.ignoreCause, ); }); @@ -452,6 +460,10 @@ export const make = Effect.gen(function* () { readonly provider: UsageProviderKind; readonly dir: string; readonly volumeId: string; + readonly hostId?: string; + readonly status?: UsageSource["status"]; + readonly message?: string; + readonly action?: UsageSource["action"]; /** Parsed records per file, or `null` when the directory does not exist. */ readonly files: | readonly { readonly path: string; readonly records: readonly UsageRecord[] }[] @@ -487,6 +499,171 @@ export const make = Effect.gen(function* () { } scanned.push({ provider, dir, volumeId, files: parsedFiles }); } + + const home = NodeOS.homedir(); + const envRoots = Effect.fnUntraced(function* (key: string, defaults: readonly string[]) { + const roots = hostEnvironment[key] + ?.split(",") + .map((value) => value.trim()) + .filter(Boolean); + const canonical = new Set(); + for (const root of roots?.length ? roots : defaults) { + const resolved = path.resolve(expandHomePath(root)); + canonical.add( + yield* fileSystem.realPath(resolved).pipe(Effect.orElseSucceed(() => resolved)), + ); + } + return [...canonical]; + }); + const dataHome = hostEnvironment["XDG_DATA_HOME"]?.trim(); + for (const dir of yield* envRoots("OPENCODE_DATA_DIR", [ + path.join( + dataHome && path.isAbsolute(dataHome) ? dataHome : path.join(home, ".local", "share"), + "opencode", + ), + ])) { + const result = yield* Effect.promise(() => readOpenCodeUsage(dir, windowStartMs)); + scanned.push({ + provider: "opencode", + dir, + volumeId: yield* Effect.promise(() => readDirectoryVolumeId(dir)), + files: result.missing && !result.error ? null : result.files, + status: result.error ? "partial" : "ok", + ...(result.error ? { message: "Some OpenCode history could not be read." } : {}), + }); + } + const antigravityRoots = yield* envRoots("ANTIGRAVITY_DATA_DIR", [ + ...["antigravity", "antigravity-cli", "antigravity-ide", "antigravity-backup"].map((name) => + path.join(home, ".gemini", name), + ), + path.join(home, ".config", "antigravity"), + ]); + for (const [instanceId, instance] of Object.entries(settings.providerInstances)) { + if (instance.driver === "antigravity") { + const directories = yield* resolveAntigravityInstanceDirectories( + config.stateDir, + ProviderInstanceId.make(instanceId), + ).pipe( + Effect.provideService(Crypto.Crypto, crypto), + Effect.provideService(Path.Path, path), + Effect.mapError( + (cause) => + new UsageReadError({ + reason: "scanFailed", + detail: "Antigravity profile directory could not be resolved.", + cause, + }), + ), + ); + antigravityRoots.push(path.join(directories.profile, "antigravity-acp")); + } + } + const antigravityDirs = new Set(); + for (const root of antigravityRoots) { + const resolvedRoot = yield* fileSystem.realPath(root).pipe(Effect.orElseSucceed(() => root)); + const nested = path.join(resolvedRoot, "conversations"); + const dir = (yield* fileSystem + .exists(nested) + .pipe(Effect.catchCause(() => Effect.succeed(false)))) + ? nested + : resolvedRoot; + antigravityDirs.add(yield* fileSystem.realPath(dir).pipe(Effect.orElseSucceed(() => dir))); + } + const antigravity = yield* Effect.promise(() => + readAntigravityUsage([...antigravityDirs], windowStartMs), + ); + for (const dir of antigravityDirs) { + const exists = yield* fileSystem + .exists(dir) + .pipe(Effect.catchCause(() => Effect.succeed(false))); + const failed = antigravity.errors.some( + (error) => error === dir || error.startsWith(`${dir}${path.sep}`), + ); + scanned.push({ + provider: "antigravity", + dir, + volumeId: yield* Effect.promise(() => readDirectoryVolumeId(dir)), + files: !exists && !failed ? null : antigravity.files.filter((file) => file.root === dir), + status: failed ? "partial" : "ok", + ...(failed ? { message: "Some Antigravity history could not be read." } : {}), + }); + } + const cursorUserHome = + (platform === "win32" ? hostEnvironment["USERPROFILE"] : hostEnvironment["HOME"]) || home; + const configHome = hostEnvironment["XDG_CONFIG_HOME"]?.trim(); + const cursorHome = + platform === "darwin" + ? path.join(cursorUserHome, "Library", "Application Support") + : platform === "win32" + ? hostEnvironment["APPDATA"] || path.join(cursorUserHome, "AppData", "Roaming") + : configHome && path.isAbsolute(configHome) + ? configHome + : path.join(cursorUserHome, ".config"); + const cursorAuthPath = + platform === "darwin" + ? path.join(cursorUserHome, ".cursor", "auth.json") + : path.join(cursorHome, platform === "win32" ? "Cursor" : "cursor", "auth.json"); + const credentialStore = hostEnvironment["AGENT_CLI_CREDENTIAL_STORE"]; + const loginUnavailable = + Boolean(hostEnvironment["CURSOR_AUTH_TOKEN"]?.trim()) || + Boolean(hostEnvironment["CURSOR_API_KEY"]?.trim()) || + credentialStore === "memory"; + if ( + platform === "darwin" && + credentialStore !== "file" && + !loginUnavailable && + !settings.cursorKeychainUsageEnabled + ) { + scanned.push({ + provider: "cursor", + dir: cursorAuthPath, + volumeId: "", + files: null, + message: "Cursor account usage is off on this environment.", + action: "enableCursorKeychain", + }); + return scanned; + } + const cursorUntilMs = yield* Clock.currentTimeMillis; + const account = loginUnavailable + ? { + accountKey: null, + records: [], + missing: true, + error: "Cursor account history needs a Cursor CLI login on this server.", + } + : yield* Effect.promise(() => + readCursorAccountUsage( + platform === "darwin" && credentialStore !== "file" + ? { kind: "keychain" } + : cursorAuthPath, + windowStartMs, + cursorUntilMs, + ), + ); + if (account.accountKey !== null && account.error === null && !account.missing) { + // The same account includes CLI and desktop history from every machine. + // A stable remote fingerprint prevents connected environments counting it twice. + const source = `cursor-account:${account.accountKey}`; + scanned.push({ + provider: "cursor", + dir: source, + hostId: "cursor.com", + volumeId: account.accountKey, + files: [{ path: source, records: account.records }], + status: "ok", + }); + return scanned; + } + scanned.push({ + provider: "cursor", + dir: cursorAuthPath, + volumeId: yield* Effect.promise(() => readDirectoryVolumeId(cursorAuthPath)), + // Never combine a local fallback with another server's account-wide history. + files: null, + message: + account.error ?? "Cursor account history needs a Cursor CLI login saved on this server.", + }); return scanned; }); @@ -561,7 +738,16 @@ export const make = Effect.gen(function* () { const sources: UsageSource[] = []; - for (const { provider, dir, volumeId, files } of scannedDirs) { + for (const { + provider, + dir, + volumeId, + files, + status, + message, + action, + hostId: sourceHostId, + } of scannedDirs) { const retainedFiles = [...(files ?? [])]; const livePaths = new Set(retainedFiles.map((file) => file.path)); // Cleanup may remove transcripts, but the usage we already saved still @@ -607,21 +793,23 @@ export const make = Effect.gen(function* () { } // Only sessions contributing in-window count; the mtime slack can // admit boundary files whose records fall outside the range. - if (aggregator.add(usageRecord) && record.sessionId.length > 0) { + if (aggregator.add(usageRecord, dir) && record.sessionId.length > 0) { sessionIds.add(record.sessionId); } } } sources.push({ - fingerprint: { hostId, provider, resolvedHomePath: dir, volumeId }, + fingerprint: { hostId: sourceHostId ?? hostId, provider, resolvedHomePath: dir, volumeId }, // Clients exclude missing sources, so saved records remain an available source. - status: files === null && scannedFiles === 0 ? "missing" : "ok", + status: files === null && scannedFiles === 0 ? "missing" : (status ?? "ok"), scannedFiles, skippedFiles, malformedRecords: 0, distinctSessions: sessionIds.size, - message: files === null ? "No transcript directory on this environment." : null, + message: + message ?? (files === null ? "No transcript directory on this environment." : null), + ...(action ? { action } : {}), }); } @@ -656,6 +844,7 @@ export const make = Effect.gen(function* () { const scanKey = ( input: UsageSummaryInput, priceOverrides: ServerSettingsValue["usagePriceOverrides"], + cursorKeychainUsageEnabled: boolean, ): string => JSON.stringify([ input.timeZone, @@ -665,11 +854,12 @@ export const make = Effect.gen(function* () { input.sinceTime ?? null, input.untilTime ?? null, priceOverrides, + cursorKeychainUsageEnabled, ]); const readSummary = Effect.fn("UsageService.readSummary")(function* (input: UsageSummaryInput) { const settings = yield* readSettings; - const key = scanKey(input, settings.usagePriceOverrides); + const key = scanKey(input, settings.usagePriceOverrides, settings.cursorKeychainUsageEnabled); const deferred = yield* Effect.uninterruptible( Effect.gen(function* () { const existing = inflightScans.get(key); diff --git a/apps/server/src/usage/antigravityUsageReader.ts b/apps/server/src/usage/antigravityUsageReader.ts new file mode 100644 index 000000000000..54c00b804b7a --- /dev/null +++ b/apps/server/src/usage/antigravityUsageReader.ts @@ -0,0 +1,374 @@ +// node:sqlite reads live conversation databases while Node fs discovers them. +// @effect-diagnostics nodeBuiltinImport:off +import * as NodeFSP from "node:fs/promises"; +import * as NodePath from "node:path"; +import * as NodeSqlite from "node:sqlite"; +import * as NodeTimersPromises from "node:timers/promises"; + +import type { UsageRecord } from "./usageTranscripts.ts"; + +type FieldValue = number | bigint | Uint8Array; +type Fields = Map; + +/** Antigravity stores usage metadata as protobuf, independently of conversation text. */ +function fields(bytes: Uint8Array): Fields { + let offset = 0; + const result: Fields = new Map(); + const varint = () => { + let value = 0n; + for (let shift = 0n; shift < 70n; shift += 7n) { + const byte = bytes[offset++]; + if (byte === undefined || (shift === 63n && byte > 1)) { + throw new Error("Invalid Antigravity protobuf varint"); + } + value |= BigInt(byte & 127) << shift; + if (byte < 128) { + return value > BigInt(Number.MAX_SAFE_INTEGER) ? value : Number(value); + } + } + throw new Error("Invalid Antigravity protobuf varint"); + }; + while (offset < bytes.length) { + const tag = varint(); + if (typeof tag !== "number") throw new Error("Invalid protobuf field"); + const number = Math.floor(tag / 8); + const wire = tag % 8; + if (number === 0) throw new Error("Invalid protobuf field"); + let value: FieldValue; + if (wire === 0) { + value = varint(); + } else if (wire === 1 || wire === 5 || wire === 2) { + const length = wire === 2 ? varint() : wire === 1 ? 8 : 4; + if (typeof length !== "number") throw new Error("Invalid protobuf field length"); + if (length > bytes.length - offset) throw new Error("Truncated protobuf field"); + value = bytes.subarray(offset, offset + length); + offset += length; + if (wire !== 2) continue; + } else { + throw new Error("Unsupported protobuf wire type"); + } + const entries = result.get(number) ?? []; + entries.push(value); + result.set(number, entries); + } + return result; +} + +const numberAt = (value: Fields, key: number) => { + const entry = value.get(key)?.[0]; + return typeof entry === "number" ? entry : 0; +}; +const bytesAt = (value: Fields, key: number) => { + const entry = value.get(key)?.[0]; + return entry instanceof Uint8Array ? entry : undefined; +}; +const nested = (value: Fields, key: number) => { + const bytes = bytesAt(value, key); + return bytes === undefined ? new Map() : fields(bytes); +}; +const textAt = (value: Fields, key: number) => { + const bytes = bytesAt(value, key); + return bytes === undefined ? "" : new TextDecoder("utf-8", { fatal: true }).decode(bytes).trim(); +}; +const timestamp = (value: Fields) => { + const seconds = numberAt(value, 1); + return seconds > 0 ? seconds * 1000 + Math.floor(numberAt(value, 2) / 1_000_000) : null; +}; + +const MODEL_IDS: Record = { + 246: "gemini-2.5-pro", + 312: "gemini-2.5-flash", + 313: "gemini-2.5-flash-thinking", + 329: "gemini-2.5-flash-thinking", + 330: "gemini-2.5-flash-lite", + 281: "claude-sonnet-4", + 282: "claude-sonnet-4", + 290: "claude-opus-4", + 291: "claude-opus-4", + 333: "claude-sonnet-4-5", + 334: "claude-sonnet-4-5", + 340: "claude-haiku-4-5", + 341: "claude-haiku-4-5", + 1026: "claude-opus-4-6", + 1035: "claude-sonnet-4-6", + 1016: "gemini-3.1-pro", + 1036: "gemini-3.1-pro", + 1037: "gemini-3.1-pro", + 1018: "gemini-3-flash-preview", + 1084: "gemini-3-flash-preview", + 1047: "gemini-3-flash-preview", +}; + +function modelName(name: string, id: number): string { + if (name) { + const normalized = name + .toLowerCase() + .replace(/\s*\([^)]*\)\s*$/, "") + .replaceAll(" ", "-"); + if (normalized.startsWith("claude-")) { + return normalized + .replace(/^claude-(4(?:\.\d+)?)-(sonnet|opus|haiku)/, "claude-$2-$1") + .replaceAll(".", "-"); + } + return normalized; + } + return MODEL_IDS[id] ?? (id > 0 ? `antigravity-model-${id}` : ""); +} + +interface Metadata { + model: string; + timestampMs: number | null; + usages: Fields[]; +} + +function metadata(bytes: Uint8Array, step: boolean): Metadata { + const root = fields(bytes); + if (!step && bytesAt(root, 1) === undefined) { + throw new Error("Missing Antigravity generation metadata"); + } + const data = step ? root : nested(root, 1); + const model = step ? nested(data, 24) : data; + const usage = bytesAt(data, step ? 9 : 4); + const usages = usage === undefined ? [] : [fields(usage)]; + for (const retry of data.get(step ? 28 : 17) ?? []) { + if (!(retry instanceof Uint8Array)) throw new Error("Invalid retry metadata"); + const retryUsage = bytesAt(fields(retry), 2); + if (retryUsage !== undefined) usages.push(fields(retryUsage)); + } + return { + model: modelName( + textAt(model, step ? 12 : 19) || textAt(model, step ? 8 : 21), + numberAt(model, step ? 1 : 3), + ), + timestampMs: step + ? (timestamp(nested(data, 8)) ?? timestamp(nested(data, 1))) + : timestamp(nested(nested(data, 9), 4)), + usages, + }; +} + +function blob(value: unknown): Uint8Array { + if (!(value instanceof Uint8Array)) throw new Error("Invalid Antigravity metadata blob"); + return value; +} + +interface UsageCandidate { + record: UsageRecord; + keys: readonly string[]; + timestampQuality: number; +} + +async function readDatabase(path: string, fallbackTimestamp: number): Promise { + const db = new NodeSqlite.DatabaseSync(path, { readOnly: true }); + try { + db.exec("PRAGMA busy_timeout = 100; BEGIN"); + const tables = new Set( + db + .prepare("SELECT name FROM sqlite_master WHERE type = 'table'") + .all() + .map((row) => row.name), + ); + if (!tables.has("gen_metadata") && !tables.has("steps")) { + throw new Error("Missing Antigravity usage tables"); + } + const readMetadata = async (query: string, column: string, step: boolean) => { + const entries: Array<{ idx: number; entry: Metadata }> = []; + for (const row of db.prepare(query).iterate()) { + if (typeof row.idx !== "number") throw new Error("Invalid Antigravity metadata index"); + entries.push({ idx: row.idx, entry: metadata(blob(row[column]), step) }); + if (entries.length % 256 === 0) await NodeTimersPromises.setImmediate(); + } + return entries; + }; + const generations = tables.has("gen_metadata") + ? await readMetadata("SELECT idx, data FROM gen_metadata ORDER BY idx", "data", false) + : []; + let trajectoryTimestamp: number | null = null; + if (tables.has("trajectory_metadata_blob")) { + for (const row of db.prepare("SELECT data FROM trajectory_metadata_blob").iterate()) { + trajectoryTimestamp ??= timestamp(nested(fields(blob(row.data)), 2)); + } + } + const steps = tables.has("steps") + ? await readMetadata( + "SELECT idx, metadata FROM steps WHERE metadata IS NOT NULL ORDER BY idx", + "metadata", + true, + ) + : []; + const sessionId = NodePath.basename(path, ".db"); + const records: UsageCandidate[] = []; + const generationModels = new Map(generations.map(({ idx, entry }) => [idx, entry.model])); + for (const [source, entries] of [ + ["step", steps], + ["generation", generations], + ] as const) { + for (const [index, { idx, entry }] of entries.entries()) { + for (const [usageIndex, usage] of entry.usages.entries()) { + const outputTokens = Math.max( + numberAt(usage, 3), + numberAt(usage, 9) + numberAt(usage, 10), + ); + const totals = { + uncachedInputTokens: numberAt(usage, 2), + cachedInputTokens: numberAt(usage, 5), + cacheCreationTokens: numberAt(usage, 4), + outputTokens, + reasoningTokens: Math.min(outputTokens, numberAt(usage, 9)), + }; + if ( + totals.uncachedInputTokens + + totals.cachedInputTokens + + totals.cacheCreationTokens + + outputTokens === + 0 + ) + continue; + const keys = ([11, 12, 7] as const).flatMap((key) => { + const id = textAt(usage, key); + return id ? [`antigravity:${key}:${id}`] : []; + }); + const record: UsageRecord = { + provider: "antigravity", + sessionId, + timestampMs: entry.timestampMs ?? trajectoryTimestamp ?? fallbackTimestamp, + model: + MODEL_IDS[numberAt(usage, 1)] || + entry.model || + (source === "step" ? generationModels.get(idx) : "") || + modelName("", numberAt(usage, 1)) || + "antigravity-unknown", + totals, + reportedCostUsd: null, + fast: false, + dedupeKey: keys[0] ?? `antigravity:${sessionId}:${source}:${index}:${usageIndex}`, + }; + records.push({ + record, + keys, + timestampQuality: entry.timestampMs !== null ? 2 : trajectoryTimestamp !== null ? 1 : 0, + }); + } + } + } + return records; + } finally { + db.close(); + } +} + +/** Reads and merges aliases across every configured Antigravity store before date filtering. */ +export async function readAntigravityUsage( + conversationsDirectories: string | readonly string[], + sinceMs: number, +) { + const roots = + typeof conversationsDirectories === "string" + ? [conversationsDirectories] + : conversationsDirectories; + const files: Array<{ root: string; path: string; records: UsageRecord[] }> = []; + const errors: string[] = []; + const identities = new Map(); + const groups: Array< + UsageCandidate & { parent: number; size: number; owner: number; fileIndex: number } + > = []; + const find = (index: number): number => { + let root = index; + while (groups[root]!.parent !== root) root = groups[root]!.parent; + while (index !== root) { + const parent = groups[index]!.parent; + groups[index]!.parent = root; + index = parent; + } + return root; + }; + const merge = (left: number, right: number): number => { + let a = find(left); + let b = find(right); + if (a === b) return a; + if (groups[a]!.size < groups[b]!.size) [a, b] = [b, a]; + const target = groups[a]!; + const source = groups[b]!; + const first = target.owner < source.owner ? target : source; + const bestTime = + source.timestampQuality > target.timestampQuality || + (source.timestampQuality === target.timestampQuality && + source.record.timestampMs < target.record.timestampMs) + ? source + : target; + const x = target.record.totals; + const y = source.record.totals; + target.record = { + ...first.record, + model: + first.record.model === "antigravity-unknown" + ? first === target + ? source.record.model + : target.record.model + : first.record.model, + timestampMs: bestTime.record.timestampMs, + totals: { + uncachedInputTokens: Math.max(x.uncachedInputTokens, y.uncachedInputTokens), + cachedInputTokens: Math.max(x.cachedInputTokens, y.cachedInputTokens), + cacheCreationTokens: Math.max(x.cacheCreationTokens, y.cacheCreationTokens), + outputTokens: Math.max(x.outputTokens, y.outputTokens), + reasoningTokens: Math.max(x.reasoningTokens, y.reasoningTokens), + }, + }; + target.timestampQuality = bestTime.timestampQuality; + target.owner = first.owner; + target.fileIndex = first.fileIndex; + target.size += source.size; + source.parent = a; + return a; + }; + const append = (candidate: UsageCandidate, fileIndex: number) => { + const index = groups.length; + groups.push({ ...candidate, parent: index, size: 1, owner: index, fileIndex }); + for (const key of candidate.keys) { + const existing = identities.get(key); + if (existing !== undefined) merge(index, existing); + identities.set(key, index); + } + }; + const visited = new Set(); + const walk = async (directory: string, root: string): Promise => { + let entries; + try { + entries = await NodeFSP.readdir(directory, { withFileTypes: true }); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") errors.push(directory); + return; + } + entries.sort((a, b) => a.name.localeCompare(b.name)); + for (const entry of entries) { + const path = NodePath.join(directory, entry.name); + if (entry.isDirectory()) { + await walk(path, root); + } else if (entry.isFile() && entry.name.endsWith(".db")) { + try { + const canonical = await NodeFSP.realpath(path); + if (visited.has(canonical)) continue; + visited.add(canonical); + const stat = await NodeFSP.stat(path); + const candidates = await readDatabase(path, stat.mtimeMs); + const fileIndex = files.length; + files.push({ root, path, records: [] }); + for (const [index, candidate] of candidates.entries()) { + append(candidate, fileIndex); + if (index % 256 === 255) await NodeTimersPromises.setImmediate(); + } + } catch { + errors.push(path); + } + } + } + }; + for (const root of roots) await walk(root, root); + for (const [index, group] of groups.entries()) { + if (group.parent === index && group.record.timestampMs >= sinceMs) { + files[group.fileIndex]!.records.push(group.record); + } + } + return { files, errors }; +} diff --git a/apps/server/src/usage/cursorUsageReader.ts b/apps/server/src/usage/cursorUsageReader.ts new file mode 100644 index 000000000000..1e07fcc7a825 --- /dev/null +++ b/apps/server/src/usage/cursorUsageReader.ts @@ -0,0 +1,267 @@ +// Node fs reads CLI credentials, and crypto hashes account IDs for deduplication. +// @effect-diagnostics nodeBuiltinImport:off +import * as NodeFSP from "node:fs/promises"; +import * as NodeCrypto from "node:crypto"; +import * as NodeTimersPromises from "node:timers/promises"; + +import type { UsageRecord } from "./usageTranscripts.ts"; +import { readMacCursorAccessToken } from "../provider/cursorCredentialStore.ts"; + +function object(value: unknown): Record { + return typeof value === "object" && value !== null && !Array.isArray(value) + ? (value as Record) + : {}; +} + +function tokens(value: unknown): number { + return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0; +} + +/** + * Maps Cursor's tiered names (`cursor-grok-4.6-high-fast`, + * `claude-fable-5-1-thinking-high`) to the base model's rate-table key. + * Grok resolves through xAI's first-party entry, which has no bare alias. + */ +export function cursorRateModel(model: string): string { + const base = model + .replace(/^cursor-/, "") + .replace(/(?:-thinking)?(?:-(?:none|minimal|low|medium|high|xhigh|max))?(?:-fast)?$/, ""); + return base.startsWith("grok-") ? `xai/${base}` : base; +} + +export interface CursorAccountUsageReadResult { + readonly accountKey: string | null; + readonly records: readonly UsageRecord[]; + readonly missing: boolean; + readonly error: string | null; +} + +const accountHash = (value: string) => NodeCrypto.createHash("sha256").update(value).digest("hex"); + +function canonicalJson(value: unknown): string { + if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`; + if (value !== null && typeof value === "object") { + return `{${Object.entries(value) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`) + .join(",")}}`; + } + return JSON.stringify(value) ?? "null"; +} + +/** Find the longest exact suffix/prefix overlap in linear time. */ +function boundaryOverlap(previous: readonly string[], current: readonly string[]): number { + const sequence = [...current, "", ...previous]; + const lengths = Array.from({ length: sequence.length }, () => 0); + for (let index = 1; index < sequence.length; index++) { + let length = lengths[index - 1]!; + while (length > 0 && sequence[index] !== sequence[length]) length = lengths[length - 1]!; + if (sequence[index] === sequence[length]) length++; + lengths[index] = length; + } + return lengths.at(-1) ?? 0; +} + +/** Dashboard usage includes headless agents and reports fresh input separately from cache reads. */ +export async function readCursorAccountUsage( + credentialSource: string | { readonly kind: "keychain" }, + sinceMs: number, + endDate: number, + request: (url: string, init: RequestInit) => Promise = globalThis.fetch, + keychainToken: () => Promise = readMacCursorAccessToken, +): Promise { + let accessToken: unknown; + try { + accessToken = + typeof credentialSource === "string" + ? object(JSON.parse(await NodeFSP.readFile(credentialSource, "utf8"))).accessToken + : await keychainToken(); + } catch (cause) { + const missing = typeof credentialSource === "string" && object(cause).code === "ENOENT"; + return { + accountKey: null, + records: [], + missing, + error: missing + ? null + : typeof credentialSource === "string" + ? "Cursor credentials could not be read." + : "Cursor Keychain credentials could not be read.", + }; + } + if (typeof accessToken !== "string" || !accessToken) { + return { + accountKey: null, + records: [], + missing: true, + error: + typeof credentialSource === "string" + ? null + : "Cursor account history needs a macOS Keychain CLI login on this server.", + }; + } + let accountKey: string | null = null; + try { + const payload = accessToken.split(".")[1]; + const subject = object( + JSON.parse(Buffer.from(payload ?? "", "base64url").toString("utf8")), + ).sub; + if (typeof subject !== "string" || !subject) throw new Error("Invalid authentication"); + const userId = subject.split("|").at(-1); + if (!userId) throw new Error("Invalid authentication"); + accountKey = accountHash(subject); + if (!Number.isFinite(sinceMs) || !Number.isFinite(endDate) || sinceMs < 0 || sinceMs > endDate) + throw new Error("Invalid date window"); + const deadline = AbortSignal.timeout(60_000); + const records: UsageRecord[] = []; + const occurrences = new Map(); + const pages: unknown[][] = []; + let completed = false; + const pageSize = 1000; + let total: number | undefined; + for (let page = 1; ; page++) { + // A count can include overlapping page boundaries. Allow room to + // reconcile them without imposing a fixed account-size limit. + if (page > (total === undefined ? 1000 : Math.ceil(total / pageSize) * 2 + 1)) { + throw new Error("Account usage page limit exceeded"); + } + const response = await request("https://cursor.com/api/dashboard/get-filtered-usage-events", { + method: "POST", + redirect: "error", + signal: AbortSignal.any([deadline, AbortSignal.timeout(10_000)]), + headers: { + "Content-Type": "application/json", + Origin: "https://cursor.com", + Cookie: `WorkosCursorSessionToken=${encodeURIComponent(`${userId}::${accessToken}`)}`, + }, + body: JSON.stringify({ + page, + pageSize, + startDate: String(sinceMs), + endDate: String(endDate), + }), + }); + if (response.status === 401 || response.status === 403) { + return { + accountKey, + records: [], + missing: false, + error: "Sign in to Cursor again to read account usage.", + }; + } + if (!response.ok) throw new Error("Account usage request failed"); + const parsed: unknown = await response.json(); + if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) { + throw new Error("Invalid account usage page"); + } + const body = object(parsed); + const keys = Object.keys(body); + if ("error" in body || "message" in body || "code" in body) + throw new Error("Account usage error response"); + const count = keys.length === 0 ? 0 : body.totalUsageEventsCount; + const events = + keys.length === 0 || (keys.length === 1 && keys[0] === "totalUsageEventsCount") + ? [] + : body.usageEventsDisplay; + if ( + (count !== undefined && + (typeof count !== "number" || + !Number.isSafeInteger(count) || + count < 0 || + (total !== undefined && count !== total))) || + !Array.isArray(events) || + events.length > pageSize || + (count === undefined && !Array.isArray(body.usageEventsDisplay)) + ) { + throw new Error("Inconsistent account usage page"); + } + if (typeof count === "number") total = count; + pages.push(events); + if (events.length < pageSize) { + completed = true; + break; + } + } + if (!completed) throw new Error("Account usage page limit exceeded"); + const rawCount = pages.reduce((sum, page) => sum + page.length, 0); + if (total !== undefined && rawCount < total) throw new Error("Incomplete account usage pages"); + let removalsRemaining = total === undefined ? 0 : rawCount - total; + let previousKeys: string[] = []; + for (const events of pages) { + const eventKeys = + removalsRemaining > 0 ? events.map((event) => accountHash(canonicalJson(event))) : []; + const removalCount = Math.min(removalsRemaining, boundaryOverlap(previousKeys, eventKeys)); + removalsRemaining -= removalCount; + previousKeys = eventKeys; + for (const raw of events.slice(removalCount)) { + const event = object(raw); + const usage = object(event.tokenUsage); + if (event.tokenUsage === undefined || event.tokenUsage === null) continue; + for (const key of [ + "inputTokens", + "outputTokens", + "cacheReadTokens", + "cacheWriteTokens", + "totalCents", + ]) { + const value = usage[key]; + if ( + value !== undefined && + (typeof value !== "number" || !Number.isFinite(value) || value < 0) + ) { + throw new Error("Invalid account usage totals"); + } + } + const timestampMs = + typeof event.timestamp === "string" && event.timestamp.trim() !== "" + ? Number(event.timestamp) + : event.timestamp; + if ( + typeof timestampMs !== "number" || + !Number.isFinite(timestampMs) || + typeof event.model !== "string" || + !event.model + ) + throw new Error("Invalid account usage event"); + if (timestampMs < sinceMs || timestampMs > endDate) continue; + const totals = { + uncachedInputTokens: tokens(usage.inputTokens), + cachedInputTokens: tokens(usage.cacheReadTokens), + cacheCreationTokens: tokens(usage.cacheWriteTokens), + outputTokens: tokens(usage.outputTokens), + reasoningTokens: 0, + }; + const reportedCostUsd = + typeof usage.totalCents === "number" ? usage.totalCents / 100 : null; + const sessionId = typeof event.conversationId === "string" ? event.conversationId : ""; + // No event ID is provided. Preserve identical billed rows with an occurrence index. + const key = accountHash( + JSON.stringify([timestampMs, event.model, sessionId, totals, reportedCostUsd]), + ); + const occurrence = occurrences.get(key) ?? 0; + occurrences.set(key, occurrence + 1); + records.push({ + provider: "cursor", + timestampMs, + model: event.model, + rateModel: cursorRateModel(event.model), + sessionId, + totals, + reportedCostUsd, + fast: false, + dedupeKey: `cursor-account:${accountKey}:${key}:${occurrence}`, + }); + } + await NodeTimersPromises.setImmediate(); + } + if (removalsRemaining !== 0) throw new Error("Inconsistent account usage boundaries"); + return { accountKey, records, missing: false, error: null }; + } catch { + return { + accountKey, + records: [], + missing: false, + error: "Cursor account usage could not be read.", + }; + } +} diff --git a/apps/server/src/usage/opencodeUsageReader.ts b/apps/server/src/usage/opencodeUsageReader.ts new file mode 100644 index 000000000000..45d6ef33a584 --- /dev/null +++ b/apps/server/src/usage/opencodeUsageReader.ts @@ -0,0 +1,188 @@ +// node:sqlite reads live OpenCode databases; Node fs walks legacy JSON history. +// @effect-diagnostics nodeBuiltinImport:off +import * as NodeFSP from "node:fs/promises"; +import * as NodePath from "node:path"; +import * as NodeSqlite from "node:sqlite"; +import * as NodeTimersPromises from "node:timers/promises"; + +import { totalTokens, type UsageRecord } from "./usageTranscripts.ts"; + +function object(value: unknown): Record { + return typeof value === "object" && value !== null && !Array.isArray(value) + ? (value as Record) + : {}; +} + +function tokens(value: unknown): number { + return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.trunc(value) : 0; +} + +function text(value: unknown): string { + return typeof value === "string" ? value : ""; +} + +/** OpenCode stores uncached input and reasoning separately from input/output. */ +function parseOpenCodeMessage( + source: string, + fallback: { + readonly id?: string; + readonly sessionId?: string; + readonly timestampMs?: number; + } = {}, +): UsageRecord | null { + let parsed: unknown; + try { + parsed = JSON.parse(source); + } catch { + return null; + } + const message = object(parsed); + if (message.role !== undefined && message.role !== "assistant") return null; + const usage = object(message.tokens); + const cache = object(usage.cache); + const modelReference = object(message.model); + const model = text(modelReference.id) || text(modelReference.modelID) || text(message.modelID); + const timestampMs = object(message.time).created ?? fallback.timestampMs; + if (!model || typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null; + const reasoningTokens = tokens(usage.reasoning); + const totals = { + uncachedInputTokens: tokens(usage.input), + cachedInputTokens: tokens(cache.read), + cacheCreationTokens: tokens(cache.write), + outputTokens: tokens(usage.output) + reasoningTokens, + reasoningTokens, + }; + if (totalTokens(totals) === 0) return null; + const id = fallback.id || text(message.id); + const cost = message.cost; + return { + provider: "opencode", + timestampMs, + model, + sessionId: fallback.sessionId || text(message.sessionID), + totals, + // OpenCode writes zero for models without a known rate, including paid + // subscription models. Let the shared price table estimate those records. + reportedCostUsd: typeof cost === "number" && Number.isFinite(cost) && cost > 0 ? cost : null, + fast: false, + dedupeKey: id ? `opencode:${id}` : null, + }; +} + +export interface OpenCodeUsageReadResult { + readonly files: readonly { readonly path: string; readonly records: readonly UsageRecord[] }[]; + readonly missing: boolean; + readonly error: boolean; +} + +/** Reads current SQLite and pre-migration JSON stores without modifying either. */ +export async function readOpenCodeUsage( + root: string, + sinceMs: number, +): Promise { + const files: { path: string; records: UsageRecord[] }[] = []; + const seen = new Set(); + let found = false; + let error = false; + const append = (records: UsageRecord[], record: UsageRecord | null) => { + if (record === null || record.timestampMs < sinceMs) return; + if (record.dedupeKey !== null) { + if (seen.has(record.dedupeKey)) return; + seen.add(record.dedupeKey); + } + records.push(record); + }; + + let databases: string[] = []; + try { + databases = (await NodeFSP.readdir(root, { withFileTypes: true })) + .filter((entry) => entry.isFile() && /^opencode(?:-[a-zA-Z0-9_-]+)?\.db$/.test(entry.name)) + .map((entry) => entry.name) + .sort((a, b) => (a === "opencode.db" ? -1 : b === "opencode.db" ? 1 : a.localeCompare(b))); + } catch (cause) { + if (object(cause).code !== "ENOENT") error = true; + } + for (const name of databases) { + found = true; + const file = { path: NodePath.join(root, name), records: [] as UsageRecord[] }; + files.push(file); + let database: NodeSqlite.DatabaseSync | undefined; + try { + database = new NodeSqlite.DatabaseSync(NodePath.join(root, name), { readOnly: true }); + // A busy live provider should fail this source promptly rather than + // stalling the server while SQLite waits for its writer. + database.exec("PRAGMA busy_timeout = 100"); + const tables = new Set( + database + .prepare("SELECT name FROM sqlite_master WHERE type = 'table'") + .all() + .map((row) => row.name), + ); + if (!tables.has("message") && !tables.has("session_message")) error = true; + for (const table of ["message", "session_message"] as const) { + if (!tables.has(table)) continue; + const columns = new Set( + database + .prepare(`PRAGMA table_info(${table})`) + .all() + .map((row) => row.name), + ); + const timestamp = columns.has("time_created") ? "time_created" : "NULL"; + const predicates = table === "session_message" ? ["type = 'assistant'"] : []; + if (timestamp !== "NULL") predicates.push("time_created >= ?"); + const where = predicates.length > 0 ? ` WHERE ${predicates.join(" AND ")}` : ""; + const statement = database.prepare( + `SELECT id, session_id, data, ${timestamp} AS created FROM ${table}${where}`, + ); + let count = 0; + for (const row of statement.iterate(...(timestamp === "NULL" ? [] : [sinceMs]))) { + append( + file.records, + parseOpenCodeMessage(text(row.data), { + id: text(row.id), + sessionId: text(row.session_id), + ...(typeof row.created === "number" ? { timestampMs: row.created } : {}), + }), + ); + if (++count % 256 === 0) await NodeTimersPromises.setImmediate(); + } + } + } catch { + error = true; + } finally { + database?.close(); + } + } + + // Do not follow symlinks, including cycles. Database records win over their + // old JSON copies when OpenCode has migrated a store in place. + const directories = [NodePath.join(root, "storage", "message")]; + while (directories.length > 0) { + const directory = directories.pop()!; + try { + for (const entry of await NodeFSP.readdir(directory, { withFileTypes: true })) { + const path = NodePath.join(directory, entry.name); + if (entry.isDirectory()) { + directories.push(path); + } else if (entry.isFile() && entry.name.endsWith(".json")) { + found = true; + const id = entry.name.slice(0, -5); + if (seen.has(`opencode:${id}`)) continue; + const file = { path, records: [] as UsageRecord[] }; + files.push(file); + try { + append( + file.records, + parseOpenCodeMessage(await NodeFSP.readFile(path, "utf8"), { id }), + ); + } catch (cause) { + if (object(cause).code !== "ENOENT") error = true; + } + } + } + } catch (cause) { + if (object(cause).code !== "ENOENT") error = true; + } + } + return { files, missing: !found && !error, error }; +} diff --git a/apps/server/src/usage/usageAggregation.test.ts b/apps/server/src/usage/usageAggregation.test.ts index 8da4e920ac06..75435de08ff7 100644 --- a/apps/server/src/usage/usageAggregation.test.ts +++ b/apps/server/src/usage/usageAggregation.test.ts @@ -12,6 +12,7 @@ const rates: RateTable = new Map([ outputCostPerToken: 5e-5, cacheReadCostPerToken: 1e-6, cacheCreationCostPerToken: 1.25e-5, + fastMultiplier: 1, }, ], ]); @@ -31,6 +32,7 @@ function record(overrides: Partial = {}): UsageRecord { reasoningTokens: 0, }, reportedCostUsd: null, + fast: false, dedupeKey: null, ...overrides, }; diff --git a/apps/server/src/usage/usageAggregation.ts b/apps/server/src/usage/usageAggregation.ts index 2ad3893ad4e6..92abfef74108 100644 --- a/apps/server/src/usage/usageAggregation.ts +++ b/apps/server/src/usage/usageAggregation.ts @@ -112,7 +112,7 @@ export class UsageAggregator { * can derive per-window facts (distinct sessions, for one) from the records * that landed rather than everything the mtime prefilter happened to admit. */ - add(record: UsageRecord): boolean { + add(record: UsageRecord, sourcePath?: string): boolean { if (record.dedupeKey !== null) { if (this.#seen.has(record.dedupeKey)) { this.#duplicatesDropped += 1; @@ -146,7 +146,7 @@ export class UsageAggregator { this.#hourlyWindow.sinceTimeMs + Math.floor((record.timestampMs - this.#hourlyWindow.sinceTimeMs) / HOUR_MS) * HOUR_MS, ).toISOString(); - const key = `${day}\u0000${hourStart}\u0000${record.provider}\u0000${record.model}`; + const key = `${day}\u0000${hourStart}\u0000${record.provider}\u0000${record.model}\u0000${sourcePath ?? ""}`; let bucket = this.#buckets.get(key); if (bucket === undefined) { bucket = { @@ -161,20 +161,13 @@ export class UsageAggregator { this.#buckets.set(key, bucket); } - const priced = priceUsage( - this.#options.rates, - record.model, - record.totals, - record.reportedCostUsd, - this.#options.priceOverrides, - ); + const priced = priceUsage(this.#options.rates, record, this.#options.priceOverrides); bucket.totals = addTotals(bucket.totals, record.totals); bucket.costUsd += priced.costUsd; bucket.cacheSavingsUsd += cacheSavingsUsd( this.#options.rates, - record.model, - record.totals, + record, this.#options.priceOverrides, ); bucket.records += 1; @@ -187,12 +180,14 @@ export class UsageAggregator { finish(): AggregateResult { const buckets: UsageBucket[] = []; for (const [key, bucket] of this.#buckets) { - const [day = "", hourStart = "", provider = "", model = ""] = key.split("\u0000"); + const [day = "", hourStart = "", provider = "", model = "", sourcePath = ""] = + key.split("\u0000"); buckets.push({ day: day as UsageDay, ...(hourStart === "" ? {} : { hourStart }), provider: provider as UsageBucket["provider"], model, + ...(sourcePath === "" ? {} : { sourcePath }), totals: bucket.totals, costUsd: bucket.costUsd, cacheSavingsUsd: bucket.cacheSavingsUsd, diff --git a/apps/server/src/usage/usagePricing.test.ts b/apps/server/src/usage/usagePricing.test.ts index 713d860999cb..db4f68c2cb42 100644 --- a/apps/server/src/usage/usagePricing.test.ts +++ b/apps/server/src/usage/usagePricing.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "@effect/vitest"; +import { cursorRateModel } from "./cursorUsageReader.ts"; import { cacheSavingsUsd, createOverrideRateTable, @@ -22,6 +23,12 @@ describe("usage pricing", () => { outputTokens: 1_000_000, reasoningTokens: 500_000, }; + const record = (model: string, reportedCostUsd: number | null = null, fast = false) => ({ + model, + totals, + reportedCostUsd, + fast, + }); it("uses custom token rates ahead of public and provider-reported costs", () => { const table = parseRateTable({ "example-model": rate(1) }); @@ -35,12 +42,32 @@ describe("usage pricing", () => { }); for (const reportedCostUsd of [null, 99]) { - expect(priceUsage(table, "example-model", totals, reportedCostUsd, overrides)).toEqual({ + expect(priceUsage(table, record("example-model", reportedCostUsd), overrides)).toEqual({ costUsd: 13.5, costSource: "modelPriced", }); } - expect(cacheSavingsUsd(table, "example-model", totals, overrides)).toBe(1.5); + expect(cacheSavingsUsd(table, record("example-model"), overrides)).toBe(1.5); + }); + + it("prices Cursor cache savings at the base model rate", () => { + const table = parseRateTable({ + "claude-fable-5-1": rate(10e-6, 1e-6), + "xai/grok-4.7": rate(2e-6, 0.5e-6), + "openrouter/x-ai/grok-4.7": rate(3e-6, 0.5e-6), + }); + const cursorRecord = (model: string) => ({ + ...record(model, 0.25), + rateModel: cursorRateModel(model), + }); + + expect(cacheSavingsUsd(table, cursorRecord("claude-fable-5-1-thinking-high"))).toBeCloseTo(9); + expect(cacheSavingsUsd(table, cursorRecord("cursor-grok-4.7-high-fast"))).toBeCloseTo(1.5); + expect(cacheSavingsUsd(table, cursorRecord("default"))).toBe(0); + expect(priceUsage(table, cursorRecord("grok-4.7-xhigh-fast"))).toEqual({ + costUsd: 0.25, + costSource: "providerReported", + }); }); it("prices unknown models offline and uses input prices for omitted cache rates", () => { @@ -49,11 +76,11 @@ describe("usage pricing", () => { "example-model": { inputCostPerMillionTokens: 2, outputCostPerMillionTokens: 8 }, }); - expect(priceUsage(table, "example-model", totals, null, overrides)).toEqual({ + expect(priceUsage(table, record("example-model"), overrides)).toEqual({ costUsd: 14, costSource: "modelPriced", }); - expect(cacheSavingsUsd(table, "example-model", totals, overrides)).toBe(0); + expect(cacheSavingsUsd(table, record("example-model"), overrides)).toBe(0); }); it("preserves explicit zero rates and matches only the exact trimmed model ID", () => { @@ -64,7 +91,7 @@ describe("usage pricing", () => { outputCostPerMillionTokens: 0, }, }); - expect(priceUsage(table, " vendor/example-model[1m] ", totals, 99, overrides)).toEqual({ + expect(priceUsage(table, record(" vendor/example-model[1m] ", 99), overrides)).toEqual({ costUsd: 0, costSource: "modelPriced", }); @@ -74,14 +101,36 @@ describe("usage pricing", () => { "vendor/Example-model[1m]", "other/example-model[1m]", ]) { - expect(priceUsage(table, model, totals, null, overrides).costSource).toBe("unpriced"); - expect(priceUsage(table, model, totals, 99, overrides)).toEqual({ + expect(priceUsage(table, record(model), overrides).costSource).toBe("unpriced"); + expect(priceUsage(table, record(model, 99), overrides)).toEqual({ costUsd: 99, costSource: "providerReported", }); } }); + it("prices fast-mode requests at the model's published fast multiple", () => { + const table = parseRateTable({ + "claude-opus-5-5": { ...rate(4e-6, 2e-7), provider_specific_entry: { fast: 2, us: 1.1 } }, + "claude-fable-5-1": { ...rate(1e-5, 2.5e-7), provider_specific_entry: { us: 1.1 } }, + }); + const overrides = createOverrideRateTable({ + "claude-opus-5-5": { inputCostPerMillionTokens: 4, outputCostPerMillionTokens: 20 }, + }); + const cost = (model: string, fast: boolean, custom?: typeof overrides) => + priceUsage(table, record(model, null, fast), custom).costUsd; + + expect(cost("claude-opus-5-5", true)).toBeCloseTo(2 * cost("claude-opus-5-5", false)); + expect(cacheSavingsUsd(table, record("claude-opus-5-5", null, true))).toBeCloseTo( + 2 * cacheSavingsUsd(table, record("claude-opus-5-5")), + ); + // No published fast tier, and custom prices, both stay at the standard rate. + expect(cost("claude-fable-5-1", true)).toBe(cost("claude-fable-5-1", false)); + expect(cost("claude-opus-5-5", true, overrides)).toBe( + cost("claude-opus-5-5", false, overrides), + ); + }); + it("keeps the canonical Fable rate separate from DeepInfra in either order", () => { const canonical = ["claude-fable-5", rate(1e-5, 1e-6)] as const; const deepInfra = ["deepinfra/anthropic/claude-fable-5", rate(1e-5)] as const; diff --git a/apps/server/src/usage/usagePricing.ts b/apps/server/src/usage/usagePricing.ts index 6c94be424827..78f6cf2c5cd9 100644 --- a/apps/server/src/usage/usagePricing.ts +++ b/apps/server/src/usage/usagePricing.ts @@ -7,11 +7,9 @@ * * @module usagePricing */ -import type { - UsageCostSource, - UsageModelPriceOverride, - UsageTokenTotals, -} from "@t3tools/contracts"; +import type { UsageCostSource, UsageModelPriceOverride } from "@t3tools/contracts"; + +import type { UsageRecord } from "./usageTranscripts.ts"; /** * The subset of a LiteLLM entry we price against. All values are USD per token. @@ -26,11 +24,19 @@ export interface ModelRate { readonly outputCostPerToken: number; readonly cacheReadCostPerToken: number; readonly cacheCreationCostPerToken: number; + /** + * Multiple of the rates above billed for a fast-mode request, from LiteLLM's + * `provider_specific_entry.fast`. `1` when the model publishes no fast tier. + */ + readonly fastMultiplier: number; } export type RateTable = ReadonlyMap; -/** Custom IDs keep their case, provider prefix, and variant suffix. */ +/** + * Custom IDs keep their case, provider prefix, and variant suffix. Custom rates + * apply as entered, fast-mode requests included. + */ export function createOverrideRateTable( overrides: Readonly>, ): RateTable { @@ -44,6 +50,7 @@ export function createOverrideRateTable( (prices.cacheReadCostPerMillionTokens ?? prices.inputCostPerMillionTokens) / 1_000_000, cacheCreationCostPerToken: (prices.cacheWriteCostPerMillionTokens ?? prices.inputCostPerMillionTokens) / 1_000_000, + fastMultiplier: 1, }, ]), ); @@ -55,12 +62,21 @@ interface LiteLlmEntry { readonly output_cost_per_token?: unknown; readonly cache_read_input_token_cost?: unknown; readonly cache_creation_input_token_cost?: unknown; + readonly provider_specific_entry?: unknown; } function finiteNumber(value: unknown): number | null { return typeof value === "number" && Number.isFinite(value) ? value : null; } +/** Reads `provider_specific_entry.fast`, e.g. `2` for Claude Opus 5.5. */ +function fastMultiplier(entry: LiteLlmEntry): number { + const specific = entry.provider_specific_entry; + if (typeof specific !== "object" || specific === null) return 1; + const fast = finiteNumber((specific as Record)["fast"]); + return fast !== null && fast > 0 ? fast : 1; +} + /** * Projects the LiteLLM document into a rate table. * @@ -92,6 +108,7 @@ export function parseRateTable(document: unknown): RateTable { // input rather than as free. cacheReadCostPerToken: finiteNumber(entry.cache_read_input_token_cost) ?? input, cacheCreationCostPerToken: finiteNumber(entry.cache_creation_input_token_cost) ?? input, + fastMultiplier: fastMultiplier(entry), }); } @@ -119,7 +136,8 @@ function sameRate(a: ModelRate, b: ModelRate): boolean { a.inputCostPerToken === b.inputCostPerToken && a.outputCostPerToken === b.outputCostPerToken && a.cacheReadCostPerToken === b.cacheReadCostPerToken && - a.cacheCreationCostPerToken === b.cacheCreationCostPerToken + a.cacheCreationCostPerToken === b.cacheCreationCostPerToken && + a.fastMultiplier === b.fastMultiplier ); } @@ -165,39 +183,47 @@ export function lookupRate(table: RateTable, model: string): ModelRate | null { return table.get(key) ?? null; } +/** The parts of a transcript record that decide its price. */ +export type PricedRecord = Pick< + UsageRecord, + "model" | "rateModel" | "totals" | "fast" | "reportedCostUsd" +>; + export interface PricedUsage { readonly costUsd: number; readonly costSource: UsageCostSource; } /** - * Prices a bucket's tokens. + * Prices one record's tokens. * * `reasoningTokens` is intentionally not charged separately: it is already * counted inside `outputTokens`. */ export function priceUsage( table: RateTable, - model: string, - totals: UsageTokenTotals, - reportedCostUsd: number | null, + record: PricedRecord, overrides?: RateTable, ): PricedUsage { + const { model, totals, reportedCostUsd } = record; const override = overrides?.get(model.trim()); if (override === undefined && reportedCostUsd !== null && Number.isFinite(reportedCostUsd)) { return { costUsd: reportedCostUsd, costSource: "providerReported" }; } - const rate = override ?? lookupRate(table, model); + const rate = override ?? lookupRate(table, record.rateModel ?? model); if (rate === null) return { costUsd: 0, costSource: "unpriced" }; - const costUsd = + const standardCostUsd = totals.uncachedInputTokens * rate.inputCostPerToken + totals.cachedInputTokens * rate.cacheReadCostPerToken + totals.cacheCreationTokens * rate.cacheCreationCostPerToken + totals.outputTokens * rate.outputCostPerToken; - return { costUsd, costSource: "modelPriced" }; + return { + costUsd: standardCostUsd * (record.fast ? rate.fastMultiplier : 1), + costSource: "modelPriced", + }; } /** @@ -206,11 +232,15 @@ export function priceUsage( */ export function cacheSavingsUsd( table: RateTable, - model: string, - totals: UsageTokenTotals, + record: PricedRecord, overrides?: RateTable, ): number { - const rate = overrides?.get(model.trim()) ?? lookupRate(table, model); + const rate = + overrides?.get(record.model.trim()) ?? lookupRate(table, record.rateModel ?? record.model); if (rate === null) return 0; - return totals.cachedInputTokens * (rate.inputCostPerToken - rate.cacheReadCostPerToken); + return ( + record.totals.cachedInputTokens * + (rate.inputCostPerToken - rate.cacheReadCostPerToken) * + (record.fast ? rate.fastMultiplier : 1) + ); } diff --git a/apps/server/src/usage/usageScanCache.test.ts b/apps/server/src/usage/usageScanCache.test.ts index cc1bdbcc1626..6455b1eb7770 100644 --- a/apps/server/src/usage/usageScanCache.test.ts +++ b/apps/server/src/usage/usageScanCache.test.ts @@ -24,6 +24,7 @@ function record(overrides: Partial = {}): UsageRecord { reasoningTokens: 0, }, reportedCostUsd: null, + fast: false, dedupeKey: "msg_1:", ...overrides, }; @@ -57,7 +58,11 @@ function cacheWith(entries: readonly [string, number, readonly UsageRecord[]][]) describe("scan cache round trip", () => { it("restores records unchanged", () => { const original = cacheWith([ - ["/a.jsonl", 100, [record(), record({ dedupeKey: "msg_2:", model: "claude-opus-5" })]], + [ + "/a.jsonl", + 100, + [record(), record({ dedupeKey: "msg_2:", model: "claude-opus-5-5", fast: true })], + ], ["/b.jsonl", 200, [record({ sessionId: "session-b", reportedCostUsd: 1.5 })]], ]); original.set("/grok.jsonl", { @@ -123,9 +128,20 @@ describe("scan cache round trip", () => { expect(decodeScanCache(JSON.parse(JSON.stringify(poisoned))).has("/a.jsonl")).toBe(false); }); + it("drops an entry whose fast flag is not 0 or 1", () => { + const encoded = encodeScanCache(cacheWith([["/a.jsonl", 100, [record({ fast: true })]]])); + const row = encoded.files["/a.jsonl"]!.r[0]!; + const poisoned = { + ...encoded, + files: { "/a.jsonl": { ...encoded.files["/a.jsonl"]!, r: [[...row.slice(0, 10), true]] } }, + }; + + expect(decodeScanCache(JSON.parse(JSON.stringify(poisoned))).has("/a.jsonl")).toBe(false); + }); + it("rejects a document from the previous cache version", () => { const encoded = encodeScanCache(cacheWith([["/a.jsonl", 100, [record()]]])); - const previous = { ...encoded, version: 2 }; + const previous = { ...encoded, version: 3 }; expect(decodeScanCache(JSON.parse(JSON.stringify(previous))).size).toBe(0); }); diff --git a/apps/server/src/usage/usageScanCache.ts b/apps/server/src/usage/usageScanCache.ts index 71ef25051eb6..79cca5cff8e2 100644 --- a/apps/server/src/usage/usageScanCache.ts +++ b/apps/server/src/usage/usageScanCache.ts @@ -23,7 +23,8 @@ import type { CodexScanState, UsageRecord } from "./usageTranscripts.ts"; // entries would keep serving double-counted records forever. // v3: entries carry the parse position and reducer state so a grown file // re-parses only its appended bytes instead of starting over. -const USAGE_SCAN_CACHE_VERSION = 3 as const; +// v4: records carry Claude fast mode, which v3 rows never captured. +const USAGE_SCAN_CACHE_VERSION = 4 as const; export interface CachedFile { readonly size: number; @@ -58,6 +59,7 @@ type SerializedRecord = readonly [ reasoningTokens: number, dedupeKey: string | null, reportedCostUsd: number | null, + fast: 0 | 1, ]; interface SerializedFile { @@ -109,6 +111,7 @@ export function encodeScanCache(cache: ScanCache): SerializedCache { record.totals.reasoningTokens, record.dedupeKey, record.reportedCostUsd, + record.fast ? 1 : 0, ]; const files: Record = {}; @@ -165,7 +168,7 @@ export function decodeScanCache(document: unknown): ScanCache { ): UsageRecord[] | null => { const records: UsageRecord[] = []; for (const row of rows) { - if (!isRecordArray(row) || row.length < 10) return null; + if (!isRecordArray(row) || row.length < 11) return null; const [ timestampMs, modelIndex, @@ -177,6 +180,7 @@ export function decodeScanCache(document: unknown): ScanCache { reasoning, dedupeKey, reportedCostUsd, + fast, ] = row as SerializedRecord; const model = typeof modelIndex === "number" ? models[modelIndex] : undefined; @@ -188,7 +192,8 @@ export function decodeScanCache(document: unknown): ScanCache { !Number.isFinite(cached) || !Number.isFinite(cacheCreation) || !Number.isFinite(output) || - !Number.isFinite(reasoning) + !Number.isFinite(reasoning) || + (fast !== 0 && fast !== 1) ) { return null; } @@ -206,6 +211,7 @@ export function decodeScanCache(document: unknown): ScanCache { reasoningTokens: reasoning, }, reportedCostUsd: typeof reportedCostUsd === "number" ? reportedCostUsd : null, + fast: fast === 1, dedupeKey: typeof dedupeKey === "string" ? dedupeKey : null, }); } diff --git a/apps/server/src/usage/usageTranscriptReader.test.ts b/apps/server/src/usage/usageTranscriptReader.test.ts index 5feb68b2ff58..6c95a36df19b 100644 --- a/apps/server/src/usage/usageTranscriptReader.test.ts +++ b/apps/server/src/usage/usageTranscriptReader.test.ts @@ -4,13 +4,40 @@ import * as NodeFSP from "node:fs/promises"; import * as NodeOS from "node:os"; import * as NodePath from "node:path"; +import * as NodeSqlite from "node:sqlite"; import { afterEach, assert, beforeEach, describe, it } from "@effect/vitest"; import { readTranscriptRecords } from "./usageTranscriptReader.ts"; +import { readOpenCodeUsage } from "./opencodeUsageReader.ts"; +import { readCursorAccountUsage } from "./cursorUsageReader.ts"; +import { readAntigravityUsage } from "./antigravityUsageReader.ts"; let dir: string; +function protoNumber(field: number, value: number): number[] { + const varint = (number: number) => { + const bytes: number[] = []; + do { + const byte = number % 128; + number = Math.floor(number / 128); + bytes.push(byte + (number > 0 ? 128 : 0)); + } while (number > 0); + return bytes; + }; + return [...varint(field * 8), ...varint(value)]; +} + +function protoBytes(field: number, bytes: readonly number[]): number[] { + const encoded = protoNumber(field, bytes.length); + encoded[0] = encoded[0]! + 2; + return [...encoded, ...bytes]; +} + +function protoText(field: number, value: string): number[] { + return protoBytes(field, [...Buffer.from(value)]); +} + beforeEach(async () => { dir = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "usage-reader-test-")); }); @@ -208,3 +235,530 @@ describe("readTranscriptRecords resume", () => { assert.isNull(await readTranscriptRecords(NodePath.join(dir, "missing.jsonl"), "claude")); }); }); + +describe("SQLite usage readers", () => { + it("reads Cursor account history with the default macOS Keychain login", async () => { + const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo" })).toString("base64url")}.signature`; + let keychainReads = 0; + const result = await readCursorAccountUsage( + { kind: "keychain" }, + 0, + 1781000000000, + async (_url, init) => { + assert.include(new Headers(init.headers).get("cookie") ?? "", "demo%3A%3A"); + return Response.json({ totalUsageEventsCount: 0, usageEventsDisplay: [] }); + }, + async () => { + keychainReads++; + return accessToken; + }, + ); + assert.strictEqual(keychainReads, 1); + assert.isNull(result.error); + assert.isFalse(result.missing); + assert.isNotNull(result.accountKey); + }); + + it("reads paginated Cursor account history including headless calls with separate cache tokens", async () => { + const authPath = NodePath.join(dir, "auth.json"); + const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo", exp: 4102444800 })).toString("base64url")}.signature`; + await NodeFSP.writeFile(authPath, JSON.stringify({ accessToken })); + const pages: number[] = []; + const signals: AbortSignal[] = []; + const request = async (url: string, init: RequestInit) => { + assert.strictEqual(String(url), "https://cursor.com/api/dashboard/get-filtered-usage-events"); + assert.strictEqual(init?.redirect, "error"); + const headers = new Headers(init?.headers); + assert.strictEqual(headers.get("origin"), "https://cursor.com"); + assert.include(headers.get("cookie") ?? "", "WorkosCursorSessionToken=demo%3A%3A"); + const body = JSON.parse(String(init?.body)); + pages.push(body.page); + if (init.signal) signals.push(init.signal); + return Response.json({ + totalUsageEventsCount: 1001, + usageEventsDisplay: Array.from({ length: body.page === 1 ? 1000 : 1 }, (_, index) => ({ + timestamp: String(1780000000000 + ((body.page - 1) * 1000 + index) * 1000), + model: "claude-sonnet-4-5", + conversationId: `conversation-${body.page}`, + isHeadless: body.page === 2, + chargedCents: 0, + tokenUsage: { + inputTokens: 10, + outputTokens: 5, + cacheReadTokens: 30, + cacheWriteTokens: 2, + totalCents: 25, + }, + })), + }); + }; + const result = await readCursorAccountUsage(authPath, 0, 1781000000000, request); + assert.isNull(result.error); + assert.deepStrictEqual(pages, [1, 2]); + assert.lengthOf(signals, 2); + assert.notStrictEqual(signals[0], signals[1]); + assert.strictEqual(result.records.length, 1001); + assert.strictEqual(result.records.at(-1)?.sessionId, "conversation-2"); + assert.deepStrictEqual(result.records[0]?.totals, { + uncachedInputTokens: 10, + cachedInputTokens: 30, + cacheCreationTokens: 2, + outputTokens: 5, + reasoningTokens: 0, + }); + assert.strictEqual(result.records[0]?.reportedCostUsd, 0.25); + assert.isFalse(result.accountKey?.includes("demo") ?? true); + }); + + it("reads Cursor account history beyond 100 pages", async () => { + const authPath = NodePath.join(dir, "auth.json"); + const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo" })).toString("base64url")}.signature`; + await NodeFSP.writeFile(authPath, JSON.stringify({ accessToken })); + const fullPage = Array.from({ length: 1000 }, () => ({ tokenUsage: null })); + let requests = 0; + const result = await readCursorAccountUsage(authPath, 0, 1781000000000, async () => { + requests += 1; + return Response.json({ + totalUsageEventsCount: 100_001, + usageEventsDisplay: requests <= 100 ? fullPage : [{ tokenUsage: null }], + }); + }); + assert.isNull(result.error); + assert.strictEqual(requests, 101); + assert.deepStrictEqual(result.records, []); + }); + + it("accepts confirmed empty Cursor usage but rejects error envelopes", async () => { + const authPath = NodePath.join(dir, "auth.json"); + const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo" })).toString("base64url")}.signature`; + await NodeFSP.writeFile(authPath, JSON.stringify({ accessToken })); + for (const body of [ + {}, + { totalUsageEventsCount: 0 }, + { totalUsageEventsCount: 0, usageEventsDisplay: [] }, + ]) { + const result = await readCursorAccountUsage(authPath, 0, 1781000000000, async () => + Response.json(body), + ); + assert.isNull(result.error); + assert.deepStrictEqual(result.records, []); + assert.isFalse(result.missing); + } + for (const body of [ + { error: "upstream error" }, + { detail: "unknown error envelope" }, + { totalUsageEventsCount: 0, error: "upstream error" }, + null, + [], + "invalid", + 0, + ]) { + const result = await readCursorAccountUsage(authPath, 0, 1781000000000, async () => + Response.json(body), + ); + assert.isNotNull(result.error); + assert.deepStrictEqual(result.records, []); + } + }); + + it("requires a terminal Cursor page after a full page reaches the reported count", async () => { + const authPath = NodePath.join(dir, "auth.json"); + const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo" })).toString("base64url")}.signature`; + await NodeFSP.writeFile(authPath, JSON.stringify({ accessToken })); + let requests = 0; + const result = await readCursorAccountUsage(authPath, 0, 1781000000000, async () => { + requests++; + return Response.json( + requests === 1 + ? { + totalUsageEventsCount: 1000, + usageEventsDisplay: Array.from({ length: 1000 }, (_, index) => ({ + timestamp: String(1780000000000 + index), + model: "gpt-5", + tokenUsage: { inputTokens: 10, outputTokens: 5 }, + })), + } + : { totalUsageEventsCount: 1000 }, + ); + }); + assert.isNull(result.error); + assert.strictEqual(result.records.length, 1000); + assert.strictEqual(requests, 2); + }); + + it("removes only count-proven Cursor boundary copies and preserves identical billed events", async () => { + const authPath = NodePath.join(dir, "auth.json"); + const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo" })).toString("base64url")}.signature`; + await NodeFSP.writeFile(authPath, JSON.stringify({ accessToken })); + const event = (index: number) => ({ + timestamp: String(1780000000000 + index), + model: "gpt-5", + tokenUsage: { inputTokens: 10, outputTokens: 5, totalCents: 1 }, + }); + for (const total of [2000, 2001]) { + let requests = 0; + const result = await readCursorAccountUsage(authPath, 0, 1781000000000, async () => { + requests++; + return Response.json({ + totalUsageEventsCount: total, + usageEventsDisplay: + requests === 1 + ? Array.from({ length: 1000 }, (_, index) => event(index)) + : requests === 2 + ? Array.from({ length: 1000 }, (_, index) => event(999 + index)) + : [event(1999)], + }); + }); + assert.isNull(result.error); + assert.strictEqual(result.records.length, total); + assert.strictEqual(requests, 3); + assert.strictEqual(result.records.at(-1)?.timestampMs, 1780000001999); + assert.strictEqual( + result.records.filter((record) => record.timestampMs === 1780000000999).length, + total === 2000 ? 1 : 2, + ); + assert.strictEqual(new Set(result.records.map((record) => record.dedupeKey)).size, total); + } + let requests = 0; + const inconsistent = await readCursorAccountUsage(authPath, 0, 1781000000000, async () => { + requests++; + return Response.json({ + totalUsageEventsCount: 1001, + usageEventsDisplay: + requests === 1 + ? Array.from({ length: 1000 }, (_, index) => event(index)) + : [event(500), event(1000)], + }); + }); + assert.isNotNull(inconsistent.error); + assert.deepStrictEqual(inconsistent.records, []); + }); + + it("does not present truncated Cursor account pages or authentication failures as complete history", async () => { + const authPath = NodePath.join(dir, "auth.json"); + const accessToken = `header.${Buffer.from(JSON.stringify({ sub: "auth|demo", exp: 4102444800 })).toString("base64url")}.signature`; + await NodeFSP.writeFile(authPath, JSON.stringify({ accessToken })); + const truncated = await readCursorAccountUsage(authPath, 0, 1781000000000, async () => + Response.json({ totalUsageEventsCount: 101, usageEventsDisplay: [] }), + ); + assert.isNotNull(truncated.error); + assert.deepStrictEqual(truncated.records, []); + const denied = await readCursorAccountUsage( + authPath, + 0, + 1781000000000, + async () => new Response(accessToken, { status: 401 }), + ); + assert.isNotNull(denied.error); + assert.isFalse(denied.error?.includes(accessToken) ?? true); + assert.deepStrictEqual(denied.records, []); + let requested = false; + const missing = await readCursorAccountUsage( + NodePath.join(dir, "missing.json"), + 0, + 1781000000000, + async () => { + requested = true; + return Response.json({}); + }, + ); + assert.isTrue(missing.missing); + assert.isFalse(requested); + }); + + it("counts migrated OpenCode messages once and sees subsequent WAL writes", async () => { + const db = new NodeSqlite.DatabaseSync(NodePath.join(dir, "opencode.db")); + try { + db.exec( + "PRAGMA journal_mode = WAL; PRAGMA wal_autocheckpoint = 0; CREATE TABLE message (id TEXT, session_id TEXT, data TEXT)", + ); + const message = { + id: "msg-1", + sessionID: "session-1", + role: "assistant", + modelID: "claude-sonnet-4-5", + time: { created: 1780000000000 }, + cost: 0.25, + tokens: { input: 100, output: 20, reasoning: 5, cache: { read: 30, write: 10 } }, + }; + const insert = db.prepare("INSERT INTO message VALUES (?, ?, ?)"); + insert.run(message.id, message.sessionID, JSON.stringify(message)); + const legacy = NodePath.join(dir, "storage", "message", message.sessionID); + await NodeFSP.mkdir(legacy, { recursive: true }); + await NodeFSP.writeFile(NodePath.join(legacy, "msg-1.json"), JSON.stringify(message)); + const first = await readOpenCodeUsage(dir, 0); + assert.isFalse(first.error); + const records = first.files.flatMap((file) => file.records); + assert.strictEqual(records.length, 1); + assert.deepStrictEqual(records[0]?.totals, { + uncachedInputTokens: 100, + cachedInputTokens: 30, + cacheCreationTokens: 10, + outputTokens: 25, + reasoningTokens: 5, + }); + assert.strictEqual(records[0]?.reportedCostUsd, 0.25); + insert.run( + "msg-2", + message.sessionID, + JSON.stringify({ ...message, id: "msg-2", time: { created: 1780000001000 } }), + ); + const next = await readOpenCodeUsage(dir, 1780000001000); + assert.isFalse(next.error); + assert.deepStrictEqual( + next.files.flatMap((file) => file.records).map((record) => record.dedupeKey), + ["opencode:msg-2"], + ); + assert.isAbove((await NodeFSP.stat(NodePath.join(dir, "opencode.db-wal"))).size, 0); + } finally { + db.close(); + } + }); + + it("deduplicates Antigravity generation and step usage while preserving retry model and token buckets", async () => { + const db = new NodeSqlite.DatabaseSync(NodePath.join(dir, "session-1.db")); + const stamp = protoNumber(1, 1780000000); + const usage = [ + ...protoNumber(2, 100), + ...protoNumber(3, 40), + ...protoNumber(4, 5), + ...protoNumber(5, 20), + ...protoNumber(9, 10), + ...protoText(11, "response-1"), + ]; + const retry = [ + ...protoNumber(1, 1026), + ...protoNumber(2, 12), + ...protoNumber(3, 3), + ...protoText(11, "retry-1"), + ]; + const generation = protoBytes(1, [ + ...protoBytes(4, usage), + ...protoText(19, "Gemini 3 Pro"), + ...protoBytes(9, protoBytes(4, stamp)), + ]); + const step = [ + ...protoBytes(9, usage), + ...protoBytes(8, stamp), + ...protoBytes(28, protoBytes(2, retry)), + ]; + try { + db.exec( + "CREATE TABLE gen_metadata (idx INTEGER, data BLOB); CREATE TABLE steps (idx INTEGER, metadata BLOB)", + ); + db.prepare("INSERT INTO gen_metadata VALUES (?, ?)").run(0, new Uint8Array(generation)); + db.prepare("INSERT INTO steps VALUES (?, ?)").run(0, new Uint8Array(step)); + } finally { + db.close(); + } + const result = await readAntigravityUsage(dir, 0); + assert.deepStrictEqual(result.errors, []); + const records = result.files.flatMap((file) => file.records); + assert.strictEqual(records.length, 2); + const main = records.find((record) => record.model === "gemini-3-pro"); + assert.isDefined(main); + assert.strictEqual(main?.timestampMs, 1780000000000); + assert.strictEqual(main?.sessionId, "session-1"); + assert.deepStrictEqual(main?.totals, { + uncachedInputTokens: 100, + cachedInputTokens: 20, + cacheCreationTokens: 5, + outputTokens: 40, + reasoningTokens: 10, + }); + assert.strictEqual( + records.find((record) => record.model === "claude-opus-4-6")?.totals.uncachedInputTokens, + 12, + ); + assert.deepStrictEqual( + (await readAntigravityUsage(dir, 1780000000001)).files.flatMap((file) => file.records), + [], + ); + }); + + it("uses the matching Antigravity generation model for each model-less step", async () => { + const db = new NodeSqlite.DatabaseSync(NodePath.join(dir, "model-switch.db")); + try { + db.exec( + "CREATE TABLE gen_metadata (idx INTEGER, data BLOB); CREATE TABLE steps (idx INTEGER, metadata BLOB)", + ); + const generation = db.prepare("INSERT INTO gen_metadata VALUES (?, ?)"); + const step = db.prepare("INSERT INTO steps VALUES (?, ?)"); + for (const [idx, name] of ["Gemini 3 Pro", "Claude Opus 4.6"].entries()) { + generation.run(idx, new Uint8Array(protoBytes(1, protoText(19, name)))); + step.run(idx, new Uint8Array(protoBytes(9, protoNumber(2, 10 + idx)))); + } + } finally { + db.close(); + } + const result = await readAntigravityUsage(dir, 0); + assert.deepStrictEqual(result.errors, []); + assert.deepStrictEqual( + result.files.flatMap((file) => file.records).map((record) => record.model), + ["gemini-3-pro", "claude-opus-4-6"], + ); + }); + + it("merges Antigravity aliases that bridge previously separate step records", async () => { + const db = new NodeSqlite.DatabaseSync(NodePath.join(dir, "bridge.db")); + try { + db.exec( + "CREATE TABLE gen_metadata (idx INTEGER, data BLOB); CREATE TABLE steps (idx INTEGER, metadata BLOB)", + ); + const step = db.prepare("INSERT INTO steps VALUES (?, ?)"); + step.run( + 0, + new Uint8Array(protoBytes(9, [...protoNumber(2, 100), ...protoText(11, "response")])), + ); + step.run( + 1, + new Uint8Array(protoBytes(9, [...protoNumber(3, 40), ...protoText(12, "provider")])), + ); + db.prepare("INSERT INTO gen_metadata VALUES (?, ?)").run( + 0, + new Uint8Array( + protoBytes(1, [ + ...protoText(19, "Gemini 3 Pro"), + ...protoBytes(4, [ + ...protoNumber(2, 50), + ...protoNumber(5, 20), + ...protoText(11, "response"), + ...protoText(12, "provider"), + ]), + ]), + ), + ); + } finally { + db.close(); + } + const result = await readAntigravityUsage(dir, 0); + assert.deepStrictEqual(result.errors, []); + const records = result.files.flatMap((file) => file.records); + assert.strictEqual(records.length, 1); + assert.deepStrictEqual(records[0]?.totals, { + uncachedInputTokens: 100, + cachedInputTokens: 20, + cacheCreationTokens: 0, + outputTokens: 40, + reasoningTokens: 0, + }); + }); + + it("merges Antigravity provider and message aliases across configured roots while keeping original ownership", async () => { + const roots = [NodePath.join(dir, "first"), NodePath.join(dir, "second")]; + for (const [index, root] of roots.entries()) { + await NodeFSP.mkdir(root); + const db = new NodeSqlite.DatabaseSync(NodePath.join(root, `session-${index}.db`)); + try { + db.exec("CREATE TABLE steps (idx INTEGER, metadata BLOB)"); + for (const identity of [7, 12]) { + const usage = [ + ...protoNumber(1, 246), + ...protoNumber(2, index === 0 ? 100 : 150), + ...protoText(11, `response-${index}-${identity}`), + ...protoText(identity, `shared-${identity}`), + ]; + db.prepare("INSERT INTO steps VALUES (?, ?)").run( + identity, + new Uint8Array(protoBytes(9, usage)), + ); + } + } finally { + db.close(); + } + } + const result = await readAntigravityUsage(roots, 0); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual(result.files.length, 2); + assert.strictEqual(result.files[0]?.root, roots[0]); + assert.strictEqual(result.files[0]?.records.length, 2); + assert.strictEqual(result.files[1]?.records.length, 0); + assert.deepStrictEqual( + result.files[0]?.records.map((record) => record.totals.uncachedInputTokens), + [150, 150], + ); + assert.isTrue(result.files[0]?.records.every((record) => record.sessionId === "session-0")); + }); + + it("upgrades Antigravity fallback timestamps before applying the date window", async () => { + for (const fallback of ["mtime", "trajectory"]) { + const path = NodePath.join(dir, `${fallback}.db`); + const db = new NodeSqlite.DatabaseSync(path); + try { + db.exec( + "CREATE TABLE gen_metadata (idx INTEGER, data BLOB); CREATE TABLE steps (idx INTEGER, metadata BLOB)", + ); + if (fallback === "trajectory") { + db.exec("CREATE TABLE trajectory_metadata_blob (data BLOB)"); + db.prepare("INSERT INTO trajectory_metadata_blob VALUES (?)").run( + new Uint8Array(protoBytes(2, protoNumber(1, 1780000200))), + ); + } + for (const [index, seconds] of [1780000000, 1780000200].entries()) { + const usage = [...protoNumber(2, 10), ...protoText(11, `${fallback}-${index}`)]; + db.prepare("INSERT INTO steps VALUES (?, ?)").run( + index, + new Uint8Array(protoBytes(9, usage)), + ); + db.prepare("INSERT INTO gen_metadata VALUES (?, ?)").run( + index, + new Uint8Array( + protoBytes(1, [ + ...protoBytes(4, usage), + ...protoBytes(9, protoBytes(4, protoNumber(1, seconds))), + ]), + ), + ); + } + } finally { + db.close(); + } + await NodeFSP.utimes(path, 1780000000, 1780000000); + } + const result = await readAntigravityUsage(dir, 1780000100000); + assert.deepStrictEqual(result.errors, []); + const records = result.files.flatMap((file) => file.records); + assert.strictEqual(records.length, 2); + assert.deepStrictEqual( + records.map((record) => record.timestampMs), + [1780000200000, 1780000200000], + ); + }); + + it("reads Antigravity step-only stores and reports malformed databases", async () => { + const db = new NodeSqlite.DatabaseSync(NodePath.join(dir, "steps.db")); + try { + db.exec("CREATE TABLE steps (idx INTEGER, metadata BLOB)"); + const usage = [...protoNumber(1, 246), ...protoNumber(2, 10), ...protoNumber(3, 5)]; + db.prepare("INSERT INTO steps VALUES (?, ?)").run( + 0, + new Uint8Array([...protoBytes(9, usage), ...protoBytes(8, protoNumber(1, 1780000000))]), + ); + } finally { + db.close(); + } + await NodeFSP.writeFile(NodePath.join(dir, "broken.db"), "not a sqlite database"); + const result = await readAntigravityUsage(dir, 0); + assert.strictEqual(result.errors.length, 1); + assert.strictEqual(result.files.flatMap((file) => file.records)[0]?.model, "gemini-2.5-pro"); + assert.strictEqual(result.files.flatMap((file) => file.records)[0]?.totals.outputTokens, 5); + }); + + it("ignores large values in unused Antigravity protobuf fields", async () => { + const db = new NodeSqlite.DatabaseSync(NodePath.join(dir, "large-varint.db")); + try { + db.exec("CREATE TABLE steps (idx INTEGER, metadata BLOB)"); + const unusedField = [...protoNumber(99, 0).slice(0, -1), ...Array(9).fill(0xff), 0x01]; + const usage = [...protoNumber(1, 246), ...protoNumber(2, 10), ...unusedField]; + db.prepare("INSERT INTO steps VALUES (?, ?)").run(0, new Uint8Array(protoBytes(9, usage))); + } finally { + db.close(); + } + const result = await readAntigravityUsage(dir, 0); + assert.deepStrictEqual(result.errors, []); + assert.strictEqual( + result.files.flatMap((file) => file.records)[0]?.totals.uncachedInputTokens, + 10, + ); + }); +}); diff --git a/apps/server/src/usage/usageTranscripts.test.ts b/apps/server/src/usage/usageTranscripts.test.ts index b09db613ed85..ace3b7d18cf7 100644 --- a/apps/server/src/usage/usageTranscripts.test.ts +++ b/apps/server/src/usage/usageTranscripts.test.ts @@ -15,6 +15,7 @@ function claudeLine(overrides: { contentType: string; model?: string; outputTokens?: number; + speed?: string; }): string { return JSON.stringify({ type: "assistant", @@ -31,6 +32,7 @@ function claudeLine(overrides: { cache_creation_input_tokens: 66818, cache_read_input_tokens: 1000, output_tokens: overrides.outputTokens ?? 286, + ...(overrides.speed === undefined ? {} : { speed: overrides.speed }), }, }, }); @@ -51,6 +53,15 @@ describe("parseClaudeLine", () => { reasoningTokens: 0, }); expect(record?.dedupeKey).toBe("msg_1:"); + expect(record?.fast).toBe(false); + }); + + it("marks fast-mode requests", () => { + const line = (speed: string) => + parseClaudeLine(claudeLine({ messageId: "msg_1", contentType: "text", speed })); + + expect(line("fast")?.fast).toBe(true); + expect(line("standard")?.fast).toBe(false); }); it("gives every content block of one message the same dedupe key", () => { diff --git a/apps/server/src/usage/usageTranscripts.ts b/apps/server/src/usage/usageTranscripts.ts index 5d909379eb10..6e01c2c5a8ed 100644 --- a/apps/server/src/usage/usageTranscripts.ts +++ b/apps/server/src/usage/usageTranscripts.ts @@ -12,9 +12,19 @@ export interface UsageRecord { readonly provider: UsageProviderKind; readonly timestampMs: number; readonly model: string; + /** + * Rate-table key when the provider's display name carries tiers the table + * does not know, such as Cursor's `claude-opus-5-5-high`. Defaults to `model`. + */ + readonly rateModel?: string; readonly sessionId: string; readonly totals: UsageTokenTotals; readonly reportedCostUsd: number | null; + /** + * Whether the request ran in fast mode, which bills at a model-specific + * multiple of the standard rate. Only Claude Code records this. + */ + readonly fast: boolean; /** * Key for cross-file de-duplication, or `null` when the record is inherently * unique and needs no dedup. @@ -145,6 +155,7 @@ export function parseClaudeLine(line: string): UsageRecord | null { reasoningTokens: 0, }, reportedCostUsd: typeof cost === "number" && Number.isFinite(cost) ? cost : null, + fast: usageRecord["speed"] === "fast", dedupeKey, }; } @@ -304,6 +315,7 @@ export function parseCodexLine(line: string, state: CodexScanState): UsageRecord totals, // Codex does not report cost in the rollout. reportedCostUsd: null, + fast: false, // Events surviving the fork-copy suppression above are unique to this // rollout, so they need no global dedup. dedupeKey: null, @@ -433,6 +445,7 @@ export function parseGrokLine(line: string): readonly UsageRecord[] { sessionId, totals: grokTotalsToUsage(topLevel), reportedCostUsd: grokCostTicksToUsd(topLevel.costUsdTicks), + fast: false, // No prompt id means we cannot tell two same-second updates apart. dedupeKey: promptId === null ? null : `${sessionId}:${promptId}:grok`, }, @@ -479,6 +492,7 @@ export function parseGrokLine(line: string): readonly UsageRecord[] { sessionId, totals, reportedCostUsd, + fast: false, dedupeKey: promptId === null ? null : `${sessionId}:${promptId}:${entry.model}`, }); } diff --git a/apps/server/src/vcs/GitVcsDriver.ts b/apps/server/src/vcs/GitVcsDriver.ts index f450eef0dc24..71ebeb1cfdb3 100644 --- a/apps/server/src/vcs/GitVcsDriver.ts +++ b/apps/server/src/vcs/GitVcsDriver.ts @@ -1101,7 +1101,7 @@ export const makeVcsDriverShape = Effect.fn("makeGitVcsDriverShape")(function* ( /^warning: failed to remove \.\/: [^\n]+$/.test(cleaned.stderr.trim()) && (yield* fileSystem.readDirectory(input.cwd).pipe( Effect.map((entries) => entries.length === 0), - Effect.catch(() => Effect.succeed(false)), + Effect.orElseSucceed(() => false), )); if (!emptiedWorkspace) return yield* new VcsProcessExitError({ diff --git a/apps/server/src/vcs/GitVcsDriverCore.test.ts b/apps/server/src/vcs/GitVcsDriverCore.test.ts index 4a5dff870fd2..47ca4e178700 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.test.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.test.ts @@ -1449,6 +1449,44 @@ it.layer(TestLayer)("GitVcsDriver core integration", (it) => { }), ); + for (const splitIndex of [false, true]) { + it.effect(`keeps the preceding second cached in review previews (split: ${splitIndex})`, () => + Effect.gen(function* () { + const cwd = yield* makeTmpDir(); + yield* initRepoWithCommit(cwd); + const driver = yield* GitVcsDriver.GitVcsDriver; + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + yield* writeTextFile(cwd, ".gitattributes", "stable.txt filter=probe\n"); + yield* writeTextFile(cwd, "stable.txt", "unchanged\n"); + yield* writeTextFile( + cwd, + ".git/filter.cjs", + 'require("node:fs").appendFileSync(".git/filter-runs", "read\\n"); process.stdin.pipe(process.stdout);', + ); + yield* git(cwd, ["config", "filter.probe.clean", "node .git/filter.cjs"]); + yield* fs.utimes(path.join(cwd, "stable.txt"), 1_699_999_999.5, 1_699_999_999.5); + yield* git(cwd, ["add", "."]); + yield* git(cwd, ["commit", "-m", "cache stable file"]); + if (splitIndex) yield* git(cwd, ["update-index", "--split-index"]); + const indexPath = path.join(cwd, ".git", "index"); + yield* fs.utimes(indexPath, 1_700_000_000, 1_700_000_000); + const originalIndex = yield* fs.readFile(indexPath); + const originalMtime = (yield* fs.stat(indexPath)).mtime; + yield* writeTextFile(cwd, ".git/filter-runs", ""); + yield* writeTextFile(cwd, "untracked.txt", "new\n"); + const preview = yield* driver.getReviewDiffPreview({ cwd }); + assert.deepStrictEqual( + preview.sources.find((source) => source.kind === "working-tree")!.files, + [{ path: "untracked.txt", previousPath: null, additions: 1, deletions: 0 }], + ); + assert.strictEqual(yield* fs.readFileString(path.join(cwd, ".git/filter-runs")), ""); + assert.deepStrictEqual(yield* fs.readFile(indexPath), originalIndex); + assert.deepStrictEqual((yield* fs.stat(indexPath)).mtime, originalMtime); + }), + ); + } + for (const [timestamp, splitIndex] of [ [1_700_000_000, false], [1_700_000_000.9999, false], diff --git a/apps/server/src/vcs/GitVcsDriverCore.ts b/apps/server/src/vcs/GitVcsDriverCore.ts index 47fd3bd20725..8ec274a46611 100644 --- a/apps/server/src/vcs/GitVcsDriverCore.ts +++ b/apps/server/src/vcs/GitVcsDriverCore.ts @@ -946,16 +946,14 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* return yield* execution.pipe( Effect.timeoutOption(timeoutMs), Effect.flatMap((result) => - Option.match(result, { - onNone: () => - Effect.fail( - new GitCommandError({ - ...gitCommandContext(commandInput), - detail: "Git command timed out.", - }), - ), - onSome: Effect.succeed, - }), + Effect.fromOption( + result, + () => + new GitCommandError({ + ...gitCommandContext(commandInput), + detail: "Git command timed out.", + }), + ), ), ); }, @@ -1004,11 +1002,9 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* : {}), ...(options.progress ? { progress: options.progress } : {}), }).pipe( - Effect.flatMap((result) => { - if (options.allowNonZeroExit || result.exitCode === 0) { - return Effect.succeed(result); - } - return Effect.fail( + Effect.filterOrFail( + (result) => options.allowNonZeroExit || result.exitCode === 0, + (result) => new GitCommandError({ ...gitCommandContext({ operation, cwd, args }), detail: options.fallbackErrorDetail ?? "Git command exited with a non-zero status.", @@ -1016,8 +1012,7 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* stdoutLength: result.stdout.length, stderrLength: result.stderr.length, }), - ); - }), + ), ); const executeGitWithStableDiagnostics = ( @@ -2363,9 +2358,10 @@ export const makeGitVcsDriverCore = Effect.fn("makeGitVcsDriverCore")(function* if (indexExists) { const { mtime } = yield* fileSystem.stat(indexPath); yield* fileSystem.copyFile(indexPath, tempIndexPath); - // A newer copy timestamp hides racily clean edits. Round down before Git reads or rewrites it. + // Node FileSystem.stat truncates bigint timestamps to milliseconds before creating its Date. + // Flooring preserves the source second without making preceding-second files racy. const indexTime = Option.isSome(mtime) - ? Math.max(0, Math.floor((mtime.value.getTime() - 1) / 1000)) + ? Math.max(0, Math.floor(mtime.value.getTime() / 1000)) : 0; yield* fileSystem.utimes(tempIndexPath, indexTime, indexTime); } diff --git a/apps/server/src/vcs/VcsStatusBroadcaster.ts b/apps/server/src/vcs/VcsStatusBroadcaster.ts index 6668cc6a0ff5..ae9abdd0cc4e 100644 --- a/apps/server/src/vcs/VcsStatusBroadcaster.ts +++ b/apps/server/src/vcs/VcsStatusBroadcaster.ts @@ -541,13 +541,13 @@ export const make = Effect.gen(function* () { const demandCwds = yield* Ref.get(demandCwdsRef); const shouldRun = needsInitialRefresh || - (yield* Effect.all( - [...demandCwds.keys()].map((demandCwd) => + (yield* Effect.forEach( + [...demandCwds.keys()], + (demandCwd) => backgroundPolicy.shouldRunScopeWork({ type: "vcs-status", cwd: demandCwd, }), - ), { concurrency: "unbounded" }, )).some(Boolean); if (!shouldRun) { diff --git a/apps/server/src/ws.ts b/apps/server/src/ws.ts index 738736820fac..1e4d56a36a93 100644 --- a/apps/server/src/ws.ts +++ b/apps/server/src/ws.ts @@ -739,27 +739,23 @@ const makeWsRpcLayer = ( case "project.meta-updated": return projectUpsertOrRemove(ProjectId.make(event.aggregateId), event.sequence); case "project.deleted": - return Effect.succeed( - Option.some({ - kind: "project-removed" as const, - sequence: event.sequence, - projectId: ProjectId.make(event.aggregateId), - }), - ); + return Effect.succeedSome({ + kind: "project-removed" as const, + sequence: event.sequence, + projectId: ProjectId.make(event.aggregateId), + }); case "thread.deleted": case "thread.archived": - return Effect.succeed( - Option.some({ - kind: "thread-removed" as const, - sequence: event.sequence, - threadId: ThreadId.make(event.aggregateId), - }), - ); + return Effect.succeedSome({ + kind: "thread-removed" as const, + sequence: event.sequence, + threadId: ThreadId.make(event.aggregateId), + }); case "thread.unarchived": return threadUpsertOrRemove(ThreadId.make(event.aggregateId), event.sequence); default: if (event.aggregateKind !== "thread") { - return Effect.succeed(Option.none()); + return Effect.succeedNone; } return threadUpsertOrRemove(ThreadId.make(event.aggregateId), event.sequence); } @@ -777,7 +773,7 @@ const makeWsRpcLayer = ( ): Effect.Effect, never, never> => read.pipe( Effect.retry({ times: 1 }), - Effect.map(Option.some), + Effect.asSome, Effect.tapError((error) => Effect.logWarning("orchestration shell projection refetch failed", { aggregateKind, diff --git a/apps/web/src/components/AppSidebarLayout.tsx b/apps/web/src/components/AppSidebarLayout.tsx index 97c0bd44eb96..0cf965c4bd22 100644 --- a/apps/web/src/components/AppSidebarLayout.tsx +++ b/apps/web/src/components/AppSidebarLayout.tsx @@ -35,6 +35,7 @@ import LegacyThreadSidebar from "./LegacySidebar"; import ThreadSidebar from "./Sidebar"; import { SettingsSidebarNav } from "./settings/SettingsSidebarNav"; import { SidebarChromeHeader } from "./sidebar/SidebarChrome"; +import { MainAppLocationTracker } from "./sidebar/mainAppLocation"; import { useSidebarStageBackdropVariant } from "./SidebarStageBackdrop"; import { useProjects } from "../state/entities"; import { @@ -321,6 +322,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { {children} + ); diff --git a/apps/web/src/components/BranchToolbar.logic.test.ts b/apps/web/src/components/BranchToolbar.logic.test.ts index ff4bb76bf12a..bd1b11a6cd28 100644 --- a/apps/web/src/components/BranchToolbar.logic.test.ts +++ b/apps/web/src/components/BranchToolbar.logic.test.ts @@ -489,6 +489,24 @@ describe("resolveEffectiveEnvMode", () => { }), ).toBe("worktree"); }); + + it("keeps a server thread in worktree mode while its worktree is still being created", () => { + expect( + resolveEffectiveEnvMode({ + activeWorktreePath: null, + hasServerThread: true, + draftThreadEnvMode: undefined, + preparingWorktree: true, + }), + ).toBe("worktree"); + expect( + resolveEffectiveEnvMode({ + activeWorktreePath: null, + hasServerThread: true, + draftThreadEnvMode: undefined, + }), + ).toBe("local"); + }); }); describe("resolveEnvModeLabel", () => { @@ -510,11 +528,17 @@ describe("resolveCurrentWorkspaceLabel", () => { describe("resolveLockedWorkspaceLabel", () => { it("uses a shorter label for the main repo checkout", () => { - expect(resolveLockedWorkspaceLabel(null)).toBe("Local checkout"); + expect(resolveLockedWorkspaceLabel(null, "local")).toBe("Local checkout"); }); it("uses a shorter label for an attached worktree", () => { - expect(resolveLockedWorkspaceLabel("/repo/.t3/worktrees/feature-a")).toBe("Worktree"); + expect(resolveLockedWorkspaceLabel("/repo/.t3/worktrees/feature-a", "worktree")).toBe( + "Worktree", + ); + }); + + it("describes a worktree that is still being created as a new worktree", () => { + expect(resolveLockedWorkspaceLabel(null, "worktree")).toBe("New worktree"); }); }); diff --git a/apps/web/src/components/BranchToolbar.logic.ts b/apps/web/src/components/BranchToolbar.logic.ts index 85d306f919ae..cbc887954019 100644 --- a/apps/web/src/components/BranchToolbar.logic.ts +++ b/apps/web/src/components/BranchToolbar.logic.ts @@ -104,8 +104,14 @@ export function resolveCurrentWorkspaceLabel(activeWorktreePath: string | null): return activeWorktreePath ? "Current worktree" : resolveEnvModeLabel("local"); } -export function resolveLockedWorkspaceLabel(activeWorktreePath: string | null): string { - return activeWorktreePath ? "Worktree" : "Local checkout"; +// A locked thread in worktree mode with no path is still creating its +// worktree, so it reads as a new worktree rather than the project checkout. +export function resolveLockedWorkspaceLabel( + activeWorktreePath: string | null, + effectiveEnvMode: EnvMode, +): string { + if (activeWorktreePath) return "Worktree"; + return effectiveEnvMode === "worktree" ? resolveEnvModeLabel("worktree") : "Local checkout"; } export interface PreviousWorktreeSeed { @@ -159,15 +165,20 @@ export function resolveEffectiveEnvMode(input: { activeWorktreePath: string | null; hasServerThread: boolean; draftThreadEnvMode: EnvMode | undefined; + /** + * The server is still creating this thread's worktree. The thread exists + * from the start of that setup but gets its worktree path only at the end. + */ + preparingWorktree?: boolean; }): EnvMode { - const { activeWorktreePath, hasServerThread, draftThreadEnvMode } = input; + const { activeWorktreePath, hasServerThread, draftThreadEnvMode, preparingWorktree } = input; if (!hasServerThread) { if (activeWorktreePath) { return "local"; } return draftThreadEnvMode === "worktree" ? "worktree" : "local"; } - return activeWorktreePath ? "worktree" : "local"; + return activeWorktreePath || preparingWorktree ? "worktree" : "local"; } export function resolveDraftEnvModeAfterBranchChange(input: { diff --git a/apps/web/src/components/BranchToolbar.tsx b/apps/web/src/components/BranchToolbar.tsx index 60f529ebd911..eca4fab601d0 100644 --- a/apps/web/src/components/BranchToolbar.tsx +++ b/apps/web/src/components/BranchToolbar.tsx @@ -28,7 +28,6 @@ import { resolveContextStripLabelsCompact, resolveCurrentWorkspaceLabel, resolveEnvModeLabel, - resolveEffectiveEnvMode, resolveLockedWorkspaceLabel, resolvePreviousWorktreeLabel, resolvePreviousWorktreeSeed, @@ -74,7 +73,8 @@ interface BranchToolbarProps { showGitControls: boolean; draftId?: DraftId; onEnvModeChange: (mode: EnvMode) => void; - effectiveEnvModeOverride?: EnvMode; + /** The thread's env mode as ChatView resolves it. */ + envMode: EnvMode; activeThreadBranchOverride?: string | null; onActiveThreadBranchOverrideChange?: (branch: string | null) => void; startFromOrigin: boolean; @@ -141,7 +141,7 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({ const workspaceLabel = forceNewWorktree ? resolveEnvModeLabel("worktree") : envModeLocked - ? resolveLockedWorkspaceLabel(activeWorktreePath) + ? resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode) : effectiveEnvMode === "worktree" ? resolveEnvModeLabel("worktree") : resolveCurrentWorkspaceLabel(activeWorktreePath); @@ -312,6 +312,34 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({ ); }); +const COMPOSER_CONTEXT_MOTION_DURATION_MS = 180; +const COMPOSER_CONTEXT_MOTION_EASING = "cubic-bezier(0.32, 0.72, 0, 1)"; +const COMPOSER_CONTEXT_LABEL_SELECTOR = "[data-composer-label]"; + +/** + * The width a label takes when shown, clipped parts included. + * + * Text keeps its full width when its box clips it, so each text run measures + * whole. A label can hold more than one run (MiddleTruncate splits a branch + * into a head and a tail), so the runs are added. Reading one element's + * scrollWidth drops the tail when the label is hidden or squeezed, and the + * strip then flips between labels and icons on every measure. + * + * A shown label never grows past its motion span's max width, so longer text + * reserves only that much. + */ +function labelTextWidth(label: HTMLElement, range: Range): number { + const walker = document.createTreeWalker(label, NodeFilter.SHOW_TEXT); + let width = 0; + for (let node = walker.nextNode(); node; node = walker.nextNode()) { + range.selectNodeContents(node); + width += range.getBoundingClientRect().width; + } + const motion = label.querySelector("[data-composer-label-motion]"); + const maxWidth = motion ? Number.parseFloat(getComputedStyle(motion).maxWidth) : Number.NaN; + return Number.isFinite(maxWidth) ? Math.min(width, maxWidth) : width; +} + /** * Collapse the strip's labels to icons only when the text no longer fits. * @@ -320,10 +348,6 @@ const MobileRunContextSelector = memo(function MobileRunContextSelector({ * the expanded width without remembered values that could go stale or latch * the strip compact. A small hysteresis keeps the boundary from flapping. */ -const COMPOSER_CONTEXT_MOTION_DURATION_MS = 180; -const COMPOSER_CONTEXT_MOTION_EASING = "cubic-bezier(0.32, 0.72, 0, 1)"; -const COMPOSER_CONTEXT_LABEL_SELECTOR = "[data-composer-label]"; - function useLabelsOverflow(element: HTMLDivElement | null): boolean { const [overflows, setOverflows] = useState(false); const pendingLabelRectsRef = useRef | null>(null); @@ -384,17 +408,11 @@ function useLabelsOverflow(element: HTMLDivElement | null): boolean { needed += width; } needed += stripGap * Math.max(0, groups - 1); + const range = document.createRange(); for (const label of current.querySelectorAll("[data-composer-label]")) { - // The clipping can happen below the marker (SelectValue truncates - // internally), where the outer span's scrollWidth matches its clipped - // box. The text's real width is the largest scrollWidth in the subtree. - let textWidth = label.scrollWidth; - for (const inner of label.querySelectorAll("*")) { - textWidth = Math.max(textWidth, inner.scrollWidth); - } // Subtract the visible width even during an animation. The content // sum already includes it; only the hidden text needs reserving. - needed += Math.max(0, textWidth - label.getBoundingClientRect().width); + needed += Math.max(0, labelTextWidth(label, range) - label.getBoundingClientRect().width); } const nextOverflows = resolveContextStripLabelsCompact({ compact, @@ -493,7 +511,7 @@ export const BranchToolbar = memo(function BranchToolbar({ showGitControls, draftId, onEnvModeChange, - effectiveEnvModeOverride, + envMode, activeThreadBranchOverride, onActiveThreadBranchOverrideChange, startFromOrigin, @@ -528,13 +546,7 @@ export const BranchToolbar = memo(function BranchToolbar({ const activeWorktreePath = forceNewWorktree ? null : (serverThread?.worktreePath ?? draftThread?.worktreePath ?? null); - const effectiveEnvMode = - (forceNewWorktree ? "worktree" : effectiveEnvModeOverride) ?? - resolveEffectiveEnvMode({ - activeWorktreePath, - hasServerThread: serverThread !== null, - draftThreadEnvMode: draftThread?.envMode, - }); + const effectiveEnvMode = forceNewWorktree ? "worktree" : envMode; const envModeLocked = envLocked || (serverThread !== null && activeWorktreePath !== null); // "Previous worktree" hops a draft into the most recently active worktree @@ -702,11 +714,7 @@ export const BranchToolbar = memo(function BranchToolbar({ threadId={threadId} {...(draftId ? { draftId } : {})} envLocked={envLocked} - {...(forceNewWorktree - ? { effectiveEnvModeOverride: "worktree" } - : effectiveEnvModeOverride - ? { effectiveEnvModeOverride } - : {})} + effectiveEnvModeOverride={effectiveEnvMode} {...(activeThreadBranchOverride !== undefined ? { activeThreadBranchOverride } : {})} {...(onActiveThreadBranchOverrideChange ? { onActiveThreadBranchOverrideChange } : {})} startFromOrigin={startFromOrigin} diff --git a/apps/web/src/components/BranchToolbarEnvModeSelector.tsx b/apps/web/src/components/BranchToolbarEnvModeSelector.tsx index 1d72ddf87e18..40692063e979 100644 --- a/apps/web/src/components/BranchToolbarEnvModeSelector.tsx +++ b/apps/web/src/components/BranchToolbarEnvModeSelector.tsx @@ -64,10 +64,10 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-1.75 font-normal text-muted-foreground/70 text-xs sm:h-6" data-composer-context-control > - {forceNewWorktree ? ( - - ) : activeWorktreePath ? ( + {activeWorktreePath ? ( + ) : effectiveEnvMode === "worktree" ? ( + ) : ( )} @@ -79,16 +79,14 @@ export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSe data-composer-label-motion className="block w-full min-w-0 max-w-[240px] truncate transition-opacity duration-180 ease-drawer group-data-[compact]/composer-context:opacity-0 motion-reduce:transition-none" > - {forceNewWorktree - ? resolveEnvModeLabel("worktree") - : resolveLockedWorkspaceLabel(activeWorktreePath)} + {resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)} {forceNewWorktree ? "Each model starts in its own worktree." - : resolveLockedWorkspaceLabel(activeWorktreePath)} + : resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)} ); diff --git a/apps/web/src/components/ChatMarkdown.test.tsx b/apps/web/src/components/ChatMarkdown.test.tsx index d8a5f651ff83..cb25e6938fa7 100644 --- a/apps/web/src/components/ChatMarkdown.test.tsx +++ b/apps/web/src/components/ChatMarkdown.test.tsx @@ -179,6 +179,75 @@ describe("ChatMarkdown favicon privacy", () => { }); describe("ChatMarkdown streaming", () => { + it("runs only a complete single-line shell block after a click", async () => { + vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true); + const onRunShellCommand = vi.fn(); + let renderer: ReactTestRenderer | undefined; + const message = (text: string, isStreaming = false) => ( + + ); + try { + await act(async () => { + renderer = create(message("```bash\necho hello\n```", true)); + }); + const mounted = renderer!; + expect( + mounted.root + .findAllByType(Button) + .some((button) => button.props["aria-label"] === "Run in terminal"), + ).toBe(false); + + await act(async () => { + mounted.update(message("```bash\necho hello\n```")); + }); + await act(async () => { + codeButton(mounted, "Run in terminal").onClick?.({} as never); + }); + expect(onRunShellCommand).toHaveBeenCalledExactlyOnceWith("echo hello"); + + for (const text of [ + "~~~bash\necho tilde\n~~~", + "> ```bash\n> echo quote\n> ```", + "````bash\necho four\n````", + ]) { + await act(async () => { + mounted.update(message(text)); + }); + expect(codeButton(mounted, "Run in terminal")).toBeDefined(); + } + + for (const text of [ + "```bash\necho one\necho two\n```", + "```typescript\necho hello\n```", + "```bash\n\n```", + "```bash\necho hello\n\n```", + "```bash\necho hello\\\n```", + "```bash\necho safe \u202e#\n```", + "```bash\necho incomplete", + "~~~bash\necho incomplete", + "````bash\necho incomplete\n```", + '
echo html
', + ]) { + await act(async () => { + mounted.update(message(text)); + }); + expect( + mounted.root + .findAllByType(Button) + .some((button) => button.props["aria-label"] === "Run in terminal"), + ).toBe(false); + } + } finally { + await act(async () => renderer?.unmount()); + vi.unstubAllGlobals(); + } + }); + it("does not retokenize completed lines when streaming finishes", async () => { const highlighter = await getSyntaxHighlighterPromise("typescript"); const highlight = vi.spyOn(highlighter, "codeToHast"); diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx index 5b6cdfde49d6..4a1f616aed3b 100644 --- a/apps/web/src/components/ChatMarkdown.tsx +++ b/apps/web/src/components/ChatMarkdown.tsx @@ -20,6 +20,7 @@ import { MessageSquareWarningIcon, Minimize2Icon, OctagonAlertIcon, + PlayIcon, PresentationIcon, SparklesIcon, TriangleAlertIcon, @@ -215,6 +216,7 @@ interface ChatMarkdownProps { parseRawHtml?: boolean; /** Append a prompt that invokes a newly created artifact-template skill. */ onUseArtifactTemplate?: ((template: CodexArtifactTemplate) => void) | undefined; + onRunShellCommand?: ((command: string) => void) | undefined; /** Directory that anchors relative links and images; defaults to `cwd`. Set to the file's own directory when rendering a markdown file. */ imageBaseDir?: string | undefined; @@ -585,6 +587,23 @@ function extractPreCodeMeta(node: unknown): string | undefined { return typeof meta === "string" && meta.trim().length > 0 ? meta.trim() : undefined; } +function isClosedCodeFence(node: ReactMarkdownExtraProps["node"], text: string): boolean { + const start = node?.position?.start.offset; + const end = node?.position?.end.offset; + if (start === undefined || end === undefined) return false; + const source = text.slice(start, end); + const opening = /^(?:`{3,}|~{3,})/.exec(source)?.[0]; + // One class for the blockquote prefix: nested quantifiers here backtrack + // exponentially on code lines that start with many `> ` markers. + const closing = /(?:^|\n)[ \t>]*(`{3,}|~{3,})[ \t\r]*$/.exec(source)?.[1]; + return ( + opening !== undefined && + closing !== undefined && + opening[0] === closing[0] && + closing.length >= opening.length + ); +} + type MarkdownAstNode = { type?: string; meta?: unknown; @@ -920,12 +939,16 @@ function MarkdownCodeBlock({ language, fenceTitle, theme, + onRunShellCommand, + isStreaming, children, }: { code: string; language: string; fenceTitle: string | null; theme: "light" | "dark"; + onRunShellCommand?: ((command: string) => void) | undefined; + isStreaming: boolean; children: ReactNode; }) { const [copied, setCopied] = useState(false); @@ -933,6 +956,17 @@ function MarkdownCodeBlock({ const copiedTimerRef = useRef | null>(null); const wrapLabel = wrapped ? "Disable line wrap" : "Wrap lines"; const copyLabel = copied ? "Copied" : "Copy code"; + const command = code.trim(); + const canRun = + onRunShellCommand !== undefined && + !isStreaming && + /^(?:sh|bash|zsh|fish|shell|powershell|pwsh)$/.test(language) && + code.endsWith("\n") && + command.length > 0 && + !command.endsWith("\\") && + // Control and invisible format characters (bidi overrides, zero-width) can + // make the rendered command differ from what the terminal would receive. + !/[\p{Cc}\p{Cf}]/u.test(code.slice(0, -1)); const handleCopy = useCallback(() => { if (typeof navigator === "undefined" || navigator.clipboard == null) { @@ -1004,6 +1038,24 @@ function MarkdownCodeBlock({ {wrapLabel} + {canRun ? ( + + onRunShellCommand(command)} + aria-label="Run in terminal" + /> + } + > + + + Run in terminal + + ) : null} {children}; }, pre: function MarkdownPre({ node, children, ...props }) { - const { resolvedTheme, diffThemeName, isStreaming } = use(ChatMarkdownRendererContext); + const { resolvedTheme, diffThemeName, isStreaming, onRunShellCommand, text } = use( + ChatMarkdownRendererContext, + ); const codeBlock = extractCodeBlock(children); if (!codeBlock) { return
{children}
; @@ -3225,6 +3282,12 @@ const CHAT_MARKDOWN_COMPONENTS = { language={language} fenceTitle={fenceTitle} theme={resolvedTheme} + onRunShellCommand={ + onRunShellCommand && !isStreaming && isClosedCodeFence(node, text) + ? onRunShellCommand + : undefined + } + isStreaming={isStreaming} > { + runProjectScriptRef.current = runProjectScript; + }, [runProjectScript]); + const runShellCommand = useCallback((command: string) => { + void runProjectScriptRef.current( + { + id: "chat-code-block", + name: "Chat code block", + command, + icon: "play", + runOnWorktreeCreate: false, + }, + { rememberAsLastInvoked: false }, + ); + }, []); + const supportsProjectSettingsOverrides = environmentById.get(environmentId)?.serverConfig?.environment.capabilities .projectSettingsOverrides === true; @@ -5833,6 +5852,7 @@ export default function ChatView(props: ChatViewProps) { activeWorktreePath, hasServerThread: isServerThread, draftThreadEnvMode: isLocalDraftThread ? draftThread?.envMode : undefined, + preparingWorktree: isPreparingWorktree, }); const canOverrideServerThreadEnvMode = Boolean( isServerThread && @@ -6296,11 +6316,15 @@ export default function ChatView(props: ChatViewProps) { return null; } const working = activeBackgroundLiveness === "working"; + const liveCount = agentPanelModel.liveCount; const copy = buildBackgroundWorkBannerCopy({ liveness: working ? "working" : "monitoring", - liveAgentCount: agentPanelModel.liveCount, + liveAgentCount: liveCount, tasks: liveBackgroundTasks, }); + // Hidden once the Agents surface is on screen; the link would point at nothing. + const showViewAgents = + liveCount > 0 && !(rightPanelOpen && activeRightPanelSurface?.kind === "agents"); return { id: `background-liveness:${activeThread.id}`, variant: "default", @@ -6322,6 +6346,11 @@ export default function ChatView(props: ChatViewProps) { {liveBackgroundTasks.length > 0 ? ( ) : null} + {showViewAgents ? ( + + ) : null} , + shortcutCommand: "usage.open", run: async () => { await navigate({ to: "/usage" }); }, diff --git a/apps/web/src/components/ComposerPromptEditorTiptap.tsx b/apps/web/src/components/ComposerPromptEditorTiptap.tsx index 74e8736f04e7..2a5fe2ba0588 100644 --- a/apps/web/src/components/ComposerPromptEditorTiptap.tsx +++ b/apps/web/src/components/ComposerPromptEditorTiptap.tsx @@ -29,6 +29,7 @@ import { useMemo, useRef, useState, + type KeyboardEvent as ReactKeyboardEvent, } from "react"; import { EditorContent, useEditor } from "@tiptap/react"; @@ -371,6 +372,16 @@ function ComposerCitationNodeView({ node, editor, getPos }: NodeViewProps) { .run(); }, [editor, nodePos]); + // Put the caret right after the chip so Enter sends and typing continues the prompt. + const onRestoreFocus = useCallback(() => { + if (!editor.isEditable) return; + const pos = nodePos(); + if (pos === null) return; + const current = editor.state.doc.nodeAt(pos); + if (!current || current.type.name !== "composer-citation") return; + editor.commands.focus(pos + current.nodeSize); + }, [editor, nodePos]); + return ( ) => { + // Tab from the comment button returns to the caret after the chip. + if ( + !editor.isEditable || + event.key !== "Tab" || + event.shiftKey || + event.altKey || + event.metaKey || + event.ctrlKey || + !(event.target instanceof HTMLElement) || + event.target.dataset.citationCommentTrigger === undefined + ) { + return; + } + event.preventDefault(); + onRestoreFocus(); + }} > @@ -856,6 +885,32 @@ function ComposerPromptEditorTiptapInner(props: ComposerPromptEditorProps) { return true; } } + // Shift+Tab from just after a citation reaches its comment button, which + // native tab order skips because the chip lives inside the editor. + if ( + event.key === "Tab" && + event.shiftKey && + !event.altKey && + !event.metaKey && + !event.ctrlKey && + view.state.selection.empty + ) { + const { $from } = view.state.selection; + const citation = $from.nodeBefore; + if (citation?.type.name === "composer-citation") { + const chip = view.nodeDOM($from.pos - citation.nodeSize); + const commentButton = + chip instanceof HTMLElement + ? chip.querySelector("[data-citation-comment-trigger]") + : null; + if (commentButton) { + event.preventDefault(); + event.stopPropagation(); + commentButton.focus(); + return true; + } + } + } if (event.key === "Enter" && (event.isComposing || event.keyCode === 229)) { event.stopPropagation(); return true; diff --git a/apps/web/src/components/GitActionsControl.tsx b/apps/web/src/components/GitActionsControl.tsx index 5c9d37d8b4e5..2c26f1e69b1d 100644 --- a/apps/web/src/components/GitActionsControl.tsx +++ b/apps/web/src/components/GitActionsControl.tsx @@ -636,7 +636,7 @@ function PublishRepositoryDialog(props: PublishRepositoryDialogProps) { return (
diff --git a/apps/web/src/components/Icons.tsx b/apps/web/src/components/Icons.tsx index 2a4463c11a06..df7ea7b1c95e 100644 --- a/apps/web/src/components/Icons.tsx +++ b/apps/web/src/components/Icons.tsx @@ -547,10 +547,14 @@ export const OpenAI: Icon = ({ className, ...props }) => ( - + ); diff --git a/apps/web/src/components/LegacySidebar.tsx b/apps/web/src/components/LegacySidebar.tsx index 217016d8794f..784569d22953 100644 --- a/apps/web/src/components/LegacySidebar.tsx +++ b/apps/web/src/components/LegacySidebar.tsx @@ -18,6 +18,7 @@ import { prStatusIndicator, PrStatusTooltipContent, terminalStatusFromRunningIds, + synchronizeTerminalPulse, ThreadStatusLabel, ThreadWorktreeIndicator, useLinkedThreadPullRequest, @@ -844,7 +845,8 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP } > {terminalStatus.label} diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 6d97d9033d95..0bea385ccce0 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -207,6 +207,7 @@ import { prStatusIndicator, resolveThreadPullRequestBadge, terminalStatusFromRunningIds, + synchronizeTerminalPulse, type TerminalStatusIndicator, useLinkedThreadPullRequest, } from "./ThreadStatusIndicators"; @@ -1407,6 +1408,11 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { : shouldRecede ? "text-sidebar-muted-foreground/75 hover:bg-sidebar-row-hover hover:text-sidebar-foreground" : "bg-transparent text-sidebar-foreground hover:bg-sidebar-row-hover", + // Background work fades as a whole row, status label included, so it + // takes less attention than rows that need a human (input, approval). + shouldRecede && + (status === "working" || status === "monitoring") && + "opacity-70 transition-opacity hover:opacity-100 focus-within:opacity-100 motion-reduce:transition-none", isFileDragOver && "ring-1 ring-inset ring-primary/70", // The hover tint must not clobber an active/selected row's own surface. isFileDragOver && !props.isActive && !isSelected && "bg-sidebar-row-hover", @@ -1520,7 +1526,10 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { data-testid={`sidebar-terminal-status-${thread.id}`} className={cn("inline-flex shrink-0 items-center justify-center", terminalStatus.colorClass)} > - + ) : null; // Same pen the new-thread draft rows lead with, so both kinds of unsent @@ -1973,7 +1982,7 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { showBadge={showInstanceBadge} // Glyph dims, badge stays saturated; offset matches the composer trigger. iconClassName="size-3.5 opacity-60" - badgeClassName="right-[-0.1875rem] bottom-[-0.1875rem] h-3 min-w-3 px-0.5 text-3xs" + badgeClassName="right-[-0.1875rem] bottom-[-0.1875rem] h-3 min-w-3 px-0.5 text-5xs" /> ) : null} @@ -2171,6 +2180,7 @@ export default function Sidebar() { confirmAndUnpinThread, reorderPinnedThread, reorderActiveThread, + setThreadAutoSettle, archiveThread, deleteThread, } = useThreadActions(); @@ -4081,6 +4091,9 @@ export default function Sidebar() { serverConfigs.get(thread.environmentId)?.environment.capabilities.threadSnooze === true; const supportsPinning = serverConfigs.get(thread.environmentId)?.environment.capabilities.threadPinning === true; + const supportsAutoSettleOptOut = + serverConfigs.get(thread.environmentId)?.environment.capabilities + .threadAutoSettleOptOut === true; const supportsTitleRegeneration = serverConfigs.get(thread.environmentId)?.environment.capabilities .threadTitleRegeneration === true; @@ -4110,6 +4123,7 @@ export default function Sidebar() { : null, isPinned, isSettled, + autoSettleEnabled: thread.autoSettleDisabledAt == null, isSnoozed, canSnoozeNow: canSnooze(thread, { now: new Date().toISOString() }), isRegeneratingTitle, @@ -4117,6 +4131,7 @@ export default function Sidebar() { thread.session?.status === "running" && thread.session.activeTurnId != null, supports: { settlement: supportsSettlement, + autoSettleOptOut: supportsAutoSettleOptOut, snooze: supportsSnooze, pinning: supportsPinning, titleRegeneration: supportsTitleRegeneration, @@ -4188,6 +4203,24 @@ export default function Sidebar() { case "unpin": attemptUnpin(threadRef); return; + case "auto-settle:enabled": + case "auto-settle:disabled": { + const result = await setThreadAutoSettle( + threadRef, + clicked.value === "auto-settle:enabled", + ); + if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { + const error = squashAtomCommandFailure(result); + toastManager.add( + stackedThreadToast({ + type: "error", + title: "Failed to update auto-settle", + description: error instanceof Error ? error.message : "An error occurred.", + }), + ); + } + return; + } case "rename": startThreadRename(threadRef, thread.title); return; @@ -4314,6 +4347,7 @@ export default function Sidebar() { projectByKey, serverConfigs, setProjectScopeKey, + setThreadAutoSettle, startThreadRename, updateThreadMetadata, timestampFormat, diff --git a/apps/web/src/components/ThreadStatusIndicators.test.ts b/apps/web/src/components/ThreadStatusIndicators.test.ts index 60b17b19aa6b..5406cba08c0d 100644 --- a/apps/web/src/components/ThreadStatusIndicators.test.ts +++ b/apps/web/src/components/ThreadStatusIndicators.test.ts @@ -1,14 +1,31 @@ import { ProjectId, type PullRequestSummary, type VcsStatusResult } from "@t3tools/contracts"; import { describe, expect, it } from "@effect/vitest"; +import type { AnimationEvent } from "react"; import { ChangeRequestStatusIcon, prStatusIndicator, resolveThreadPullRequestBadgePresentation, + synchronizeTerminalPulse, } from "./ThreadStatusIndicators"; import { newestPullRequestSummary } from "../state/pullRequests"; import { PullRequestGlyph } from "~/components/pullRequest/pullRequestIcons"; +describe("synchronizeTerminalPulse", () => { + it("pins only the status pulse to the document clock", () => { + const pulse = { animationName: "status-pulse", startTime: 975 } as CSSAnimation; + const otherCss = { animationName: "other-animation", startTime: 125 } as CSSAnimation; + const otherAnimation = { startTime: 250 } as Animation; + + synchronizeTerminalPulse({ + animationName: "status-pulse", + currentTarget: { getAnimations: () => [pulse, otherCss, otherAnimation] }, + } as AnimationEvent); + + expect([pulse.startTime, otherCss.startTime, otherAnimation.startTime]).toEqual([0, 125, 250]); + }); +}); + describe("ChangeRequestStatusIcon", () => { it.each([ ["open", "open", false, PullRequestGlyph.pullRequest], diff --git a/apps/web/src/components/ThreadStatusIndicators.tsx b/apps/web/src/components/ThreadStatusIndicators.tsx index b8d8a00ae8bb..33e5c491229b 100644 --- a/apps/web/src/components/ThreadStatusIndicators.tsx +++ b/apps/web/src/components/ThreadStatusIndicators.tsx @@ -16,7 +16,7 @@ import { } from "@t3tools/shared/threadPullRequests"; import { FolderGit2Icon, TerminalIcon } from "lucide-react"; import { useRender } from "@base-ui/react/use-render"; -import { useMemo, type MouseEvent, type ReactElement } from "react"; +import { useMemo, type AnimationEvent, type MouseEvent, type ReactElement } from "react"; import { cn } from "../lib/utils"; import { useEnvironment, usePrimaryEnvironmentId } from "../state/environments"; import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; @@ -394,6 +394,17 @@ export function terminalStatusFromRunningIds( }; } +/** Align newly started pulses with the document clock without a timer or frame loop. */ +export function synchronizeTerminalPulse(event: AnimationEvent) { + if (event.animationName !== "status-pulse") return; + + for (const animation of event.currentTarget.getAnimations()) { + if ("animationName" in animation && animation.animationName === "status-pulse") { + animation.startTime = 0; + } + } +} + export function ThreadWorktreeIndicator({ thread, }: { @@ -582,7 +593,8 @@ export function ThreadRowTrailingStatus({ thread }: { thread: SidebarThreadSumma } > {terminalStatus.label} diff --git a/apps/web/src/components/ThreadTerminalDrawer.tsx b/apps/web/src/components/ThreadTerminalDrawer.tsx index b34b8a60b12f..b155421572e0 100644 --- a/apps/web/src/components/ThreadTerminalDrawer.tsx +++ b/apps/web/src/components/ThreadTerminalDrawer.tsx @@ -1447,7 +1447,7 @@ export default function ThreadTerminalDrawer({ void; onSave: (comment: string) => boolean; onSaveAndSend?: (comment: string) => boolean; + /** Returns focus to the host editor when the popover closes instead of to the pencil trigger. */ + onRestoreFocus?: () => void; }; }) { const navigate = useNavigate(); const commentInputRef = useRef(null); + const commentPopupRef = useRef(null); const draftCommentRef = useRef(null); const [unavailableSourceAnchor, setUnavailableSourceAnchor] = useState(null); @@ -155,6 +158,7 @@ export function AssistantCitationChip({ > } >
- {showPhone ? ( + {showPhone && isDuo && model ? ( + + ) : showPhone ? ( ) : null} @@ -433,16 +509,8 @@ export function DeviceStreamView(props: { variant={showPhone ? "secondary" : "ghost"} size="xs" aria-pressed={!!showPhone} - disabled={phoneUnavailable || !!mjpegUrl || !!props.axOverlay} - title={ - phoneUnavailable - ? "3D is unavailable on this browser" - : mjpegUrl - ? "3D requires the H.264 stream" - : props.axOverlay - ? "Turn off accessibility frames to use 3D" - : "Show 3D phone" - } + disabled={!!phoneUnavailableReason} + title={phoneUnavailableReason ?? "Show 3D phone"} onClick={() => setPresentation("phone")} > 3D @@ -464,7 +532,14 @@ export function DeviceStreamView(props: { ) : null} - {status !== "streaming" ? ( + {retainingAndroidFrame && showPhone && showRestartNotice ? ( +
+ + Waiting for device video… + +
+ ) : null} + {status !== "streaming" && !(retainingAndroidFrame && showPhone) ? (
=> + typeof value === "object" && value !== null && !Array.isArray(value); + +const parseFold = (payload: unknown): AndroidFoldState => { + if (!isRecord(payload) || payload.ok !== true || !isRecord(payload.fold)) { + throw new Error("Unexpected Android fold response."); + } + const { supported, posture, hingeAngle } = payload.fold; + if ( + typeof supported !== "boolean" || + (posture !== null && + posture !== "closed" && + posture !== "half_opened" && + posture !== "opened" && + posture !== "flipped" && + posture !== "tent") || + (hingeAngle !== null && (typeof hingeAngle !== "number" || !Number.isFinite(hingeAngle))) + ) { + throw new Error("Unexpected Android fold response."); + } + return { supported, posture, hingeAngle }; +}; + +const foldRequest = async ( + access: DeviceHubAccess, + deviceId: string, + posture?: AndroidFoldPosture, + signal?: AbortSignal, +): Promise => { + const url = withDeviceHubQuery( + `${access.httpBase}/vendor/serve-emu/api/fold?${new URLSearchParams({ device: deviceId })}`, + access, + ); + const response = await fetch(url, { + method: posture ? "POST" : "GET", + cache: "no-store", + credentials: access.credentials ? "include" : "same-origin", + ...(posture + ? { headers: { "content-type": "application/json" }, body: JSON.stringify({ posture }) } + : {}), + ...(signal ? { signal } : {}), + }); + if (!response.ok) { + const payload: unknown = await response.json().catch(() => null); + const error = isRecord(payload) ? payload.error : null; + throw new Error( + typeof error === "string" ? error : `Fold command failed (${response.status}).`, + ); + } + return parseFold(await response.json()); +}; + +export const readAndroidFold = (access: DeviceHubAccess, deviceId: string, signal?: AbortSignal) => + foldRequest(access, deviceId, undefined, signal); + +export const setAndroidFold = ( + access: DeviceHubAccess, + deviceId: string, + posture: AndroidFoldPosture, + signal?: AbortSignal, +) => foldRequest(access, deviceId, posture, signal); diff --git a/apps/web/src/components/device/deviceModels.ts b/apps/web/src/components/device/deviceModels.ts index 682e0e6de4d5..d9197a27e357 100644 --- a/apps/web/src/components/device/deviceModels.ts +++ b/apps/web/src/components/device/deviceModels.ts @@ -4,6 +4,7 @@ import { type DeviceModelSource, } from "@t3tools/client-runtime/device/model"; import type { DevicePlatform } from "@t3tools/contracts"; +import iphoneDuo from "./models/iphone-duo.glb?url"; import iphone18Pro from "./models/iphone-18-pro.glb?url"; import iphone18ProMax from "./models/iphone-18-pro-max.glb?url"; import magicKeyboard from "./models/ipad-pro-13-m5-magic-keyboard.glb?url"; @@ -11,6 +12,7 @@ import ipadPro13M5 from "./models/ipad-pro-13-m5.glb?url"; // Bundled URLs follow the client origin in local, desktop, hosted and remote sessions. const models: Record = { + "iphone-duo": { id: "iphone-duo", url: iphoneDuo }, "iphone-18-pro": { id: "iphone-18-pro", url: iphone18Pro }, "iphone-18-pro-max": { id: "iphone-18-pro-max", url: iphone18ProMax }, "ipad-pro-13-m5": { id: "ipad-pro-13-m5", url: ipadPro13M5 }, diff --git a/apps/web/src/components/device/models/iphone-duo.glb b/apps/web/src/components/device/models/iphone-duo.glb new file mode 100644 index 000000000000..0269a1dc40e0 Binary files /dev/null and b/apps/web/src/components/device/models/iphone-duo.glb differ diff --git a/apps/web/src/components/device/models/sources.json b/apps/web/src/components/device/models/sources.json index 513a56fc291d..766b65a7a759 100644 --- a/apps/web/src/components/device/models/sources.json +++ b/apps/web/src/components/device/models/sources.json @@ -6,7 +6,7 @@ "gltfTransform": "4.2.1", "changes": [ "Extracted device bodies and a separate Magic Keyboard accessory; excluded unrelated parts and presentation duplicates.", - "Normalized portrait axes, centered the screen and uniformly scaled its height to 2.2 scene units.", + "Normalized conventional device portrait axes, centered their screen and uniformly scaled its height to 2.2 scene units. The Duo retains its upstream centimeter hinge rig.", "Replaced the baked marketing screen with a named placeholder for the live framebuffer.", "Resized textures to at most 1024 pixels and encoded them as WebP; retained body geometry." ] @@ -44,6 +44,14 @@ "bodyNode": "zRrSLDpdYmKeRJQ", "screenNode": "lsDiIbtoSGSmWWZ", "accessoryNode": "PoBqSMmyhhcJsBX" + }, + { + "id": "iphone-duo", + "file": "iphone-duo.glb", + "sourceUrl": "https://www.apple.com/105/media/us/iphone-duo/2026/9305e4b9-72d9-4c05-9381-b572adadd5e5/ar/iPhone_Duo_e-sim_Star-White_Variant.usdz", + "sourceSha256": "5cab2ea636da0bc0b06c8abf4809843498f7c1d680042b4f95e383b5c6a718b2", + "upstreamConversionCommit": "bb265b11c13b395e5302d121458e2d42224a2e9f", + "conversion": "Preserved the centimeter split hinge rig and three display meshes from serve-sim; resized textures to 1024 pixels and encoded them as WebP without simplifying or flattening the rig." } ] } diff --git a/apps/web/src/components/files/fileSurfaceChrome.tsx b/apps/web/src/components/files/fileSurfaceChrome.tsx index d5263f73377c..184c5626e90f 100644 --- a/apps/web/src/components/files/fileSurfaceChrome.tsx +++ b/apps/web/src/components/files/fileSurfaceChrome.tsx @@ -29,8 +29,11 @@ export const FILE_LINK_REVEAL_UNSAFE_CSS = ` color: var(--code-foreground, var(--foreground)) !important; } + /* Tint through --diffs-line-bg, not background-color. The editor paints row + tints on a layer below its text selection; a background on the row itself + covers the selection and makes selected text on this line invisible. */ [${FILE_LINK_REVEAL_ATTRIBUTE}][data-line] { - background-color: light-dark( + --diffs-line-bg: light-dark( color-mix( in lab, var(--diffs-computed-diff-line-bg) 82%, diff --git a/apps/web/src/components/settings/AddProviderInstanceDialog.tsx b/apps/web/src/components/settings/AddProviderInstanceDialog.tsx index f1d028a3d2c1..84d0a966880d 100644 --- a/apps/web/src/components/settings/AddProviderInstanceDialog.tsx +++ b/apps/web/src/components/settings/AddProviderInstanceDialog.tsx @@ -287,7 +287,7 @@ export function AddProviderInstanceDialog({ value={option.value} disabled className={cn( - "relative flex cursor-not-allowed items-center gap-3 rounded-lg bg-card/60 px-3 py-3 text-left opacity-55 outline-none ring-1 ring-black/5 dark:bg-white/2 dark:ring-white/5", + "relative flex cursor-not-allowed items-center gap-3 rounded-lg bg-card/60 px-3 py-3 text-left opacity-64 outline-none ring-1 ring-black/5 dark:bg-white/2 dark:ring-white/5", )} > diff --git a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts index 10fc154d5b0c..05fa0943fba8 100644 --- a/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts +++ b/apps/web/src/components/settings/KeybindingsSettings.logic.test.ts @@ -243,6 +243,7 @@ describe("KeybindingsSettings.logic", () => { "chat.new", "rightPanel.toggleMaximized", "thread.stop", + "usage.open", "script.setup-db.run", ]), ); diff --git a/apps/web/src/components/settings/ProviderInstanceCard.tsx b/apps/web/src/components/settings/ProviderInstanceCard.tsx index 4e2405c138b1..ff0546f5012d 100644 --- a/apps/web/src/components/settings/ProviderInstanceCard.tsx +++ b/apps/web/src/components/settings/ProviderInstanceCard.tsx @@ -570,7 +570,7 @@ export function ProviderInstanceCard({ showBadge={Boolean(accentColor)} className="size-5" iconClassName="size-4 text-foreground/80" - badgeClassName="right-[-0.125rem] bottom-[-0.125rem] h-3 min-w-3 px-0.5 text-3xs" + badgeClassName="right-[-0.125rem] bottom-[-0.125rem] h-3 min-w-3 px-0.5 text-5xs" /> ) : FallbackIconComponent ? ( diff --git a/apps/web/src/components/settings/ProviderSettingsPanel.tsx b/apps/web/src/components/settings/ProviderSettingsPanel.tsx index acec3be8387f..6722969075a1 100644 --- a/apps/web/src/components/settings/ProviderSettingsPanel.tsx +++ b/apps/web/src/components/settings/ProviderSettingsPanel.tsx @@ -314,7 +314,25 @@ function ProviderSettingsPanelContent(target: ProviderSettingsTarget) { hasServerConfig: environment.serverConfig !== null, }), )?.environmentId; + const searchableCursorEnvironmentId = options.find( + (environment) => + environment.serverConfig?.environment.platform.os === "darwin" && + isProviderSettingsEnvironmentAvailable({ + connectionPhase: environment.connection.phase, + hasServerConfig: true, + }), + )?.environmentId; useEffect(() => { + if ( + !target.scoped && + searchTargetId === searchableSetting("cursor-keychain-usage").id && + (!selectedEnvironmentCanRenderSettings || + selectedEnvironment?.serverConfig?.environment.platform.os !== "darwin") && + searchableCursorEnvironmentId !== undefined + ) { + setSelectedEnvironmentId(searchableCursorEnvironmentId); + return; + } if ( !target.scoped && (searchTargetId === searchableSetting("provider-health-check-interval").id || @@ -326,7 +344,9 @@ function ProviderSettingsPanelContent(target: ProviderSettingsTarget) { } }, [ searchTargetId, + searchableCursorEnvironmentId, searchableEnvironmentId, + selectedEnvironment, selectedEnvironmentCanRenderSettings, target.scoped, ]); @@ -1102,6 +1122,7 @@ export function EnvironmentProviderSettings({ environmentId={environmentId} environmentLabel={environmentLabel} sources={settings.usageLimitSources} + cursorKeychainUsageEnabled={settings.cursorKeychainUsageEnabled} readOnly={readOnly} /> diff --git a/apps/web/src/components/settings/SettingsSidebarNav.tsx b/apps/web/src/components/settings/SettingsSidebarNav.tsx index e2ea39c761f2..142972cf1ece 100644 --- a/apps/web/src/components/settings/SettingsSidebarNav.tsx +++ b/apps/web/src/components/settings/SettingsSidebarNav.tsx @@ -116,7 +116,7 @@ export function SettingsSidebarNav({ pathname }: { pathname: string }) { const searchInputRef = useRef(null); const [query, setQuery] = useState(""); const [activeResultIndex, setActiveResultIndex] = useState(0); - const searchableItems = useAvailableSettingsSearchItems(); + const searchableItems = useAvailableSettingsSearchItems(scopeSearch); const results = useMemo(() => searchSettings(query, searchableItems), [query, searchableItems]); const isSearching = query.trim().length > 0; const hasResults = results.length > 0; diff --git a/apps/web/src/components/settings/UsageProviderSettings.tsx b/apps/web/src/components/settings/UsageProviderSettings.tsx index cf2887f39684..46ed099b166e 100644 --- a/apps/web/src/components/settings/UsageProviderSettings.tsx +++ b/apps/web/src/components/settings/UsageProviderSettings.tsx @@ -1,8 +1,11 @@ import type { EnvironmentId, UnifiedSettings } from "@t3tools/contracts"; +import { useAtomValue } from "@effect/atom-react"; import { PlusIcon } from "lucide-react"; import { useState } from "react"; import { useUpdateEnvironmentSettings } from "../../hooks/useSettings"; +import { serverEnvironment } from "../../state/server"; +import { useAtomCommand } from "../../state/use-atom-command"; import { AlertDialog, AlertDialogClose, @@ -13,6 +16,7 @@ import { AlertDialogTitle, } from "../ui/alert-dialog"; import { Button } from "../ui/button"; +import { Switch } from "../ui/switch"; import { AddUsageLimitSourceDialog } from "./AddUsageLimitSourceDialog"; import { searchableSetting } from "./settingsSearch"; import { SettingsRow, SettingsSection } from "./settingsLayout"; @@ -22,17 +26,43 @@ export function UsageProviderSettings({ environmentId, environmentLabel, sources, + cursorKeychainUsageEnabled, readOnly, }: { readonly environmentId: EnvironmentId; readonly environmentLabel: string; readonly sources: UnifiedSettings["usageLimitSources"]; + readonly cursorKeychainUsageEnabled: boolean; readonly readOnly: boolean; }) { const updateSettings = useUpdateEnvironmentSettings(environmentId); + const updateCursorSettings = useAtomCommand(serverEnvironment.updateSettings, { + label: "update Cursor account usage", + }); + const refreshProviders = useAtomCommand(serverEnvironment.refreshProviders, { + reportFailure: false, + }); + const platform = useAtomValue(serverEnvironment.configValueAtom(environmentId))?.environment + .platform; const [adding, setAdding] = useState(false); + const [updatingCursor, setUpdatingCursor] = useState(false); const entries = Object.entries(sources); + const setCursorUsageEnabled = async (enabled: boolean) => { + setUpdatingCursor(true); + try { + const result = await updateCursorSettings({ + environmentId, + input: { patch: { cursorKeychainUsageEnabled: enabled } }, + }); + if (result._tag === "Success") { + await refreshProviders({ environmentId, input: {} }); + } + } finally { + setUpdatingCursor(false); + } + }; + return ( <> + {platform?.os === "darwin" ? ( + void setCursorUsageEnabled(enabled)} + /> + } + /> + ) : null} {entries.length === 0 ? ( - + ) : ( entries.map(([id, source]) => { const label = source.label?.trim() || source.url; diff --git a/apps/web/src/components/settings/settingsLayout.tsx b/apps/web/src/components/settings/settingsLayout.tsx index 8fe70c975f77..0794553cb8fb 100644 --- a/apps/web/src/components/settings/settingsLayout.tsx +++ b/apps/web/src/components/settings/settingsLayout.tsx @@ -427,7 +427,7 @@ export function SettingsRow({ tabIndex={rowProps.id ? -1 : rowProps.tabIndex} data-slot="settings-row" className={cn( - "@container/settings-row rounded-xl px-3 sm:px-4 aria-disabled:opacity-50 aria-disabled:[&_*]:text-muted-foreground", + "@container/settings-row rounded-xl px-3 sm:px-4 aria-disabled:opacity-64 aria-disabled:[&_*]:text-muted-foreground", children ? "pt-3 pb-1" : "py-3", className, )} diff --git a/apps/web/src/components/settings/settingsSearch.test.ts b/apps/web/src/components/settings/settingsSearch.test.ts index 569d736a62de..9bd4f9906acd 100644 --- a/apps/web/src/components/settings/settingsSearch.test.ts +++ b/apps/web/src/components/settings/settingsSearch.test.ts @@ -154,6 +154,7 @@ describe("searchSettings", () => { hasCloudPublicConfig: false, hasEnvironment: false, hasProviderSettingsEnvironment: false, + hasMacProviderSettingsEnvironment: false, canManageLocalBackend: false, isWslSettingsRowVisible: false, hasThreadAutoSettlement: false, @@ -165,6 +166,7 @@ describe("searchSettings", () => { "network-access", "publish-agent-activity", "provider-health-check-interval", + "cursor-keychain-usage", "source-control-writer-model", "source-control-writing-style", "t3-connect", @@ -177,11 +179,31 @@ describe("searchSettings", () => { expect(available.map((item) => item.id).filter((id) => gatedIds.has(id))).toEqual([]); }); + it("offers Cursor Keychain settings only when a macOS provider environment is available", () => { + const availability = { + hasCloudPublicConfig: false, + hasEnvironment: true, + hasProviderSettingsEnvironment: true, + hasMacProviderSettingsEnvironment: false, + canManageLocalBackend: false, + isWslSettingsRowVisible: false, + hasThreadAutoSettlement: false, + }; + const itemIds = (macAvailable: boolean) => + filterAvailableSettingsSearchItems({ + ...availability, + hasMacProviderSettingsEnvironment: macAvailable, + }).map((item) => item.id); + expect(itemIds(false)).not.toContain("cursor-keychain-usage"); + expect(itemIds(true)).toContain("cursor-keychain-usage"); + }); + it("keeps the local toggle searchable without offering hidden host publishing controls", () => { const availability = { hasCloudPublicConfig: true, hasEnvironment: true, hasProviderSettingsEnvironment: true, + hasMacProviderSettingsEnvironment: false, canManageLocalBackend: false, isWslSettingsRowVisible: false, hasThreadAutoSettlement: false, @@ -204,6 +226,7 @@ describe("searchSettings", () => { hasCloudPublicConfig: false, hasEnvironment: false, hasProviderSettingsEnvironment: false, + hasMacProviderSettingsEnvironment: false, canManageLocalBackend: false, isWslSettingsRowVisible: false, hasThreadAutoSettlement: true, @@ -330,6 +353,7 @@ describe("searchSettings", () => { hasCloudPublicConfig: false, hasEnvironment: true, hasProviderSettingsEnvironment: true, + hasMacProviderSettingsEnvironment: false, canManageLocalBackend: false, isWslSettingsRowVisible: false, hasThreadAutoSettlement: true, @@ -424,6 +448,7 @@ describe("auto-settlement search availability", () => { hasCloudPublicConfig: false, hasEnvironment: true, hasProviderSettingsEnvironment: true, + hasMacProviderSettingsEnvironment: false, canManageLocalBackend: false, isWslSettingsRowVisible: false, hasThreadAutoSettlement: availability.eligibleEnvironmentIds.length > 0, diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts index acc9b7eb7dbe..539e780baaa8 100644 --- a/apps/web/src/components/settings/settingsSearch.ts +++ b/apps/web/src/components/settings/settingsSearch.ts @@ -55,6 +55,7 @@ export interface SettingsSearchItem { readonly cloudOnly?: boolean; readonly environmentOnly?: boolean; readonly providerSettingsOnly?: boolean; + readonly macProviderSettingsOnly?: boolean; readonly localBackendManagementOnly?: boolean; readonly localEnvironmentOnly?: boolean; readonly wslAvailableOnly?: boolean; @@ -71,6 +72,7 @@ export interface SettingsSearchAvailability { readonly hasCloudPublicConfig: boolean; readonly hasEnvironment: boolean; readonly hasProviderSettingsEnvironment: boolean; + readonly hasMacProviderSettingsEnvironment: boolean; readonly canManageLocalBackend: boolean; readonly isWslSettingsRowVisible: boolean; readonly hasThreadAutoSettlement: boolean; @@ -546,6 +548,14 @@ export const SETTINGS_SEARCH_ITEMS = [ ], providerSettingsOnly: true, }, + { + id: "cursor-keychain-usage", + title: "Cursor account usage", + to: "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/settings/providers", + searchTerms: ["cursor macOS keychain usage tokens cost limits permission"], + providerSettingsOnly: true, + macProviderSettingsOnly: true, + }, { id: "provider-health-check-interval", title: "Health check interval", @@ -943,6 +953,7 @@ export function filterAvailableSettingsSearchItems( (!item.cloudOnly || availability.hasCloudPublicConfig) && (!item.environmentOnly || availability.hasEnvironment) && (!item.providerSettingsOnly || availability.hasProviderSettingsEnvironment) && + (!item.macProviderSettingsOnly || availability.hasMacProviderSettingsEnvironment) && (!item.localBackendManagementOnly || availability.canManageLocalBackend) && (!item.localEnvironmentOnly || !availability.localEnvironmentDisabled) && (!item.wslAvailableOnly || availability.isWslSettingsRowVisible) && diff --git a/apps/web/src/components/settings/useAvailableSettingsSearchItems.ts b/apps/web/src/components/settings/useAvailableSettingsSearchItems.ts index a0601e41729f..a5be950b81ab 100644 --- a/apps/web/src/components/settings/useAvailableSettingsSearchItems.ts +++ b/apps/web/src/components/settings/useAvailableSettingsSearchItems.ts @@ -10,12 +10,13 @@ import { useEnvironmentQuery } from "~/state/query"; import { usePrimarySessionState } from "~/environments/primary"; import { isWslSettingsRowVisible } from "./ConnectionsSettings.logic"; import { isProviderSettingsEnvironmentAvailable } from "./ProviderSettingsPanel.logic"; +import type { SettingsScopeSearch } from "./settingsScope"; import { filterAvailableSettingsSearchItems, getThreadAutoSettlementSearchAvailability, } from "./settingsSearch"; -export function useAvailableSettingsSearchItems() { +export function useAvailableSettingsSearchItems(scopeSearch: SettingsScopeSearch = {}) { const { environments } = useEnvironments(); const primarySessionState = usePrimarySessionState(); const localEnvironmentDisabled = isLocalEnvironmentDisabled(); @@ -41,6 +42,16 @@ export function useAvailableSettingsSearchItems() { hasServerConfig: environment.serverConfig !== null, }), ), + hasMacProviderSettingsEnvironment: environments.some( + (environment) => + (scopeSearch.machine === undefined || + environment.environmentId === scopeSearch.machine) && + environment.serverConfig?.environment.platform.os === "darwin" && + isProviderSettingsEnvironmentAvailable({ + connectionPhase: environment.connection.phase, + hasServerConfig: true, + }), + ), canManageLocalBackend, isWslSettingsRowVisible: isWslSettingsRowVisible({ state: desktopWsl.data, @@ -55,6 +66,7 @@ export function useAvailableSettingsSearchItems() { desktopWsl.error, environments, localEnvironmentDisabled, + scopeSearch.machine, ], ); } diff --git a/apps/web/src/components/sidebar/SidebarChrome.tsx b/apps/web/src/components/sidebar/SidebarChrome.tsx index a37aba27b8de..af4bd8a1f5cb 100644 --- a/apps/web/src/components/sidebar/SidebarChrome.tsx +++ b/apps/web/src/components/sidebar/SidebarChrome.tsx @@ -1,7 +1,7 @@ import { ArrowLeftIcon, ChartNoAxesColumnIcon, SettingsIcon } from "lucide-react"; import type { ReactNode } from "react"; import { memo, useCallback } from "react"; -import { Link, useCanGoBack, useLocation, useNavigate } from "@tanstack/react-router"; +import { Link, useLocation, useNavigate } from "@tanstack/react-router"; import { useEnvironmentIdentificationMode } from "../../hooks/useSettings"; import { cn } from "../../lib/utils"; @@ -24,6 +24,7 @@ import { } from "../ui/sidebar"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { readPullRequestListPreferences } from "../pullRequest/pullRequestListPreferences"; +import { isSidebarUtilityPage, useNavigateToMainApp } from "./mainAppLocation"; import { SidebarThreadUndoNotice } from "./SidebarThreadUndoNotice"; import { SidebarProviderUpdatePill } from "./SidebarProviderUpdatePill"; import { SidebarUpdateArchitectureWarning, SidebarUpdatePill } from "./SidebarUpdatePill"; @@ -127,19 +128,10 @@ function SidebarUtilityItem({ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { const navigate = useNavigate(); - const canGoBack = useCanGoBack(); + const navigateToMainApp = useNavigateToMainApp(); const { isMobile, setOpenMobile } = useSidebar(); - const currentFooterPage = useLocation({ - select: (location) => - /^\/settings(?:\/|$)/.test(location.pathname) - ? "settings" - : /^\/projects\/[^/]+\/?$/.test(location.pathname) - ? "project-settings" - : location.pathname === "/usage" - ? "usage" - : location.pathname === "/pull-requests" - ? "pull-requests" - : null, + const isOnUtilityPage = useLocation({ + select: (location) => isSidebarUtilityPage(location.pathname), }); const { environments } = useEnvironments(); // The page reads every connected server, so one of them offering pull requests is enough for @@ -173,16 +165,12 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { const handleBackClick = useCallback(() => { closeMobileSidebar(); - if (canGoBack) { - window.history.back(); - return; - } - void navigate({ to: "/" }); - }, [canGoBack, closeMobileSidebar, navigate]); + void navigateToMainApp(); + }, [closeMobileSidebar, navigateToMainApp]); return ( - {currentFooterPage ? ( + {isOnUtilityPage ? ( diff --git a/apps/web/src/components/sidebar/mainAppLocation.ts b/apps/web/src/components/sidebar/mainAppLocation.ts new file mode 100644 index 000000000000..fcbf2f489d33 --- /dev/null +++ b/apps/web/src/components/sidebar/mainAppLocation.ts @@ -0,0 +1,36 @@ +import { useLocation, useNavigate } from "@tanstack/react-router"; +import { useCallback, useEffect } from "react"; + +// Settings, Usage, and Pull Requests replace the sidebar utility row with a +// Back button. Everything else is the main app. Legacy `/projects/` links +// redirect into settings, so they count too and are never remembered. +export function isSidebarUtilityPage(pathname: string) { + return ( + pathname === "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/settings" || + pathname.startsWith("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/settings/") || + pathname.startsWith("/projects/") || + pathname === "/usage" || + pathname === "/pull-requests" + ); +} + +let mainAppHref: string | null = null; + +// Mount once in the app shell. Records the latest main app URL so Back can +// return there no matter how many utility pages were visited since. +export function MainAppLocationTracker() { + const href = useLocation({ + select: (location) => (isSidebarUtilityPage(location.pathname) ? null : location.href), + }); + useEffect(() => { + if (href !== null) mainAppHref = href; + }, [href]); + return null; +} + +// Leaves a utility page for the last main app URL, or the thread list when +// the app was opened directly on a utility page. +export function useNavigateToMainApp() { + const navigate = useNavigate(); + return useCallback(() => navigate({ href: mainAppHref ?? "/" }), [navigate]); +} diff --git a/apps/web/src/components/threadActionMenu.logic.test.ts b/apps/web/src/components/threadActionMenu.logic.test.ts index 14965d4add58..c5ebeda55627 100644 --- a/apps/web/src/components/threadActionMenu.logic.test.ts +++ b/apps/web/src/components/threadActionMenu.logic.test.ts @@ -7,11 +7,18 @@ const baseState: ThreadActionMenuState = { projectFilter: null, isPinned: false, isSettled: false, + autoSettleEnabled: true, isSnoozed: false, canSnoozeNow: true, isRegeneratingTitle: false, isRunning: false, - supports: { settlement: true, snooze: true, pinning: true, titleRegeneration: true }, + supports: { + settlement: true, + autoSettleOptOut: true, + snooze: true, + pinning: true, + titleRegeneration: true, + }, snoozePresets: [ { id: "hour", label: "In 1 hour", whenLabel: "3:00 PM", snoozedUntil: "2026-08-07T15:00:00Z" }, ], @@ -32,7 +39,13 @@ describe("buildThreadActionMenuItems", () => { expect( ids({ ...baseState, - supports: { settlement: false, snooze: false, pinning: false, titleRegeneration: false }, + supports: { + settlement: false, + autoSettleOptOut: false, + snooze: false, + pinning: false, + titleRegeneration: false, + }, }), ).toEqual(["rename", "mark-unread", "copy", "project-settings", "archive", "delete"]); }); @@ -66,7 +79,7 @@ describe("buildThreadActionMenuItems", () => { const filterIndex = items.findIndex((candidate) => candidate.id === "filter-by-project"); expect(items[filterIndex]).toMatchObject({ label: "Show all projects", icon: "folder-tree" }); expect(items[filterIndex - 1]?.id).toBe("mark-unread"); - expect(items[filterIndex + 1]?.id).toBe("copy"); + expect(items[filterIndex + 1]?.id).toBe("auto-settle"); }); it("includes branch items only for threads with a branch", () => { @@ -84,6 +97,25 @@ describe("buildThreadActionMenuItems", () => { expect(ids(baseState)).toEqual(expect.arrayContaining(["pin", "settle", "snooze"])); }); + it("offers auto-settle as a submenu with the current option checked", () => { + const find = (state: ThreadActionMenuState) => + buildThreadActionMenuItems(state).find((item) => item.id === "auto-settle"); + const on = find(baseState); + expect(on?.label).toBe("Auto-settle behavior"); + expect(on?.children?.map((child) => [child.id, child.checked])).toEqual([ + ["auto-settle:enabled", true], + ["auto-settle:disabled", false], + ]); + const off = find({ ...baseState, autoSettleEnabled: false }); + expect(off?.children?.map((child) => child.checked)).toEqual([false, true]); + // Sits with the per-thread settings after Mark unread, not the lifecycle verbs. + const items = buildThreadActionMenuItems(baseState); + expect(items[items.findIndex((item) => item.id === "mark-unread") + 1]?.id).toBe("auto-settle"); + expect( + ids({ ...baseState, supports: { ...baseState.supports, autoSettleOptOut: false } }), + ).not.toContain("auto-settle"); + }); + it("disables snooze when the thread cannot snooze, keeping presets visible", () => { const snooze = buildThreadActionMenuItems({ ...baseState, canSnoozeNow: false }).find( (item) => item.id === "snooze", @@ -117,7 +149,13 @@ describe("buildThreadActionMenuItems", () => { expect( ids({ ...baseState, - supports: { settlement: false, snooze: false, pinning: false, titleRegeneration: false }, + supports: { + settlement: false, + autoSettleOptOut: false, + snooze: false, + pinning: false, + titleRegeneration: false, + }, }), ).toContain("archive"); }); diff --git a/apps/web/src/components/threadActionMenu.logic.ts b/apps/web/src/components/threadActionMenu.logic.ts index c37ec31f929c..838fc91a636c 100644 --- a/apps/web/src/components/threadActionMenu.logic.ts +++ b/apps/web/src/components/threadActionMenu.logic.ts @@ -14,6 +14,9 @@ export type ThreadActionMenuId = | "unpin" | "settle" | "unsettle" + | "auto-settle" + | "auto-settle:enabled" + | "auto-settle:disabled" | "snooze" | `snooze:${string}` | "unsnooze" @@ -40,6 +43,8 @@ export interface ThreadActionMenuState { } | null; readonly isPinned: boolean; readonly isSettled: boolean; + /** False while the user has turned automatic settlement off for this thread. */ + readonly autoSettleEnabled: boolean; readonly isSnoozed: boolean; readonly canSnoozeNow: boolean; readonly isRegeneratingTitle: boolean; @@ -47,6 +52,8 @@ export interface ThreadActionMenuState { readonly isRunning: boolean; readonly supports: { readonly settlement: boolean; + /** Server understands thread.auto-settle.set. */ + readonly autoSettleOptOut: boolean; readonly snooze: boolean; readonly pinning: boolean; readonly titleRegeneration: boolean; @@ -131,6 +138,31 @@ export function buildThreadActionMenuItems( }, ] : []), + // A submenu with the current option checked, not a one-shot action: + // this is a setting, and it sits with the other per-thread settings + // rather than the lifecycle verbs above. Disabled keeps long-running + // threads out of the settled shelf no matter how quiet they get. + ...(state.supports.autoSettleOptOut + ? [ + { + id: "auto-settle" as const, + label: "Auto-settle behavior", + icon: "timer", + children: [ + { + id: "auto-settle:enabled" as const, + label: "Enabled", + checked: state.autoSettleEnabled, + }, + { + id: "auto-settle:disabled" as const, + label: "Disabled", + checked: !state.autoSettleEnabled, + }, + ], + }, + ] + : []), { id: "copy", label: "Copy", diff --git a/apps/web/src/components/ui/discovery-list.tsx b/apps/web/src/components/ui/discovery-list.tsx index d03ca0edd5e0..70af3c8418bd 100644 --- a/apps/web/src/components/ui/discovery-list.tsx +++ b/apps/web/src/components/ui/discovery-list.tsx @@ -24,7 +24,7 @@ export function DiscoveryListRow({ + ); + if (!tooltip) return button; + return ( + + + {CURSOR_KEYCHAIN_COPY} + + ); +} + +function CursorEnableRow({ + environmentId, + label, + showEnvironment, + onEnabled, +}: { + readonly environmentId: EnvironmentId; + readonly label: string; + readonly showEnvironment: boolean; + readonly onEnabled: () => void; +}) { + return ( +
+ + + + Cursor{showEnvironment ? ` · ${label}` : ""} + + +
+ ); +} + +function CursorEnableLimits({ + environments, + onEnabled, +}: { + readonly environments: readonly EnvironmentUsageStatus[]; + readonly onEnabled: () => void; +}) { + return ( +
+

+ + Cursor +

+
+

{CURSOR_KEYCHAIN_COPY}

+
+ {environments.map((environment) => ( + 1 ? `Enable on ${environment.label}` : "Enable"} + onEnabled={onEnabled} + tooltip={false} + /> + ))} +
+
+
+ ); +} + /** Brand mark for the harness a row belongs to. */ function ProviderMark({ provider, diff --git a/apps/web/src/components/usage/UsageProviderChart.test.ts b/apps/web/src/components/usage/UsageProviderChart.test.ts index 622d73d13844..e3060dbaefd3 100644 --- a/apps/web/src/components/usage/UsageProviderChart.test.ts +++ b/apps/web/src/components/usage/UsageProviderChart.test.ts @@ -89,6 +89,9 @@ describe("buildPeriodColumns", () => { { provider: "codex", value: 10 }, { provider: "claude", value: 20 }, { provider: "grok", value: 0 }, + { provider: "cursor", value: 0 }, + { provider: "opencode", value: 0 }, + { provider: "antigravity", value: 0 }, ]); }); diff --git a/apps/web/src/components/usage/usageProviders.ts b/apps/web/src/components/usage/usageProviders.ts index efad95e531ad..38b78cbcdb04 100644 --- a/apps/web/src/components/usage/usageProviders.ts +++ b/apps/web/src/components/usage/usageProviders.ts @@ -1,6 +1,14 @@ import type { UsageProviderKind } from "@t3tools/contracts"; -import { ClaudeAI, GrokIcon, type Icon, OpenAI } from "../Icons"; +import { + AntigravityIcon, + ClaudeAI, + CursorIcon, + GrokIcon, + type Icon, + OpenAI, + OpenCodeIcon, +} from "../Icons"; type UsageProviderPresentation = { readonly label: string; @@ -30,6 +38,9 @@ export const PROVIDER_PRESENTATION = { color: "color-mix(in oklab, var(--contrast-foreground) 72%, var(--background))", mark: GrokIcon, }, + cursor: { label: "Cursor", color: "#8b8b8b", mark: CursorIcon }, + opencode: { label: "OpenCode", color: "#5b9bbd", mark: OpenCodeIcon }, + antigravity: { label: "Antigravity", color: "#8c7bd1", mark: AntigravityIcon }, } satisfies Record; /** Stable provider reading order across charts, summaries, tables, and hover rows. */ diff --git a/apps/web/src/connection/platform.ts b/apps/web/src/connection/platform.ts index bcc2849dd041..edd15c8df3eb 100644 --- a/apps/web/src/connection/platform.ts +++ b/apps/web/src/connection/platform.ts @@ -213,7 +213,7 @@ const capabilitiesLayer = Layer.effectContext( }), }); const identity = RelayDeviceIdentity.of({ - deviceId: Effect.succeed(Option.none()), + deviceId: Effect.succeedNone, }); const primaryAuth = PrimaryEnvironmentAuth.of({ bearerToken: Effect.tryPromise({ diff --git a/apps/web/src/connection/storage.ts b/apps/web/src/connection/storage.ts index 6e777c663199..4c6287e1b739 100644 --- a/apps/web/src/connection/storage.ts +++ b/apps/web/src/connection/storage.ts @@ -587,7 +587,7 @@ export const connectionStorageLayer = Layer.effectContext( Effect.tap(() => Effect.promise(() => projectFaviconCache.hydrate())), Effect.flatMap((raw) => { if (typeof raw !== "string") { - return Effect.succeed(Option.none()); + return Effect.succeedNone; } return decodeStoredShellSnapshot(raw).pipe( Effect.mapError((cause) => persistenceError("load-shell", cause)), @@ -623,7 +623,7 @@ export const connectionStorageLayer = Layer.effectContext( readDatabaseValue(database, SERVER_CONFIG_STORE_NAME, environmentId).pipe( Effect.flatMap((raw) => { if (typeof raw !== "string") { - return Effect.succeed(Option.none()); + return Effect.succeedNone; } return decodeStoredServerConfig(raw).pipe( Effect.mapError((cause) => persistenceError("load-server-config", cause)), @@ -661,7 +661,7 @@ export const connectionStorageLayer = Layer.effectContext( ).pipe( Effect.flatMap((raw) => { if (typeof raw !== "string") { - return Effect.succeed(Option.none()); + return Effect.succeedNone; } return decodeStoredThreadSnapshot(raw).pipe( Effect.mapError((cause) => persistenceError("load-thread", cause)), @@ -703,7 +703,7 @@ export const connectionStorageLayer = Layer.effectContext( readDatabaseValue(database, VCS_REFS_STORE_NAME, vcsRefsCacheKey(environmentId, cwd)).pipe( Effect.flatMap((raw) => { if (typeof raw !== "string") { - return Effect.succeed(Option.none()); + return Effect.succeedNone; } return decodeStoredVcsRefs(raw).pipe( Effect.mapError((cause) => persistenceError("load-vcs-refs", cause)), diff --git a/apps/web/src/contextMenuFallback.ts b/apps/web/src/contextMenuFallback.ts index 2c43641b2b56..db14ed8c860c 100644 --- a/apps/web/src/contextMenuFallback.ts +++ b/apps/web/src/contextMenuFallback.ts @@ -9,6 +9,12 @@ const ICON_PATHS: Record( button.style.pointerEvents = "none"; } - if (typeof item.icon === "string") { + if (typeof item.checked === "boolean") { + // Option rows use the icon slot for the check so labels line up + // with icon rows. The unselected option keeps the slot empty. + button.setAttribute("role", "menuitemradio"); + button.setAttribute("aria-checked", item.checked ? "true" : "false"); + const check = item.checked ? createIconElement("check", "neutral") : null; + if (check) { + button.appendChild(check); + } else { + const spacer = document.createElement("span"); + spacer.className = "size-4.5 shrink-0 sm:size-4"; + spacer.style.cssText = "display:inline-block;width:1rem;height:1rem;flex-shrink:0;"; + spacer.setAttribute("aria-hidden", "true"); + button.appendChild(spacer); + } + } else if (typeof item.icon === "string") { const icon = createIconElement(item.icon, isLeafDestructive ? "destructive" : "neutral"); if (icon) { button.appendChild(icon); diff --git a/apps/web/src/hooks/useDelayedStatus.ts b/apps/web/src/hooks/useDelayedStatus.ts new file mode 100644 index 000000000000..c777e3c61390 --- /dev/null +++ b/apps/web/src/hooks/useDelayedStatus.ts @@ -0,0 +1,18 @@ +import { createDelayedStatus, type ShownStatus } from "@t3tools/client-runtime/delayed-status"; +import { useEffect, useState } from "react"; + +/** + * Returns `value` only once it has lasted past the show delay, then holds it + * for a minimum time, so a short status never flashes. `key` is what the + * status belongs to (for example a thread). A new key drops it at once. + * Mobile has the same hook. + */ +export function useDelayedStatus(key: string, value: A | null): A | null { + const [shown, setShown] = useState | null>(null); + const [status] = useState(() => createDelayedStatus(setShown)); + useEffect(() => () => status.dispose(), [status]); + useEffect(() => { + status.update(key, value); + }, [status, key, value]); + return shown?.key === key ? shown.value : null; +} diff --git a/apps/web/src/hooks/useThreadActionMenu.ts b/apps/web/src/hooks/useThreadActionMenu.ts index 8a24c5110fa7..4a99fa8f5c73 100644 --- a/apps/web/src/hooks/useThreadActionMenu.ts +++ b/apps/web/src/hooks/useThreadActionMenu.ts @@ -20,6 +20,7 @@ import { stackedThreadToast, toastManager } from "../components/ui/toast"; import { threadEnvironment } from "../state/threads"; import { useAtomCommand } from "../state/use-atom-command"; import { + readEnvironmentSupportsAutoSettleOptOut, readEnvironmentSupportsPinning, readEnvironmentSupportsSettlement, readEnvironmentSupportsSnooze, @@ -88,6 +89,7 @@ export function useThreadActionMenu(input: { unsnoozeThread, pinThread, confirmAndUnpinThread, + setThreadAutoSettle, archiveThread, deleteThread, } = useThreadActions(); @@ -132,6 +134,7 @@ export function useThreadActionMenu(input: { const now = new Date(); const supports = { settlement: readEnvironmentSupportsSettlement(threadRef.environmentId), + autoSettleOptOut: readEnvironmentSupportsAutoSettleOptOut(threadRef.environmentId), snooze: readEnvironmentSupportsSnooze(threadRef.environmentId), pinning: readEnvironmentSupportsPinning(threadRef.environmentId), titleRegeneration: readEnvironmentSupportsTitleRegeneration(threadRef.environmentId), @@ -145,6 +148,7 @@ export function useThreadActionMenu(input: { projectFilter: null, isPinned: thread.pinnedAt != null, isSettled: supports.settlement && thread.settledOverride === "settled", + autoSettleEnabled: thread.autoSettleDisabledAt == null, isSnoozed: supports.snooze && effectiveSnoozed(thread, { now: now.toISOString() }), canSnoozeNow: canSnooze(thread, { now: now.toISOString() }), isRegeneratingTitle, @@ -225,6 +229,12 @@ export function useThreadActionMenu(input: { await reportFailure("Failed to unpin thread", () => confirmAndUnpinThread(threadRef)); return; } + case "auto-settle:enabled": + case "auto-settle:disabled": + await reportFailure("Failed to update auto-settle", () => + setThreadAutoSettle(threadRef, action === "auto-settle:enabled"), + ); + return; case "rename": onStartRename(); return; @@ -333,6 +343,7 @@ export function useThreadActionMenu(input: { projectGroupingSettings, projects, router, + setThreadAutoSettle, settleThread, snoozeThread, threadRef, diff --git a/apps/web/src/hooks/useThreadActions.ts b/apps/web/src/hooks/useThreadActions.ts index 0e5491099223..6a82b920ab31 100644 --- a/apps/web/src/hooks/useThreadActions.ts +++ b/apps/web/src/hooks/useThreadActions.ts @@ -26,6 +26,7 @@ import { refreshArchivedThreadsForEnvironment } from "../lib/archivedThreadsStat import { releaseComposerDraftUploads } from "../lib/composerDraftUploads"; import { readLocalApi } from "../localApi"; import { + readEnvironmentSupportsAutoSettleOptOut, readEnvironmentSupportsPinning, readEnvironmentSupportsPinReorder, readEnvironmentSupportsActiveReorder, @@ -106,6 +107,18 @@ function topOfPinnedRunOrderKey(): string | undefined { return pinOrderKeyBetween(null, firstKey) ?? undefined; } +export class ThreadAutoSettleOptOutUnsupportedError extends Schema.TaggedError()( + "ThreadAutoSettleOptOutUnsupportedError", + { + environmentId: EnvironmentId, + threadId: ThreadId, + }, +) { + override get message(): string { + return "This environment's server does not support turning auto-settle off per thread yet. Update the server to use it."; + } +} + export class ThreadPinningUnsupportedError extends Schema.TaggedError()( "ThreadPinningUnsupportedError", { @@ -200,6 +213,9 @@ export function useThreadActions() { const unpinThreadMutation = useAtomCommand(threadEnvironment.unpin, { reportFailure: false, }); + const setThreadAutoSettleMutation = useAtomCommand(threadEnvironment.setAutoSettle, { + reportFailure: false, + }); const reorderPinnedThreadMutation = useAtomCommand(threadEnvironment.reorderPin, { reportFailure: false, }); @@ -556,6 +572,27 @@ export function useThreadActions() { [unsettleThreadMutation], ); + /** Turns automatic settlement (inactivity, merged PR) on or off for one thread. */ + const setThreadAutoSettle = useCallback( + async (target: ScopedThreadRef, enabled: boolean) => { + if (!readEnvironmentSupportsAutoSettleOptOut(target.environmentId)) { + return AsyncResult.failure( + Cause.fail( + new ThreadAutoSettleOptOutUnsupportedError({ + environmentId: target.environmentId, + threadId: target.threadId, + }), + ), + ); + } + return setThreadAutoSettleMutation({ + environmentId: target.environmentId, + input: { threadId: target.threadId, enabled }, + }); + }, + [setThreadAutoSettleMutation], + ); + const pinThread = useCallback( async (target: ScopedThreadRef, opts: { orderKey?: string } = {}) => { // Version skew: never send the command to a server that predates it. @@ -875,6 +912,7 @@ export function useThreadActions() { confirmAndUnpinThread, reorderPinnedThread, reorderActiveThread, + setThreadAutoSettle, }), [ archiveThread, @@ -884,6 +922,7 @@ export function useThreadActions() { pinThread, reorderPinnedThread, reorderActiveThread, + setThreadAutoSettle, settleThread, snoozeThread, unarchiveThread, diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 9caf42ca2d6c..1039f23b19b8 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -161,12 +161,17 @@ html[data-mobile-composer-route-transition="true"]::view-transition-old(t3-mobil @theme inline { /* Two steps below Tailwind's text-xs (12px) for dense UI: metadata rows, badges, counters, keyboard hints and diagnostics tables. 2xs is 11px, - 3xs is 10px and the floor of the scale. Registered with tailwind-merge in + 3xs is 10px. Provider icon overlays use 4xs (8px) and 5xs (7px) to fit + their smaller badges. Registered with tailwind-merge in lib/utils.ts so cn() treats them as font sizes, not colours. */ --text-2xs: 0.6875rem; --text-2xs--line-height: calc(1 / 0.6875); --text-3xs: 0.625rem; --text-3xs--line-height: calc(0.875 / 0.625); + --text-4xs: 0.5rem; + --text-4xs--line-height: 1; + --text-5xs: 0.4375rem; + --text-5xs--line-height: 1; /* Motion. drawer is the decelerating curve for surfaces that slide or pop in (composer context, pills). */ --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1); diff --git a/apps/web/src/lib/runtime.ts b/apps/web/src/lib/runtime.ts index 866f38875c76..fd4f2b194096 100644 --- a/apps/web/src/lib/runtime.ts +++ b/apps/web/src/lib/runtime.ts @@ -19,7 +19,7 @@ function configuredRelayUrl(): string { const httpClientLayer = remoteHttpClientLayer((input, init) => globalThis.fetch(input, init)); const relayTracingLayer = makeRelayClientTracingLayer(resolveRelayTracingConfig(), { - serviceName: "t3-web-relay-client", + serviceName: "t3code-web", serviceVersion: import.meta.env.APP_VERSION, runtime: "browser", client: typeof window !== "undefined" && window.desktopBridge ? "desktop" : "web", diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts index 43d55682e12d..ca9a3a6340c2 100644 --- a/apps/web/src/lib/utils.ts +++ b/apps/web/src/lib/utils.ts @@ -6,7 +6,7 @@ import { DraftId } from "../composerDraftStore"; // The theme's extra font sizes (index.css). Unregistered, tailwind-merge reads // text-2xs as a colour and drops it next to text-muted-foreground. -const twMerge = extendTailwindMerge({ extend: { theme: { text: ["2xs", "3xs"] } } }); +const twMerge = extendTailwindMerge({ extend: { theme: { text: ["2xs", "3xs", "4xs", "5xs"] } } }); export function cn(...inputs: CxOptions) { return twMerge(cx(inputs)); diff --git a/apps/web/src/markdown-clipboard.test.ts b/apps/web/src/markdown-clipboard.test.ts index 9296967e4b82..a08dc299161b 100644 --- a/apps/web/src/markdown-clipboard.test.ts +++ b/apps/web/src/markdown-clipboard.test.ts @@ -19,6 +19,7 @@ class FakeText { class FakeElement { readonly nodeType = ELEMENT_NODE; + checked = false; readonly childNodes: Array = []; readonly classList = { contains: (name: string) => this.classNames.includes(name), @@ -61,18 +62,31 @@ class FakeElement { /** Supports only the selectors markdown-clipboard actually asks for. */ querySelector(selector: string): FakeElement | null { + if (selector.includes(", ")) { + for (const part of selector.split(", ")) { + const match = this.querySelector(part); + if (match) return match; + } + return null; + } const childOnly = selector.startsWith(":scope > "); - const target = childOnly ? selector.slice(":scope > ".length) : selector; + const [target, ...rest] = (childOnly ? selector.slice(":scope > ".length) : selector).split( + " > ", + ); const matches = (element: FakeElement): boolean => { if (target === 'input[type="checkbox"]') { return element.tagName === "INPUT" && element.getAttribute("type") === "checkbox"; } - return element.tagName === target.toUpperCase(); + return element.tagName === target?.toUpperCase(); }; const search = (parent: FakeElement): FakeElement | null => { for (const child of parent.childNodes) { if (!(child instanceof FakeElement)) continue; - if (matches(child)) return child; + if (matches(child)) { + if (rest.length === 0) return child; + const nested = child.querySelector(`:scope > ${rest.join(" > ")}`); + if (nested) return nested; + } if (!childOnly) { const nested = search(child); if (nested) return nested; @@ -143,6 +157,57 @@ describe("serializeRenderedMarkdownFragment", () => { expect(serializeRenderedMarkdownFragment(asNode(container))).toBe("run `git status` first"); }); + describe.each([ + { parentLayout: "tight", childLayout: "tight" }, + { parentLayout: "tight", childLayout: "loose" }, + { parentLayout: "loose", childLayout: "tight" }, + { parentLayout: "loose", childLayout: "loose" }, + ])("$parentLayout parent with $childLayout child", ({ parentLayout, childLayout }) => { + it.each([ + { parentChecked: null, childChecked: true, parent: "- Parent", child: " - [x] Child" }, + { + parentChecked: false, + childChecked: true, + parent: "- [ ] Parent", + child: " - [x] Child", + }, + { + parentChecked: true, + childChecked: false, + parent: "- [x] Parent", + child: " - [ ] Child", + }, + ])("copies $parent with $child", ({ parentChecked, childChecked, parent, child }) => { + const parentContent = parentLayout === "loose" ? new FakeElement("P") : new FakeElement("LI"); + if (parentChecked !== null) { + const checkbox = new FakeElement("INPUT", [], { type: "checkbox" }); + checkbox.checked = parentChecked; + parentContent.append(checkbox, new FakeText(" ")); + } + parentContent.append(new FakeText("Parent")); + const parentItem = + parentLayout === "loose" ? new FakeElement("LI").append(parentContent) : parentContent; + const checkbox = new FakeElement("INPUT", [], { type: "checkbox" }); + checkbox.checked = childChecked; + const childContent = new FakeElement(childLayout === "loose" ? "P" : "LI").append( + checkbox, + new FakeText(" Child"), + ); + const childItem = + childLayout === "loose" ? new FakeElement("LI").append(childContent) : childContent; + parentItem.append(new FakeText("\n"), new FakeElement("UL").append(childItem)); + const container = new FakeElement("DIV").append( + new FakeElement("P").append(new FakeText("Before")), + new FakeElement("UL").append(parentItem), + new FakeElement("P").append(new FakeText("After")), + ); + + expect(serializeRenderedMarkdownFragment(asNode(container))).toBe( + `Before\n\n${parent}${parentLayout === "loose" ? "\n\n" : "\n"}${child}\n\nAfter`, + ); + }); + }); + it("copies the complete quote, source, and comment instead of the comment-only chip label", () => { const citation = { version: 1 as const, diff --git a/apps/web/src/markdown-clipboard.ts b/apps/web/src/markdown-clipboard.ts index 4a96c8b31d13..39b941801638 100644 --- a/apps/web/src/markdown-clipboard.ts +++ b/apps/web/src/markdown-clipboard.ts @@ -119,7 +119,9 @@ function serializeTable(table: Element): string { } function serializeListItem(item: Element, ordered: boolean, index: number): string { - const checkbox = item.querySelector('input[type="checkbox"]'); + const checkbox = item.querySelector( + ':scope > input[type="checkbox"], :scope > p > input[type="checkbox"]', + ); const task = checkbox ? `[${(checkbox as HTMLInputElement).checked ? "x" : " "}] ` : ""; const marker = ordered ? `${index}. ${task}` : `- ${task}`; let content = serializeChildren(item) diff --git a/apps/web/src/observability/clientTracing.ts b/apps/web/src/observability/clientTracing.ts index 2471de958615..e8ccd4173d35 100644 --- a/apps/web/src/observability/clientTracing.ts +++ b/apps/web/src/observability/clientTracing.ts @@ -15,8 +15,9 @@ import { APP_VERSION } from "~/branding"; const DEFAULT_EXPORT_INTERVAL_MS = 1_000; const CLIENT_TRACING_RESOURCE = { - serviceName: "t3-web", + serviceName: "t3code-web", attributes: { + "service.namespace": "t3code", "service.runtime": "t3-web", "service.mode": isElectron ? "electron" : "browser", "service.version": APP_VERSION, diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx index eee703ac3f47..8480dc70c717 100644 --- a/apps/web/src/routes/__root.tsx +++ b/apps/web/src/routes/__root.tsx @@ -26,6 +26,7 @@ import { RelayClientInstallDialog } from "../components/cloud/RelayClientInstall import { SshPasswordPromptDialog } from "../components/desktop/SshPasswordPromptDialog"; import { SnapShotCoordinator } from "../components/desktop/SnapShotCoordinator"; import { DesktopAppActivationCoordinator } from "../components/desktop/DesktopAppActivationCoordinator"; +import { RunningThreadKeepAlive } from "../components/desktop/RunningThreadKeepAlive"; import { ProviderUpdateLaunchNotification } from "../components/ProviderUpdateLaunchNotification"; import { ThreadNotificationCoordinator } from "../components/ThreadNotificationCoordinator"; import { ProjectCloneToastCoordinator } from "../components/ProjectCloneToastCoordinator"; @@ -43,6 +44,7 @@ import { toastManager, } from "../components/ui/toast"; import { resolveAndPersistPreferredEditor } from "../editorPreferences"; +import { isElectron } from "../env"; import { applyAppearanceFontVariables } from "~/appearanceFonts"; import { applyAppearanceContrast } from "~/appearanceContrast"; import { useClientSettings } from "../hooks/useSettings"; @@ -219,6 +221,7 @@ function RootRouteView() { > {primaryEnvironmentAuthenticated ? : null} {primaryEnvironmentAuthenticated ? : null} + {isElectron ? : null} diff --git a/apps/web/src/routes/settings.tsx b/apps/web/src/routes/settings.tsx index 5fc6d63f20f6..d529148e17b9 100644 --- a/apps/web/src/routes/settings.tsx +++ b/apps/web/src/routes/settings.tsx @@ -1,18 +1,12 @@ -import { - Outlet, - createFileRoute, - redirect, - useCanGoBack, - useLocation, - useNavigate, -} from "@tanstack/react-router"; -import { useCallback, useEffect, useState, type ReactNode } from "react"; +import { Outlet, createFileRoute, redirect, useLocation } from "@tanstack/react-router"; +import { useEffect, useState, type ReactNode } from "react"; import { RotateCcwIcon } from "lucide-react"; import { Button } from "../components/ui/button"; import { useSettingsRestore } from "../components/settings/SettingsPanels"; import { SettingsBreadcrumb } from "../components/settings/SettingsBreadcrumb"; import { SidebarInset } from "../components/ui/sidebar"; +import { useNavigateToMainApp } from "../components/sidebar/mainAppLocation"; import { WorkspacePageHeader } from "../components/WorkspacePageHeader"; import { isElectron } from "../env"; import { @@ -117,17 +111,9 @@ function SettingsScopeBoundary({ pathname, children }: { pathname: string; child function SettingsContentLayout() { const location = useLocation(); - const navigate = useNavigate(); - const canGoBack = useCanGoBack(); + const navigateToMainApp = useNavigateToMainApp(); const { search } = useSettingsScope(); const [restoreSignal, setRestoreSignal] = useState(0); - const navigateBackWithinApp = useCallback(() => { - if (canGoBack) { - window.history.back(); - return; - } - void navigate({ to: "/" }); - }, [canGoBack, navigate]); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { @@ -140,7 +126,7 @@ function SettingsContentLayout() { activeElement.blur(); } - navigateBackWithinApp(); + void navigateToMainApp(); } }; @@ -148,7 +134,7 @@ function SettingsContentLayout() { return () => { window.removeEventListener("keydown", onKeyDown); }; - }, [navigateBackWithinApp]); + }, [navigateToMainApp]); return ( diff --git a/apps/web/src/state/entities.ts b/apps/web/src/state/entities.ts index d9610e20717f..dcd6358cb52c 100644 --- a/apps/web/src/state/entities.ts +++ b/apps/web/src/state/entities.ts @@ -229,6 +229,15 @@ export function readEnvironmentSupportsPinReorder(environmentId: EnvironmentId): ); } +/** Whether the environment's server understands thread.auto-settle.set. + Same version-skew contract as settlement. */ +export function readEnvironmentSupportsAutoSettleOptOut(environmentId: EnvironmentId): boolean { + return ( + appAtomRegistry.get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities + .threadAutoSettleOptOut === true + ); +} + export function readEnvironmentSupportsActiveReorder(environmentId: EnvironmentId): boolean { return ( appAtomRegistry.get(environmentServerConfigsAtom).get(environmentId)?.environment.capabilities diff --git a/apps/web/src/state/threads.test.ts b/apps/web/src/state/threads.test.ts new file mode 100644 index 000000000000..cf1fc8777283 --- /dev/null +++ b/apps/web/src/state/threads.test.ts @@ -0,0 +1,200 @@ +import { + EMPTY_ENVIRONMENT_THREAD_STATE, + type EnvironmentThreadState, +} from "@t3tools/client-runtime/state/threads"; +import { + EnvironmentId, + ProjectId, + ProviderInstanceId, + ThreadId, + type OrchestrationSessionStatus, + type OrchestrationThread, + type OrchestrationThreadShell, +} from "@t3tools/contracts"; +import * as Option from "effect/Option"; +import { AsyncResult, Atom, AtomRegistry } from "effect/unstable/reactivity"; +import { describe, expect, it } from "vite-plus/test"; + +import { createRunningThreadKeepAliveAtom } from "./threads"; + +const LOCAL = EnvironmentId.make("local"); +const REMOTE = EnvironmentId.make("remote"); + +function session(threadId: ThreadId, status: OrchestrationSessionStatus) { + return { + threadId, + status, + providerName: "codex", + runtimeMode: "full-access", + activeTurnId: null, + lastError: null, + updatedAt: "2026-09-24T00:00:00.000Z", + } satisfies OrchestrationThread["session"]; +} + +function shell(id: string, status: OrchestrationSessionStatus | null) { + const threadId = ThreadId.make(id); + return { + id: threadId, + session: status === null ? null : session(threadId, status), + } satisfies Pick; +} + +function detail( + id: string, + status: OrchestrationSessionStatus, + overrides: Partial = {}, +) { + const threadId = ThreadId.make(id); + const thread: OrchestrationThread = { + id: threadId, + projectId: ProjectId.make("project"), + title: id, + modelSelection: { instanceId: ProviderInstanceId.make("codex"), model: "gpt-5.4" }, + runtimeMode: "full-access", + interactionMode: "default", + branch: null, + worktreePath: null, + latestTurn: null, + createdAt: "2026-09-24T00:00:00.000Z", + updatedAt: "2026-09-24T00:00:00.000Z", + archivedAt: null, + settledOverride: null, + settledAt: null, + pullRequests: [], + deletedAt: null, + messages: [], + proposedPlans: [], + activities: [], + checkpoints: [], + session: session(threadId, status), + }; + return AsyncResult.success({ + ...EMPTY_ENVIRONMENT_THREAD_STATE, + status: "live", + data: Option.some(thread), + ...overrides, + }); +} + +function makeHarness() { + // Registry cleanup runs only on `flush`, like the real deferred task. + const tasks: Array<() => void> = []; + const registry = AtomRegistry.make({ + scheduleTask: (task) => { + tasks.push(task); + return () => {}; + }, + }); + const flush = () => { + for (let task = tasks.shift(); task !== undefined; task = tasks.shift()) task(); + }; + const environmentIds = Atom.make>([LOCAL, REMOTE]).pipe( + Atom.keepAlive, + ); + const threads = Atom.family((_environmentId: EnvironmentId) => + Atom.make>>([]).pipe(Atom.keepAlive), + ); + // Stand-ins for the thread state atoms. Each one lives only while mounted, + // as the real stream does. + const keys = new Set(); + const states = Atom.family((_key: string) => + Atom.make>( + AsyncResult.success(EMPTY_ENVIRONMENT_THREAD_STATE), + ), + ); + const stateAtom = (environmentId: EnvironmentId, threadId: string) => { + const key = `${environmentId}:${threadId}`; + keys.add(key); + return states(key); + }; + const keepAlive = createRunningThreadKeepAliveAtom({ + environmentIdsAtom: environmentIds, + threadsAtom: threads, + stateAtom, + }); + registry.mount(keepAlive); + return { + registry, + environmentIds, + threads, + stateAtom, + keepAlive, + openStreams: () => { + flush(); + return [...keys].filter((key) => registry.getNodes().has(states(key))).toSorted(); + }, + }; +} + +describe("createRunningThreadKeepAliveAtom", () => { + it("keeps running threads open across shell updates and thread view visits", () => { + const h = makeHarness(); + h.registry.set(h.threads(LOCAL), [ + shell("a", "running"), + shell("b", "ready"), + shell("c", null), + ]); + h.registry.set(h.threads(REMOTE), [shell("d", "starting")]); + expect(h.openStreams()).toEqual(["local:a", "remote:d"]); + + // A thread view that comes and goes shares the kept stream. + const live = detail("a", "running"); + h.registry.set(h.stateAtom(LOCAL, "a"), live); + h.registry.mount(h.stateAtom(LOCAL, "a"))(); + + // A shell update that starts or stops nothing does not rebuild the set. + const kept = h.registry.get(h.keepAlive); + h.registry.set(h.threads(LOCAL), [shell("a", "running"), shell("b", "ready")]); + expect(h.registry.get(h.keepAlive)).toBe(kept); + expect(h.openStreams()).toEqual(["local:a", "remote:d"]); + expect(h.registry.get(h.stateAtom(LOCAL, "a"))).toBe(live); + }); + + it("holds a stopped thread until its own stream is live and shows the stop", () => { + const h = makeHarness(); + h.registry.set(h.threads(LOCAL), [ + shell("a", "running"), + shell("b", "running"), + shell("c", "running"), + ]); + // "b" has not loaded yet. "c" hit a stream error. + h.registry.set(h.stateAtom(LOCAL, "a"), detail("a", "running")); + h.registry.set( + h.stateAtom(LOCAL, "c"), + detail("c", "running", { status: "cached", error: Option.some("Could not sync.") }), + ); + + // The shell reports the stops first. A failed stream cannot deliver its + // stop, so only it is released now. + h.registry.set(h.threads(LOCAL), [ + shell("a", "ready"), + shell("b", "ready"), + shell("c", "ready"), + ]); + expect(h.openStreams()).toEqual(["local:a", "local:b"]); + + h.registry.set(h.stateAtom(LOCAL, "a"), detail("a", "ready")); + h.registry.set(h.stateAtom(LOCAL, "b"), detail("b", "ready", { status: "synchronizing" })); + expect(h.openStreams()).toEqual(["local:b"]); + h.registry.set(h.stateAtom(LOCAL, "b"), detail("b", "ready")); + expect(h.openStreams()).toEqual([]); + }); + + it("follows environments that connect and go away", () => { + const h = makeHarness(); + h.registry.set(h.environmentIds, [LOCAL]); + h.registry.set(h.threads(REMOTE), [shell("d", "running")]); + expect(h.openStreams()).toEqual([]); + + h.registry.set(h.environmentIds, [LOCAL, REMOTE]); + expect(h.openStreams()).toEqual(["remote:d"]); + + // Removal drops every mount, including one still waiting for its stop. + h.registry.set(h.stateAtom(REMOTE, "d"), detail("d", "running")); + h.registry.set(h.threads(REMOTE), [shell("d", "ready")]); + expect(h.openStreams()).toEqual(["remote:d"]); + h.registry.set(h.environmentIds, [LOCAL]); + expect(h.openStreams()).toEqual([]); + }); +}); diff --git a/apps/web/src/state/threads.ts b/apps/web/src/state/threads.ts index deda3ca29e9a..31f8fea47b3f 100644 --- a/apps/web/src/state/threads.ts +++ b/apps/web/src/state/threads.ts @@ -1,4 +1,6 @@ import { useAtomValue } from "@effect/atom-react"; +import { enabledEnvironmentIds } from "@t3tools/client-runtime/state/connections"; +import { arrayElementsEqual } from "@t3tools/client-runtime/state/entities"; import { createEnvironmentThreadDetailAtoms, createEnvironmentThreadShellAtoms, @@ -6,8 +8,9 @@ import { EMPTY_ENVIRONMENT_THREAD_STATE, type EnvironmentThreadState, createThreadEnvironmentAtoms, + isThreadSessionRunning, } from "@t3tools/client-runtime/state/threads"; -import type { EnvironmentId, ThreadId } from "@t3tools/contracts"; +import type { EnvironmentId, OrchestrationThreadShell, ThreadId } from "@t3tools/contracts"; import * as Option from "effect/Option"; import { AsyncResult, Atom } from "effect/unstable/reactivity"; @@ -46,3 +49,84 @@ export function useEnvironmentThread( () => EMPTY_ENVIRONMENT_THREAD_STATE, ) as EnvironmentThreadState; } + +type KeptThreads = ReadonlyMap>; + +// True once a thread's own stream no longer needs to stay open: it is in sync +// and shows a settled session, or it cannot progress (deleted or failed). A +// stream that is still loading or reconnecting keeps waiting for the stop. +function isDetailDone(result: AsyncResult.AsyncResult): boolean { + if (!AsyncResult.isSuccess(result)) return true; + const { status, data, error } = result.value; + if (status === "deleted" || Option.isSome(error)) return true; + return ( + status === "live" && !Option.exists(data, (thread) => isThreadSessionRunning(thread.session)) + ); +} + +/** + * Keeps the thread state atom mounted for each running thread in the listed + * environments. Mount the result; its value is only bookkeeping. + * + * The shell and detail streams are independent, so the shell can report a + * stop before the detail loads or catches up. A stopped thread stays mounted + * until its own detail is live and shows the stop too. Then the stream closes + * and saves the settled state to disk. + */ +export function createRunningThreadKeepAliveAtom(input: { + readonly environmentIdsAtom: Atom.Atom>; + readonly threadsAtom: ( + environmentId: EnvironmentId, + ) => Atom.Atom>>; + readonly stateAtom: ( + environmentId: EnvironmentId, + threadId: ThreadId, + ) => Atom.Atom>; +}) { + // Keeps its identity until a thread starts or stops, so ordinary shell + // updates do not rebuild the keep-alive set. + const runningThreadIdsAtom = Atom.family((environmentId: EnvironmentId) => { + let previous: ReadonlyArray = []; + return Atom.make((get) => { + const running = get(input.threadsAtom(environmentId)).flatMap((thread) => + isThreadSessionRunning(thread.session) ? [thread.id] : [], + ); + if (arrayElementsEqual(previous, running)) return previous; + previous = running; + return running; + }).pipe(Atom.withLabel(`web-running-thread-ids:${environmentId}`)); + }); + + return Atom.make((get): KeptThreads => { + const previous = Option.getOrUndefined(get.self()); + const kept = new Map>(); + // An environment that leaves the list is not visited, so its mounts drop. + for (const environmentId of get(input.environmentIdsAtom)) { + const threadIds = new Set(get(runningThreadIdsAtom(environmentId))); + for (const threadId of previous?.get(environmentId) ?? []) { + if (threadIds.has(threadId)) continue; + const stateAtom = input.stateAtom(environmentId, threadId); + // `once`, not `get`: a dependency on a stopped thread would hold its + // stream open until some other change rebuilds this atom. + if (isDetailDone(get.once(stateAtom))) continue; + threadIds.add(threadId); + // Rebuild when this detail is done, not on each update. + get.subscribe(stateAtom, (state) => { + if (isDetailDone(state)) get.refreshSelf(); + }); + } + for (const threadId of threadIds) get.mount(input.stateAtom(environmentId, threadId)); + kept.set(environmentId, threadIds); + } + return kept; + }).pipe(Atom.withLabel("web-running-thread-keep-alive")); +} + +/** Mounted by `RunningThreadKeepAlive` on desktop, for every enabled environment. */ +export const runningThreadKeepAliveAtom = createRunningThreadKeepAliveAtom({ + environmentIdsAtom: Atom.map(environmentCatalog.catalogValueAtom, (catalog) => [ + ...enabledEnvironmentIds(catalog), + ]), + threadsAtom: environmentThreadShells.environmentThreadsAtom, + stateAtom: environmentThreads.stateAtom, +}); diff --git a/docs/fork/0018-the-standard-otel-variables-are-honored.md b/docs/fork/0018-the-standard-otel-variables-are-honored.md index 101404ef4d50..ae48d3c307de 100644 --- a/docs/fork/0018-the-standard-otel-variables-are-honored.md +++ b/docs/fork/0018-the-standard-otel-variables-are-honored.md @@ -15,8 +15,9 @@ - Tell your instances apart. `OTEL_SERVICE_VERSION` and `OTEL_RESOURCE_ATTRIBUTES` are attached to every span, metric, and log record, so T3 Code sits in the same dashboards as everything else. Service - names themselves are static, and `OTEL_SERVICE_NAME` is refused with a - warning; see 0023. + names themselves are static, and `OTEL_SERVICE_NAME` or a `service.name` in + `OTEL_RESOURCE_ATTRIBUTES` is refused with a startup warning instead of being + dropped in silence. - Configure each signal on its own, logs included. A signal with its own address, wire format, or credentials is honored without disturbing the other two, `OTEL_LOGS_EXPORTER=none` stops log export while leaving spans and @@ -71,9 +72,12 @@ on. ## Upstream considerations Nothing here is fork-specific and it belongs upstream. Upstream has taken the -kill switch: `T3CODE_OTEL_SDK_DISABLED` and `OTEL_SDK_DISABLED` now stop export -there too, read in the same order, so the sync keeps upstream's reading and the -fork carries everything else on this page. The riskiest part for +kill switch, the standard endpoint, header, and protocol variables, resource +attributes, and static service names. Its reader is a subset of this one, so the +sync keeps this reader and the fork carries the rest of this page: exporter +selection, the batching and temporality knobs, `OTEL_SERVICE_VERSION`, the +refusal warning for a service name, and a bad value costing that value rather +than the whole signal. The riskiest part for them is the same part that makes it useful: an ambient endpoint starts an export that includes thread ids, turn ids, and workspace paths, and upstream may prefer an explicit opt-in for a product with this many users. diff --git a/docs/fork/0022-the-desktop-app-reports-its-own-work.md b/docs/fork/0022-the-desktop-app-reports-its-own-work.md index 5a74f817f3d1..a2721da663ea 100644 --- a/docs/fork/0022-the-desktop-app-reports-its-own-work.md +++ b/docs/fork/0022-the-desktop-app-reports-its-own-work.md @@ -7,7 +7,7 @@ - See what the desktop app itself is doing. App startup, window and menu work, backend supervision, and updates now reach your collector as traces and logs - under the service name `t3-desktop`, alongside the server work they cause. + under the service name `t3code-desktop`, alongside the server work they cause. - Configure it the way you configure everything else. The Electron main process reads the same `OTEL_*` endpoint variables as the server, in the same order, so a machine that points one of them at a collector points both. It had a @@ -20,11 +20,10 @@ payload every interval. - Turn it off the same way. `OTEL_SDK_DISABLED=true` stops both processes, and so does `T3CODE_OTEL_SDK_DISABLED=true`, which is read first. -- Tell the two apart without trusting the environment. The main process reports - as `t3-desktop`, joining `t3-server` and `t3-web`, and `service.runtime` on it - is always `desktop`, so an ambient +- Tell the two apart without trusting the environment. `service.runtime` on + the main process is always `desktop`, so an ambient `OTEL_RESOURCE_ATTRIBUTES=service.runtime=t3-server` cannot make it file its - work under the server's name. See 0023 for why names are static. + work under the server's name. ## Why @@ -50,8 +49,8 @@ into a shared package instead of being copied. Upstream taking 0018 gets this almost for free. Upstream has since shipped its own desktop exporter and the kill switch for it, -so what remains here is the rest of the environment reading, the static service name, and the per-signal protocol, -headers, and batching. Upstream's version settled two questions this one had +so what remains here is the rest of the environment reading from 0018, applied to +the main process. Upstream's version settled two questions this one had answered differently, and both of its answers were adopted: metrics stay off until a desktop metric exists, and the OTLP log exporter replaces `Logger.tracerLogger` instead of joining it, which is what the fork's own diff --git a/docs/fork/0023-a-service-name-is-not-an-environment-variable.md b/docs/fork/0023-a-service-name-is-not-an-environment-variable.md deleted file mode 100644 index c767e5d8c3e0..000000000000 --- a/docs/fork/0023-a-service-name-is-not-an-environment-variable.md +++ /dev/null @@ -1,58 +0,0 @@ -# 0023: A service name is not an environment variable - -- PR: [TrogonStack/t3code#36](https://github.com/TrogonStack/t3code/pull/36) -- Status: active - -## What you can do now - -- Trust what a service name means. T3 Code reports as `t3-server`, `t3-desktop`, - and `t3-web`, always, and nothing in the environment can change that. A - dashboard built on one of those names keeps meaning what it meant when you - built it. -- Set `OTEL_SERVICE_NAME` on a machine without consequence. It is refused, and - the refusal is named in the startup log rather than applied quietly or - dropped quietly. -- Keep telling your instances apart. `OTEL_RESOURCE_ATTRIBUTES` still works and - is the right lever for it, including `service.instance.id`, - `deployment.environment`, and `host.name`. -- Rename the server on purpose if you need to. `T3CODE_OTLP_SERVICE_NAME` is - still honored, because it is T3 Code's own variable and nobody exports it - fleet-wide by accident. - -## Why - -This reverses part of 0018, which honored `OTEL_SERVICE_NAME` because every -other OpenTelemetry SDK does. Consistency was the wrong thing to optimize for -here. - -A service name is not a preference, it is a key. Endpoints, headers, protocols, -and resource attributes are all things a machine legitimately knows better than -the app does, which is why reading them from the environment is right. A service -name is the opposite: it is the identity every dashboard, alert, and saved query -is keyed on, and it is worth exactly as much as it is stable. One ambient -variable, exported years ago in a shell profile for a different app, silently -merges T3 Code into that app's dashboards and pulls it out of its own. - -The failure mode is what makes it worth diverging over. It is invisible from -inside the app, the telemetry keeps flowing, and every panel still renders. What -you get is not an empty dashboard, which someone would investigate, but a -plausible one that is quietly describing two applications at once. - -Worth being honest that the specification does not settle this. It fixes the -precedence between `OTEL_SERVICE_NAME` and a `service.name` in -`OTEL_RESOURCE_ATTRIBUTES`, and then leaves environment-versus-code to whichever -order an SDK happens to merge its resources in. Most SDKs let hardcoded values -win, which is the same answer this reaches. The difference is that they reach it -silently and this says so out loud. - -## Upstream considerations - -The server half of this is not a divergence at all: it restores upstream's -behavior, which was static `t3-server` with `T3CODE_OTLP_SERVICE_NAME` as the -only override. 0018 is what moved away from that, and this moves back. Upstream -taking 0018 should take this with it. - -The rebase burden is one line per process plus the absence of a field. The -environment reader has no `serviceName` in its resource type at all, so a sync -that reintroduces the variable has to add the field back before it can be wired -anywhere, rather than silently succeeding. diff --git a/docs/fork/README.md b/docs/fork/README.md index c288f2a2d6bd..a69459e341ed 100644 --- a/docs/fork/README.md +++ b/docs/fork/README.md @@ -55,8 +55,6 @@ Each entry uses these sections: active, [#32](https://github.com/TrogonStack/t3code/pull/32) - **0022** [The desktop app reports its own work](./0022-the-desktop-app-reports-its-own-work.md) active, [#35](https://github.com/TrogonStack/t3code/pull/35) -- **0023** [A service name is not an environment variable](./0023-a-service-name-is-not-an-environment-variable.md) - active, [#36](https://github.com/TrogonStack/t3code/pull/36) - **0024** [A refused merge says why, and an administrator can merge anyway](./0024-a-refused-merge-says-why.md) active, [#38](https://github.com/TrogonStack/t3code/pull/38) - **0025** [A test run leaves no processes behind](./0025-a-test-run-leaves-no-processes-behind.md) diff --git a/docs/internals/connection-runtime.md b/docs/internals/connection-runtime.md index 797ff8fe31fe..301e1e0ffecb 100644 --- a/docs/internals/connection-runtime.md +++ b/docs/internals/connection-runtime.md @@ -61,6 +61,15 @@ stream, which stops when the last consumer unmounts; hidden mounted routes still count. A registry-local cache retains state and its replay cursor for five idle minutes so back navigation can resume without another snapshot download. +The desktop app adds one consumer: a +[keep-alive](../../apps/web/src/state/threads.ts) mounts every thread whose +session is starting or running, in each enabled environment. Opening a running +thread then needs no replay. The shell and detail streams are independent, so +the shell can report a stop before the detail loads or catches up. A stopped +thread stays mounted until its own stream is live and shows the stop, and the +stream then closes and saves the settled state. +Web and mobile do not keep threads alive. + Retain state and cursor together only after an update finishes. Cancellation must not advance the cached cursor beyond the applied data, and an old scope must not overwrite its successor's cache. Preserve pagination data on reuse, but clear diff --git a/docs/internals/providers.md b/docs/internals/providers.md index 489d82a01d6a..e9c58f9295a0 100644 --- a/docs/internals/providers.md +++ b/docs/internals/providers.md @@ -64,7 +64,9 @@ it. Homebrew and npm are proven by the real path (symlinks followed): a versione `brew --prefix`, or `/lib/node_modules//` (Windows: the shim beside `node_modules`). Native installer layouts and the global bin directories of pnpm, Bun, and Vite+ may match on either the resolved path or its real target, since those installers place real files or their own symlinks -there. Anything unproven stays manual-only but still reports the version gap. npm updates pin +there. Cursor and Grok are the exception: their only updater is the CLI itself, which detects its +own installer, so any resolved executable runs ` update`. Anything unproven stays +manual-only but still reports the version gap. npm updates pin `--prefix` because the `npm` on `PATH` can belong to a different Node than the one that owns the provider. Homebrew compares against `brew info` since casks trail npm by hours; native installs share npm's version diff --git a/docs/internals/t3-connect.md b/docs/internals/t3-connect.md index d0329295f4b4..367d9116e92e 100644 --- a/docs/internals/t3-connect.md +++ b/docs/internals/t3-connect.md @@ -65,6 +65,47 @@ teardown, because a database failure must leave the active link usable. Failed teardown retains enough state to retry. See the [managed endpoint lifecycle](../../infra/relay/src/environments/ManagedEndpointProvider.ts). +## Idle tunnels are reclaimed and recovered + +Cloudflare bills a tunnel whether or not a connector is attached, so a laptop +that sleeps with a linked environment leaves a paid tunnel behind. The relay's +five-minute maintenance job can reclaim those tunnels. `RELAY_TUNNEL_CLEANUP_MODE` +selects `off`, `dry-run`, or `enabled`, with `off` as the default. The mode is +read at deploy time, so changing it means a relay deploy, not a variable flip. +A candidate is a same-stage tunnel that Cloudflare reports down for at least +five minutes, or one that never connected and is at least an hour old. The +longer grace for never-connected tunnels covers a pairing still in progress. + +Cleanup deletes only tunnels whose host has registered recovery. Allocations +without recovery registration belong to hosts that cannot replace a deleted +tunnel and are left alone. Allocations with no recorded tunnel ID, or a +different tunnel ID, are skipped because a provision may own them. A tunnel with +no allocation row at all is counted as `skippedOrphan` and never deleted: there +is no row to lock, so a relink that adopts it by name could race the delete. +Clear those by hand. Each sweep is bounded: at most ten list requests, 100 deletions, a +two-minute deadline, and an early stop on a Cloudflare rate limit. Each sweep +starts one budget further along the candidate list, so a block of deletes that +keep failing cannot starve the tunnels listed after them. See the +[reaper](../../infra/relay/src/environments/ManagedEndpointReaper.ts). + +A host registers recovery at startup by sending its tunnel ID and loopback +origin with a short-lived signature from the environment key. Registration +touches Cloudflare only when the local host or port changed, and once per +existing allocation on the first registration after the upgrade because the +stored origin is empty. First registrations are jittered so an auto-update wave +does not hit the relay at once. The host stores a confirmed-origin marker with +the connector config, and a later boot starts the connector before registration +only when that marker matches the current config and port. If registration +cannot reach the relay for ten minutes, the host starts its stored config anyway +and keeps registering in the background until it can reconcile the origin. +If the connector exits, or `cloudflared` reports repeated tunnel +rejections, the host asks the relay for a replacement, at most once every two +minutes. The relay +provisions under the same allocation, so the hostname and DNS record survive +and clients keep their bindings. Every mutation on an allocation bumps its +`generation`, and deletion locks the row at the generation it claimed, so a +host that reconnects mid-sweep wins. + ## OAuth traps Interactive clients and the headless CLI use the same Clerk application but diff --git a/docs/operations/development.md b/docs/operations/development.md index 1cbf09c16008..b7e1b257d0b2 100644 --- a/docs/operations/development.md +++ b/docs/operations/development.md @@ -76,8 +76,7 @@ Put that value in the main checkout's gitignored `.env`: T3CODE_DEV_AUTH_TOKEN= ``` -The `t3.json` Setup Worktree commands on Unix and Windows link that file to each worktree's -`.env`. The dev runner reads repository env files at startup. `.env.local` and inherited process +The `t3.json` Setup Worktree action links that file to each worktree's `.env`. The dev runner reads repository env files at startup. `.env.local` and inherited process environment values override `.env`, so no per-worktree export is needed after setup. For a manual worktree or launcher without that link, export the same fixed value instead: diff --git a/docs/operations/mobile-app-store-screenshots.md b/docs/operations/mobile-app-store-screenshots.md index 5d6fff051550..0078754ad9c2 100644 --- a/docs/operations/mobile-app-store-screenshots.md +++ b/docs/operations/mobile-app-store-screenshots.md @@ -63,14 +63,14 @@ multiplies the run by six; only the native build is shared. The default matrix is: -| Output folder | Capture target | Upload dimensions | Store slot | -| ------------------------------------- | ------------------------- | ----------------- | ----------------------------------------- | -| `apple/iphone-6.9/dark/t3-code/` | iPhone 17 Pro Max | 1320×2868 | App Store Connect iPhone 6.9-inch | -| `apple/iphone-6.5/dark/t3-code/` | disposable iPhone 14 Plus | 1284×2778 | App Store Connect iPhone 6.5-inch | -| `apple/ipad-13/dark/t3-code/` | iPad Pro 13-inch (M5) | 2752×2064 | App Store Connect iPad 13-inch, landscape | -| `google-play/phone/dark/t3-code/` | Pixel AVD at 420 dpi | 1080×1920 | Google Play phone, portrait 9:16 | -| `google-play/tablet-7/dark/t3-code/` | Pixel AVD at 600dp width | 1080×1920 | Google Play 7-inch tablet, portrait 9:16 | -| `google-play/tablet-10/dark/t3-code/` | Pixel AVD at 800dp width | 1440×2560 | Google Play 10-inch tablet, portrait 9:16 | +| Output folder | Capture target | Upload dimensions | Store slot | +| ------------------------------------- | ---------------------------- | ----------------- | ----------------------------------------- | +| `apple/iphone-6.9/dark/t3-code/` | disposable iPhone 17 Pro Max | 1320×2868 | App Store Connect iPhone 6.9-inch | +| `apple/iphone-6.5/dark/t3-code/` | disposable iPhone 14 Plus | 1284×2778 | App Store Connect iPhone 6.5-inch | +| `apple/ipad-13/dark/t3-code/` | iPad Pro 13-inch (M5) | 2752×2064 | App Store Connect iPad 13-inch, landscape | +| `google-play/phone/dark/t3-code/` | Pixel AVD at 420 dpi | 1080×1920 | Google Play phone, portrait 9:16 | +| `google-play/tablet-7/dark/t3-code/` | Pixel AVD at 600dp width | 1080×1920 | Google Play 7-inch tablet, portrait 9:16 | +| `google-play/tablet-10/dark/t3-code/` | Pixel AVD at 800dp width | 1440×2560 | Google Play 10-inch tablet, portrait 9:16 | Each target captures thread, terminal, review, thread list, and environments, and every target but the iPad also captures agent activity. Each palette folder's five or six screenshots satisfy the configured Apple limit of 1–10, Google diff --git a/docs/operations/observability.md b/docs/operations/observability.md index fd20ea4f0a3b..7993443b1bcf 100644 --- a/docs/operations/observability.md +++ b/docs/operations/observability.md @@ -133,7 +133,7 @@ Default Grafana login: export T3CODE_OTLP_TRACES_URL=http://localhost:4318/v1/traces export T3CODE_OTLP_METRICS_URL=http://localhost:4318/v1/metrics export T3CODE_OTLP_LOGS_URL=http://localhost:4318/v1/logs -export T3CODE_OTLP_SERVICE_NAME=t3-local +export OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=development ``` Optional: @@ -173,7 +173,6 @@ macOS app bundle example: T3CODE_OTLP_TRACES_URL=http://localhost:4318/v1/traces \ T3CODE_OTLP_METRICS_URL=http://localhost:4318/v1/metrics \ T3CODE_OTLP_LOGS_URL=http://localhost:4318/v1/logs \ -T3CODE_OTLP_SERVICE_NAME=t3-desktop \ "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/Applications/T3 Code.app/Contents/MacOS/T3 Code" ``` @@ -183,7 +182,6 @@ Direct binary example: T3CODE_OTLP_TRACES_URL=http://localhost:4318/v1/traces \ T3CODE_OTLP_METRICS_URL=http://localhost:4318/v1/metrics \ T3CODE_OTLP_LOGS_URL=http://localhost:4318/v1/logs \ -T3CODE_OTLP_SERVICE_NAME=t3-desktop \ ./path/to/your/desktop-app-binary ``` @@ -216,14 +214,14 @@ keeps T3 Code exporting. The desktop app is two processes, and each is its own OpenTelemetry producer: -- **The server**, under service name `t3-server`. -- **The Electron main process**, under service name `t3-desktop`. It owns app startup, window and +- **The server**, under service name `t3code-server`. +- **The Electron main process**, under service name `t3code-desktop`. It owns app startup, window and menu work, backend supervision, and updates, none of which the server can see. It reads the same sources in the same order as the server, so a machine that points one of them at a collector points both. -The web client reports as `t3-web`, so the three service names are `t3-server`, `t3-desktop`, and -`t3-web`. +The web client reports as `t3code-web`, so the three service names are `t3code-server`, +`t3code-desktop`, and `t3code-web`, all in `service.namespace` `t3code`. **Service names are static and the environment cannot change them.** `OTEL_SERVICE_NAME` and a `service.name` inside `OTEL_RESOURCE_ATTRIBUTES` are both refused, with a warning naming the one you @@ -236,8 +234,8 @@ T3 Code. Use `OTEL_RESOURCE_ATTRIBUTES` to tell instances apart, which is what i export OTEL_RESOURCE_ATTRIBUTES=service.instance.id=laptop-01,deployment.environment=lab ``` -`T3CODE_OTLP_SERVICE_NAME` still renames the server, because it is T3 Code's own variable and nobody -sets it across a fleet by accident. There is no equivalent for the main process. +A `service.namespace` in `OTEL_RESOURCE_ATTRIBUTES` is overridden the same way, since it is part of +the same identity. On macOS, ambient variables reach the desktop app only when it is launched from a shell. Opening it from the Dock, Finder, or Spotlight inherits `launchd`'s environment instead, which is why the @@ -535,11 +533,13 @@ Recommended flow in Grafana: 2. Pick the `Tempo` data source. 3. Set the time range to something recent like `Last 15 minutes`. 4. Start broad. Do not begin with a very narrow query. -5. Look for spans from your configured service name, then narrow by span name or attributes. +5. Look for spans from the `t3code-server` or `t3code-desktop` service, then narrow by span name or + attributes. Good first searches: -- service name such as `t3-local`, `t3-dev`, or `t3-desktop` +- service name `t3code-server` or `t3code-desktop`, plus a resource attribute such as + `deployment.environment.name` - span names like `sendTurn` or a Git operation such as `GitVcsDriver.statusDetails.status` - Git spans whose `git.operation` attribute identifies the operation - orchestration spans with attributes like `orchestration.command_type` @@ -752,9 +752,8 @@ window and menu handling, backend supervision, and updates. It resolves its endp `apps/desktop/src/app/DesktopOtlpExport.ts`, reading the same `T3CODE_OTLP_*` names and Settings entries as the backend it supervises, and the `OTEL_*` variables through the same `packages/shared/src/otelEnvironment.ts` the server uses, so neither process can disagree with the -other about what a variable means. It reports as service `t3-desktop`, which no variable can change -(see `docs/fork/0023-a-service-name-is-not-an-environment-variable.md`), so a collector shows it -alongside the backend rather than mixed into it. It exports traces and logs only; the main process +other about what a variable means. It reports as service `t3code-desktop`, which no variable can change, so a +collector shows it alongside the backend rather than mixed into it. It exports traces and logs only; the main process records no metrics, so the metrics endpoint applies to the backend alone. ### Env Vars @@ -774,8 +773,6 @@ OTLP export: - `T3CODE_OTLP_METRICS_URL`: OTLP metric endpoint - `T3CODE_OTLP_LOGS_URL`: OTLP log endpoint - `T3CODE_OTLP_EXPORT_INTERVAL_MS`: export interval, default `10000` -- `T3CODE_OTLP_SERVICE_NAME`: server service name, default `t3-server`. The Electron main process - does not read it and is always `t3-desktop`. - `T3CODE_OTLP_HEADERS`: extra headers for all three exporters, same format as `OTEL_EXPORTER_OTLP_HEADERS`: comma-separated `key=value` pairs with percent-encoded values. - `T3CODE_OTLP_PROTOCOL`: `http/json` (default) or `http/protobuf` diff --git a/docs/operations/release.md b/docs/operations/release.md index 880d36e60c57..4116ceeb388f 100644 --- a/docs/operations/release.md +++ b/docs/operations/release.md @@ -29,7 +29,7 @@ This document covers the unified release workflow for stable and nightly desktop - Builds six desktop artifacts in parallel for both channels, each as its own job (`desktop__`, one call of `release-desktop.yml`) on hardware of its own architecture, gated only on the bundle (the Windows jobs also wait for the same-arch Linux job, whose CLI archive they embed as the WSL runtime): - macOS `arm64` DMG - macOS `x64` DMG - - Linux `x64` and `arm64` AppImage + - Linux `x64` and `arm64` AppImage and `.deb`, from one electron-builder run. The `.deb` updates in the app through electron-updater, which installs it with `dpkg`. - Windows `x64` and `arm64` NSIS installer - Publishes one GitHub Release with all produced files. - Stable tags with a suffix after `X.Y.Z` (for example `1.2.3-alpha.1`) are published as GitHub prereleases. @@ -132,12 +132,19 @@ Required `production` environment variables: Optional `production` environment variables: - `RELAY_DOMAIN` when overriding the derived `relay.` domain +- `RELAY_TUNNEL_CLEANUP_MODE` with `off`, `dry-run`, or `enabled`. Missing and blank values use + `off`. Required `production` environment secrets: - `CLERK_SECRET_KEY` - `APNS_PRIVATE_KEY` +The relay Worker reads these variables and secrets when it is deployed. Alchemy does not redeploy the +Worker when only one of these values changes ([alchemy-run/alchemy#1831](https://github.com/alchemy-run/alchemy/issues/1831)), +so a push to `main` without relay code changes leaves the old value in place. After changing one, run +the **Deploy T3 Connect relay** workflow manually from `main` with **force** checked. + The account-scoped repository credentials are consumed by Alchemy while provisioning relay stages; they are not bound into the relay Worker. The production deployment uses an Axiom personal access token, so `AXIOM_ORG_ID` must accompany `AXIOM_TOKEN`. The `prod` stage owns the retained PlanetScale @@ -151,6 +158,57 @@ Developers deploy personal stages locally rather than through pull-request autom vp run --filter t3code-relay deploy -- --stage "$USER" --env-file .env.local ``` +### Managed tunnel cleanup rollout + +Keep `RELAY_TUNNEL_CLEANUP_MODE=off` for the first production deploy. That deploy applies the +nullable allocation migration and adds the recovery endpoints. Web and mobile clients need no +coordinated release. CLI and desktop server builds must reach users before cleanup is enabled, +because those builds register recovery and replace a deleted tunnel after wake. + +1. Deploy the relay and migration with cleanup `off`. +2. Release the server build and confirm current hosts register recovery. Older hosts stay marked + legacy and are never candidates. +3. Set `dry-run`, run a forced relay deploy, and read the sweep counters (`scanned`, `wouldDelete`, + `skippedLegacy`, `skippedOrphan`, `failed`, `truncated`) across several sweeps. Each sweep records + them, and the active `mode`, as `relay.managed_endpoint_reaper.*` attributes on its + `relay.managed_endpoint_reaper.sweep` span in Axiom. +4. Run the disposable-host canary below. +5. Set `enabled` only after the canary recovers without a server restart. + +The job runs every five minutes with a five-minute grace period for tunnels that lost their +connector, so a candidate is usually removed five to ten minutes after it goes down. Tunnels that +never connected wait an hour. One sweep attempts at most 100 deletions, so a backlog takes longer. +Changing `RELAY_TUNNEL_CLEANUP_MODE`, including turning cleanup off during an incident, needs a forced +relay deploy. Confirm the new `mode` on the next sweep span. + +To roll back, set cleanup to `off` and run a forced relay deploy before downgrading any host. Keep the +recovery endpoints deployed while current server builds are in use. The nullable columns can stay. + +### Disposable-host canary + +This test has not been run against a real Cloudflare account. Run it against a disposable relay +stage, test Cloudflare account, disposable host, and disposable T3 home. Keep production cleanup at +`off` or `dry-run` until it passes. Do not stop a daily-use T3 server. + +1. Deploy the disposable stage with cleanup `dry-run`. Link a first disposable environment through + web or mobile settings and confirm its tunnel is healthy and recovery is registered. +2. Stop that host and restart the same T3 home on a different local port. Confirm the public + hostname reaches the new port and sends nothing to the old one. +3. Link a second disposable environment with a server build that predates recovery registration. + Capture its managed `cloudflared` child PID, confirm it belongs to that host, and pause only that + child with `kill -STOP `. Wait until Cloudflare reports it down for over five minutes. +4. Capture the first environment's `cloudflared` child PID from its server logs, confirm ownership, + and pause it with `kill -STOP `. Wait until Cloudflare reports it down for over five + minutes. +5. Confirm dry-run counts the first tunnel in `wouldDelete` and the second in `skippedLegacy`. +6. Set cleanup `enabled` on the disposable stage and deploy it with `--force`. Confirm in the test + Cloudflare account that the first tunnel is deleted and the legacy tunnel still exists. +7. Resume the first child with `kill -CONT `. Confirm the running server detects the + repeated rejection, requests recovery, and becomes reachable at the same hostname without a + restart. +8. Resume the legacy child with `kill -CONT ` and confirm its tunnel reconnects. +9. Repeat with a physical sleep and wake cycle on a disposable laptop before broad rollout. + ## Marketing site deployment After a nightly release is published, the release workflow deploys the same commit @@ -271,7 +329,7 @@ available. - `T3CODE_DESKTOP_UPDATE_REPOSITORY` (format `owner/repo`), if set. - otherwise `GITHUB_REPOSITORY` from GitHub Actions. - Required release assets for updater: - - platform installers (`.exe`, `.dmg`, `.AppImage`, plus macOS `.zip` for Squirrel.Mac update payloads) + - platform installers (`.exe`, `.dmg`, `.AppImage`, `.deb`, plus macOS `.zip` for Squirrel.Mac update payloads) - channel metadata: `latest*.yml` for stable releases, `nightly*.yml` for nightly releases - `*.blockmap` files (used for differential downloads) - macOS metadata note: diff --git a/docs/user/devices.md b/docs/user/devices.md index 02e06d2bdffa..a87770853a2d 100644 --- a/docs/user/devices.md +++ b/docs/user/devices.md @@ -39,6 +39,14 @@ iOS, and power off. Close the tab to stop watching; the device keeps running unless you power it off. Closed tabs stay closed after a reload. To watch the device again, choose it from **+ → Device**. +Choose **3D view** to inspect supported devices while the live screen stays +interactive. On iPhone Duo, use the fold and stance controls to change its +physical pose, or pinch over the device to adjust the hinge. Turning the model +to the other screen switches the live display and touch input to that screen. +**Restore 3D view** returns the device to a screen-facing position. +On supported Android foldables, use **Fold device** and **Unfold device** beside +the screen to change its posture in either view. + ## Tools The toolbar's **Tools** button opens a drawer for the open device. It shows the diff --git a/docs/user/install.md b/docs/user/install.md index 773979e77302..53ad5fbbed8d 100644 --- a/docs/user/install.md +++ b/docs/user/install.md @@ -58,12 +58,17 @@ update it with `git pull` and a rebuild. Download a release from [GitHub Releases](https://github.com/pingdotgg/t3code/releases), or use a package manager: -| Platform | Install | -| ------------------ | ------------------------------- | -| Windows | `winget install T3Tools.T3Code` | -| macOS | `brew install --cask t3-code` | -| Arch Linux | `yay -S t3code-bin` | -| Arch Linux nightly | `yay -S t3code-nightly-bin` | +| Platform | Install | +| ------------------ | ---------------------------------- | +| Windows | `winget install T3Tools.T3Code` | +| macOS | `brew install --cask t3-code` | +| Debian, Ubuntu | `sudo apt install ./T3-Code-*.deb` | +| Arch Linux | `yay -S t3code-bin` | +| Arch Linux nightly | `yay -S t3code-nightly-bin` | + +The `.deb` updates itself like the other desktop builds. It asks for your +password to install each update. If your desktop has no password prompt, the +update fails. Download the new `.deb` and install it the same way. ### Windows Subsystem for Linux diff --git a/docs/user/remote-access.md b/docs/user/remote-access.md index 12658d3e0720..bac23103f0bf 100644 --- a/docs/user/remote-access.md +++ b/docs/user/remote-access.md @@ -158,6 +158,12 @@ page, or **Settings → T3 Connect** on mobile, and choose **Deregister**. This revokes its cloud access and frees its host space even when the environment is offline or has been wiped. +When idle tunnel cleanup is enabled, T3 Connect removes a linked environment's +tunnel after it stays offline for several minutes. The environment stays linked +and keeps the same address. When the host starts again or wakes, T3 Connect +creates a replacement tunnel on its own. You do not need to pair again. Cleanup +usually runs five to ten minutes after the tunnel goes down. + On a command-line host, `t3 connect unlink` disables exposure while retaining your login; `t3 connect logout` also clears that login. Background-service [removal](./background-service.md#manage-the-service) is separate. diff --git a/docs/user/thread-sidebar.md b/docs/user/thread-sidebar.md index 0ed7125d7db6..56cc153a4b5e 100644 --- a/docs/user/thread-sidebar.md +++ b/docs/user/thread-sidebar.md @@ -102,7 +102,9 @@ Choose **Settle thread** from its menu to move finished work out of the active l without deleting the conversation. **Un-settle thread** restores it to active work and prevents automatic settlement until new activity resumes the usual rules. Manually settling an idle thread dismisses unanswered async questions without -sending an answer or restarting the agent. +sending an answer or restarting the agent. Settling also closes the thread's +terminals that wait at an idle prompt, and keeps their output. A terminal that +runs a command, such as a dev server, stays open. By default, environments settle inactive threads after three days and settle threads whose pull request merged. A closed pull request can also settle an idle @@ -111,6 +113,11 @@ prevent automatic settlement. An open pull request does not prevent inactivity settlement, but an old closed or merged pull request does not settle work you resumed after it closed. +To keep one thread out of the settled shelf no matter how long it sits idle, open its menu, +choose **Auto-settle behavior**, and pick **Disabled**. The current option is checked. Pick +**Enabled** to return to the usual rules. Manual settle, snooze, and archive still work while it +is disabled. + Change these rules in **Settings → General** on web and desktop, or **Settings → Thread behavior** on mobile. They continue to run when your apps are closed. On web and desktop, choose an environment at the top to change only its rules, or **All environments** to update connected environments together. diff --git a/docs/user/usage.md b/docs/user/usage.md index cb6cb1a761a5..ab77f70a6b3a 100644 --- a/docs/user/usage.md +++ b/docs/user/usage.md @@ -1,14 +1,30 @@ # Usage and limits +Open **Usage** from the sidebar or the command palette, or press `mod+u` on web and +desktop when the terminal is not focused. Customize `usage.open` in +**Settings → Keybindings**. + ## Understand your usage -**Usage** combines Codex, Claude Code, and Grok Build session history from your connected +**Usage** combines Codex, Claude Code, Grok Build, OpenCode, Antigravity, and Cursor history from your connected environments. It shows token use, cache savings, model breakdowns, and estimated API-equivalent cost. These estimates are not your subscription bill. Totals depend on the history available on each server. Grok turns without a saved completed-turn record are missing from the totals. +OpenCode reads its SQLite database and older JSON history. Antigravity reads local conversation +databases, including T3-managed profiles. Set `OPENCODE_DATA_DIR` or `ANTIGRAVITY_DATA_DIR` on the +server to read a different data directory; comma-separated paths read multiple directories. + +Cursor reads account usage from Cursor's dashboard API using the CLI login saved on the server. +This includes headless T3 sessions and desktop usage across machines; the same account counts +once across connected environments. Without an accessible CLI login, T3 shows a +notice instead of incomplete local totals. T3 does not estimate missing tokens from conversation text. +On macOS, choose **Enable Cursor usage** on Usage to allow T3 to read your existing CLI login +from Keychain. You can turn it off in **Settings → Providers → Usage providers**. macOS may ask +you to allow access on the server Mac. + Usage includes each configured account's history, including disabled accounts. Custom homes follow the account's home setting or its `CODEX_HOME`, `CLAUDE_CONFIG_DIR`, or `GROK_HOME` environment variable. Use absolute paths or `~/` paths in the account's environment settings; relative @@ -74,10 +90,10 @@ anything. The command is offered only for providers that appear under **Usage OpenCode Go reports its session, weekly, and monthly allowance when OpenCode runs locally in the environment. T3 cannot report limits for external OpenCode servers because their credentials belong to the remote server. Cursor reports -its monthly allowance, including separate Auto and API usage, using a file-based CLI login or -`CURSOR_AUTH_TOKEN`. Cursor's default macOS keychain login does not currently report limits. -On macOS, use `AGENT_CLI_CREDENTIAL_STORE=file` when signing in and in the provider's environment -to use a file-based login. +its monthly allowance, including separate Auto and API usage, using the CLI login or +`CURSOR_AUTH_TOKEN`. On macOS, this includes the default Keychain login after you enable Cursor +usage. Keychain login is used for limits only with Cursor's default API endpoint. If you configure +a custom Cursor endpoint, use an explicit token or file-based CLI login for limits. Grok reports the remaining subscription allowance and reset time for its current billing period after signing in with `grok login`. Explicit `XAI_API_KEY` connections and custom authentication @@ -102,4 +118,5 @@ settings section when you no longer need it. Add **Subscription usage** from your iOS or Android widget gallery to see remaining Codex and Claude quotas. Tap it to open **Usage → Limits**. On iOS, use **Edit Widget** to choose Session, -Weekly, or both for each provider. Reopen T3 to refresh expired readings. +Weekly, or both for each provider. Reopen T3 to refresh expired readings. The Android widget +requires Android 12L or later. diff --git a/infra/relay/.env.example b/infra/relay/.env.example index f885bff17c0c..9270ec5ff3b7 100644 --- a/infra/relay/.env.example +++ b/infra/relay/.env.example @@ -5,6 +5,10 @@ RELAY_API_ZONE_NAME=example.com RELAY_TUNNEL_ZONE_NAME=tunnels.example.com +# Optional: inactive tunnel cleanup. Start with dry-run, verify the cleanup +# logs, then set enabled. Unset and off both disable cleanup. +# RELAY_TUNNEL_CLEANUP_MODE=off + # Optional: Relay domain override # Set this only when the derived relay hostname should not be used. # RELAY_DOMAIN=relay.example.com diff --git a/infra/relay/migrations/postgres/20260919015455_managed_endpoint_recovery/migration.sql b/infra/relay/migrations/postgres/20260919015455_managed_endpoint_recovery/migration.sql new file mode 100644 index 000000000000..f2bbc5bba592 --- /dev/null +++ b/infra/relay/migrations/postgres/20260919015455_managed_endpoint_recovery/migration.sql @@ -0,0 +1,4 @@ +ALTER TABLE "relay_managed_endpoint_allocations" ADD COLUMN "recovery_enabled_at" varchar(64);--> statement-breakpoint +ALTER TABLE "relay_managed_endpoint_allocations" ADD COLUMN "recovery_environment_public_key" text;--> statement-breakpoint +ALTER TABLE "relay_managed_endpoint_allocations" ADD COLUMN "origin" jsonb;--> statement-breakpoint +ALTER TABLE "relay_managed_endpoint_allocations" ADD COLUMN "generation" integer DEFAULT 0 NOT NULL; diff --git a/infra/relay/migrations/postgres/20260919015455_managed_endpoint_recovery/snapshot.json b/infra/relay/migrations/postgres/20260919015455_managed_endpoint_recovery/snapshot.json new file mode 100644 index 000000000000..c0660307197d --- /dev/null +++ b/infra/relay/migrations/postgres/20260919015455_managed_endpoint_recovery/snapshot.json @@ -0,0 +1,1568 @@ +{ + "dialect": "postgres", + "id": "3809c51e-3821-4a08-818d-e5d28dd3e9b4", + "prevIds": ["4b6d0d21-8d78-4499-9dde-b42bcf633d05"], + "version": "8", + "ddl": [ + { + "isRlsEnabled": false, + "name": "relay_agent_activity_rows", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_delivery_attempts", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_dpop_proofs", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_environment_credentials", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_environment_links", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_live_activities", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_managed_endpoint_allocations", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_managed_tunnel_limits", + "entityType": "tables", + "schema": "public" + }, + { + "isRlsEnabled": false, + "name": "relay_mobile_devices", + "entityType": "tables", + "schema": "public" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_public_key", + "entityType": "columns", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "type": "varchar(512)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "state_json", + "entityType": "columns", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "type": "varchar(36)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "id", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(512)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thread_id", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_id", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "kind", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "source_job_id", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "token_suffix", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "apns_status", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "apns_reason", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(128)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "apns_id", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "transport_error", + "entityType": "columns", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "type": "varchar(128)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "thumbprint", + "entityType": "columns", + "schema": "public", + "table": "relay_dpop_proofs" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "jti", + "entityType": "columns", + "schema": "public", + "table": "relay_dpop_proofs" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "iat", + "entityType": "columns", + "schema": "public", + "table": "relay_dpop_proofs" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "expires_at", + "entityType": "columns", + "schema": "public", + "table": "relay_dpop_proofs" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_dpop_proofs" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credential_id", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_public_key", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "credential_hash", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "revoked_at", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'T3 Environment'", + "generated": null, + "identity": null, + "name": "environment_label", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_public_key", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "endpoint_http_base_url", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "endpoint_ws_base_url", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "varchar(32)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "endpoint_provider_kind", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "notifications_enabled", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "true", + "generated": null, + "identity": null, + "name": "live_activities_enabled", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "boolean", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "false", + "generated": null, + "identity": null, + "name": "managed_tunnels_enabled", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_by_device_id", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "revoked_at", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "relay_environment_links" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_id", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "activity_push_token", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "remote_start_queued_at", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "remote_started_at", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ended_at", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_aggregate_json", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "last_live_activity_delivery_at", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "relay_live_activities" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "environment_id", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "hostname", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tunnel_id", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "tunnel_name", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "dns_record_id", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ready_at", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recovery_enabled_at", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "recovery_environment_public_key", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "origin", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "0", + "generated": null, + "identity": null, + "name": "generation", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "type": "varchar(191)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_tunnel_limits" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "max_tunnels", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_tunnel_limits" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_tunnel_limits" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "relay_managed_tunnel_limits" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "user_id", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "device_id", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "text", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": "'iOS device'", + "generated": null, + "identity": null, + "name": "label", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "platform", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "ios_major_version", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "integer", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "android_api_level", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "app_version", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "varchar(255)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "bundle_id", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "varchar(16)", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "aps_environment", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "push_token", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "text", + "typeSchema": null, + "notNull": false, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "push_to_start_token", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "jsonb", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "preferences_json", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "created_at", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "type": "varchar(64)", + "typeSchema": null, + "notNull": true, + "dimensions": 0, + "default": null, + "generated": null, + "identity": null, + "name": "updated_at", + "entityType": "columns", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "updated_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_agent_activity_rows_updated", + "entityType": "indexes", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "thread_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "created_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_delivery_attempts_environment", + "entityType": "indexes", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "source_job_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_delivery_attempts_source_job", + "entityType": "indexes", + "schema": "public", + "table": "relay_delivery_attempts" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "expires_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_dpop_proofs_expires_at", + "entityType": "indexes", + "schema": "public", + "table": "relay_dpop_proofs" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "credential_hash", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_environment_credentials_hash", + "entityType": "indexes", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "revoked_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_environment_credentials_environment", + "entityType": "indexes", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "environment_public_key", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "revoked_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_environment_credentials_environment_key", + "entityType": "indexes", + "schema": "public", + "table": "relay_environment_credentials" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "environment_id", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + }, + { + "value": "revoked_at", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": false, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_environment_links_environment", + "entityType": "indexes", + "schema": "public", + "table": "relay_environment_links" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "activity_push_token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_live_activities_activity_push_token", + "entityType": "indexes", + "schema": "public", + "table": "relay_live_activities" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "hostname", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_managed_endpoint_allocations_hostname", + "entityType": "indexes", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "tunnel_name", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_managed_endpoint_allocations_tunnel_name", + "entityType": "indexes", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "push_token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_mobile_devices_push_token", + "entityType": "indexes", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "nameExplicit": true, + "columns": [ + { + "value": "push_to_start_token", + "isExpression": false, + "asc": true, + "nullsFirst": false, + "opclass": null + } + ], + "isUnique": true, + "where": null, + "with": "", + "method": "btree", + "concurrently": false, + "name": "idx_relay_mobile_devices_push_to_start_token", + "entityType": "indexes", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "columns": ["environment_id", "environment_public_key", "thread_id"], + "nameExplicit": false, + "name": "relay_agent_activity_rows_pkey", + "entityType": "pks", + "schema": "public", + "table": "relay_agent_activity_rows" + }, + { + "columns": ["thumbprint", "jti"], + "nameExplicit": false, + "name": "relay_dpop_proofs_pkey", + "entityType": "pks", + "schema": "public", + "table": "relay_dpop_proofs" + }, + { + "columns": ["user_id", "environment_id"], + "nameExplicit": false, + "name": "relay_environment_links_pkey", + "entityType": "pks", + "schema": "public", + "table": "relay_environment_links" + }, + { + "columns": ["user_id", "device_id"], + "nameExplicit": false, + "name": "relay_live_activities_pkey", + "entityType": "pks", + "schema": "public", + "table": "relay_live_activities" + }, + { + "columns": ["user_id", "environment_id"], + "nameExplicit": false, + "name": "relay_managed_endpoint_allocations_pkey", + "entityType": "pks", + "schema": "public", + "table": "relay_managed_endpoint_allocations" + }, + { + "columns": ["user_id", "device_id"], + "nameExplicit": false, + "name": "relay_mobile_devices_pkey", + "entityType": "pks", + "schema": "public", + "table": "relay_mobile_devices" + }, + { + "columns": ["id"], + "nameExplicit": false, + "name": "relay_delivery_attempts_pkey", + "schema": "public", + "table": "relay_delivery_attempts", + "entityType": "pks" + }, + { + "columns": ["credential_id"], + "nameExplicit": false, + "name": "relay_environment_credentials_pkey", + "schema": "public", + "table": "relay_environment_credentials", + "entityType": "pks" + }, + { + "columns": ["user_id"], + "nameExplicit": false, + "name": "relay_managed_tunnel_limits_pkey", + "schema": "public", + "table": "relay_managed_tunnel_limits", + "entityType": "pks" + } + ], + "renames": [] +} diff --git a/infra/relay/src/Config.test.ts b/infra/relay/src/Config.test.ts new file mode 100644 index 000000000000..e37d605d0644 --- /dev/null +++ b/infra/relay/src/Config.test.ts @@ -0,0 +1,35 @@ +import { expect, it } from "@effect/vitest"; +import * as ConfigProvider from "effect/ConfigProvider"; +import * as Effect from "effect/Effect"; + +import { managedEndpointCleanupModeConfig } from "./Config.ts"; + +it.effect.each([ + { name: "missing", env: {}, expected: "off" }, + { name: "empty", env: { RELAY_TUNNEL_CLEANUP_MODE: "" }, expected: "off" }, + { name: "whitespace", env: { RELAY_TUNNEL_CLEANUP_MODE: " \t" }, expected: "off" }, + { name: "off", env: { RELAY_TUNNEL_CLEANUP_MODE: "off" }, expected: "off" }, + { + name: "dry-run", + env: { RELAY_TUNNEL_CLEANUP_MODE: "dry-run" }, + expected: "dry-run", + }, + { name: "enabled", env: { RELAY_TUNNEL_CLEANUP_MODE: "enabled" }, expected: "enabled" }, +] as const)("loads $name cleanup mode as $expected", ({ env, expected }) => + Effect.gen(function* () { + const provider = ConfigProvider.fromEnv({ env }); + expect(yield* managedEndpointCleanupModeConfig.parse(provider)).toBe(expected); + }), +); + +it.effect("rejects an invalid cleanup mode", () => + Effect.gen(function* () { + const provider = ConfigProvider.fromEnv({ + env: { RELAY_TUNNEL_CLEANUP_MODE: "delete-everything" }, + }); + const error = yield* Effect.flip(managedEndpointCleanupModeConfig.parse(provider)); + + expect(error._tag).toBe("ConfigError"); + expect(error.message).toContain('Expected "off" | "dry-run" | "enabled"'); + }), +); diff --git a/infra/relay/src/Config.ts b/infra/relay/src/Config.ts index 1f9872c08f23..e2822b53caef 100644 --- a/infra/relay/src/Config.ts +++ b/infra/relay/src/Config.ts @@ -1,4 +1,6 @@ +import * as Config from "effect/Config"; import * as Context from "effect/Context"; +import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Redacted from "effect/Redacted"; import * as Schema from "effect/Schema"; @@ -6,6 +8,20 @@ import * as Schema from "effect/Schema"; export const ApnsEnvironment = Schema.Literals(["sandbox", "production"]); export type ApnsEnvironment = typeof ApnsEnvironment.Type; +export const ManagedEndpointCleanupMode = Schema.Literals(["off", "dry-run", "enabled"]); +export type ManagedEndpointCleanupMode = typeof ManagedEndpointCleanupMode.Type; +const decodeManagedEndpointCleanupMode = Schema.decodeUnknownEffect(ManagedEndpointCleanupMode); + +export const managedEndpointCleanupModeConfig = Config.String("RELAY_TUNNEL_CLEANUP_MODE").pipe( + Config.withDefault("off"), + Config.map((value) => value.trim() || "off"), + Config.mapEffect((value) => + decodeManagedEndpointCleanupMode(value).pipe( + Effect.mapError((error) => new Config.ConfigError(error)), + ), + ), +); + export interface ApnsCredentials { readonly teamId: string; readonly keyId: string; @@ -28,6 +44,7 @@ export class RelayConfiguration extends Context.Service< readonly cloudMintPublicKey: string; readonly managedEndpointBaseDomain: string | undefined; readonly managedEndpointNamespace: string | undefined; + readonly managedEndpointCleanupMode?: ManagedEndpointCleanupMode; } >()("t3code-relay/Config/RelayConfiguration") {} diff --git a/infra/relay/src/agentActivity/LiveActivities.test.ts b/infra/relay/src/agentActivity/LiveActivities.test.ts index 7f2bce87431e..5cbd3edf728f 100644 --- a/infra/relay/src/agentActivity/LiveActivities.test.ts +++ b/infra/relay/src/agentActivity/LiveActivities.test.ts @@ -276,7 +276,7 @@ describe("LiveActivities", () => { liveActivities.register({ userId: "user-1", registration }), ); const targetListError = yield* Effect.flip(liveActivities.listTargets({ userId: "user-1" })); - const deliveryErrors = yield* Effect.all( + const deliveryErrors = yield* Effect.forEach( [ liveActivities.markDelivery({ userId: "user-1", @@ -297,7 +297,8 @@ describe("LiveActivities", () => { kind: "push_notification", invalidatedAt: "2026-05-25T00:00:10.000Z", }), - ].map(Effect.flip), + ], + (effect) => Effect.flip(effect), { concurrency: 1 }, ); diff --git a/infra/relay/src/clientConfig.test.ts b/infra/relay/src/clientConfig.test.ts index 6fd28dcd948c..cfc5e665f0b6 100644 --- a/infra/relay/src/clientConfig.test.ts +++ b/infra/relay/src/clientConfig.test.ts @@ -82,13 +82,7 @@ describe("PublishClientConfig", () => { ), ); - yield* stack - .deploy( - Effect.gen(function* () { - return yield* PublishClientConfig(clientConfig("v1")); - }), - ) - .pipe(configured); + yield* stack.deploy(PublishClientConfig(clientConfig("v1"))).pipe(configured); const first = yield* fs.readFileString(target); expect(first).toContain("KEEP=yes\n"); expect(first).toContain("T3CODE_RELAY_URL=https://relay.example.com\n"); @@ -97,23 +91,11 @@ describe("PublishClientConfig", () => { // Same input: the action is skipped, so a change made by hand survives. yield* fs.writeFileString(target, `${first}MANUAL=1\n`); - yield* stack - .deploy( - Effect.gen(function* () { - return yield* PublishClientConfig(clientConfig("v1")); - }), - ) - .pipe(configured); + yield* stack.deploy(PublishClientConfig(clientConfig("v1"))).pipe(configured); expect(yield* fs.readFileString(target)).toContain("MANUAL=1\n"); // A rotated token changes the input, so it runs again and replaces the line. - yield* stack - .deploy( - Effect.gen(function* () { - return yield* PublishClientConfig(clientConfig("v2")); - }), - ) - .pipe(configured); + yield* stack.deploy(PublishClientConfig(clientConfig("v2"))).pipe(configured); const third = yield* fs.readFileString(target); expect(third).toContain("T3CODE_RELAY_CLIENT_OTLP_TRACES_TOKEN=client-v2\n"); expect(third).not.toContain("client-v1"); @@ -128,11 +110,7 @@ describe("PublishClientConfig", () => { const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-relay-client-config-" }); const target = path.join(dir, "client.env"); const exit = yield* stack - .deploy( - Effect.gen(function* () { - return yield* PublishClientConfig({ ...clientConfig("v1"), url: undefined }); - }), - ) + .deploy(PublishClientConfig({ ...clientConfig("v1"), url: undefined })) .pipe( Effect.provide( ConfigProvider.layer( @@ -155,11 +133,9 @@ describe("PublishClientConfig", () => { const target = path.join(dir, "client.env"); const exit = yield* stack .deploy( - Effect.gen(function* () { - return yield* PublishClientConfig({ - ...clientConfig("v1"), - clientTracingDataset: "relay-traces\nINJECTED=1", - }); + PublishClientConfig({ + ...clientConfig("v1"), + clientTracingDataset: "relay-traces\nINJECTED=1", }), ) .pipe( diff --git a/infra/relay/src/deploymentConfig.test.ts b/infra/relay/src/deploymentConfig.test.ts index f090c70ee22b..30682d65ba1f 100644 --- a/infra/relay/src/deploymentConfig.test.ts +++ b/infra/relay/src/deploymentConfig.test.ts @@ -7,6 +7,7 @@ import { managedEndpointHostname, isManagedEndpointHostname, managedEndpointTunnelName, + managedEndpointTunnelNamePrefix, relayOwnsManagedEndpointZone, RelayPublicDomainLabelTooLongError, relayPublicDomainForStage, @@ -84,6 +85,9 @@ describe("managed endpoint names", () => { expect(managedEndpointTunnelName("dev_julius", hash)).toBe( "t3coderelay-managedendpoint-dev-julius-abcdef0123456789", ); + expect(managedEndpointTunnelNamePrefix("dev_julius")).toBe( + "t3coderelay-managedendpoint-dev-julius-", + ); }); it("keeps the DNS label within the provider limit for long stage names", () => { diff --git a/infra/relay/src/deploymentConfig.ts b/infra/relay/src/deploymentConfig.ts index 1a3a332acc5a..961c6f1b2f4a 100644 --- a/infra/relay/src/deploymentConfig.ts +++ b/infra/relay/src/deploymentConfig.ts @@ -117,6 +117,10 @@ export function managedEndpointForHostname(hostname: string): RelayManagedEndpoi }; } +export function managedEndpointTunnelNamePrefix(stage: string): string { + return `${MANAGED_ENDPOINT_TUNNEL_PREFIX}-${relayStageSlug(stage)}-`; +} + export function managedEndpointTunnelName(stage: string, hash: string): string { - return `${MANAGED_ENDPOINT_TUNNEL_PREFIX}-${relayStageSlug(stage)}-${stableSuffix(hash)}`; + return `${managedEndpointTunnelNamePrefix(stage)}${stableSuffix(hash)}`; } diff --git a/infra/relay/src/environments/EnvironmentConnector.test.ts b/infra/relay/src/environments/EnvironmentConnector.test.ts index da6f4acc192e..379aff123d43 100644 --- a/infra/relay/src/environments/EnvironmentConnector.test.ts +++ b/infra/relay/src/environments/EnvironmentConnector.test.ts @@ -188,7 +188,9 @@ function makeAllocations( tunnelName: "tunnel-name", dnsRecordId: "dns-record-id", readyAt: "2026-05-25T00:00:00.000Z", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, updatedAt: "2026-05-25T00:00:00.000Z", + generation: 1, }, ): ManagedEndpointAllocations.ManagedEndpointAllocations["Service"] { return { @@ -197,7 +199,10 @@ function makeAllocations( recordTunnel: () => Effect.die("unused"), recordDns: () => Effect.die("unused"), markReady: () => Effect.die("unused"), + enableRecovery: () => Effect.die("unused"), + listByTunnelNames: () => Effect.die("unused"), claimRelease: () => Effect.die("unused"), + withClaimedTunnel: () => Effect.die("unused"), claimDeprovision: () => Effect.die("unused"), remove: () => Effect.die("unused"), removeClaimed: () => Effect.die("unused"), @@ -469,7 +474,9 @@ describe("EnvironmentConnector", () => { tunnelName: "tunnel-name", dnsRecordId: "dns-record-id", readyAt: null, + origin: null, updatedAt: "2026-05-25T00:00:00.000Z", + generation: 1, }), }), ), diff --git a/infra/relay/src/environments/EnvironmentLinker.test.ts b/infra/relay/src/environments/EnvironmentLinker.test.ts index 78412e6c138e..67928c25f09c 100644 --- a/infra/relay/src/environments/EnvironmentLinker.test.ts +++ b/infra/relay/src/environments/EnvironmentLinker.test.ts @@ -134,8 +134,9 @@ function testLayer(input?: { revokeForEnvironmentPublicKey: () => Effect.succeed(false), }), Layer.succeed(ManagedEndpointProvider.ManagedEndpointProvider, { + reconcileOrigin: () => Effect.succeed("ready"), prepareDeprovision: () => Effect.succeed(null), - deprovision: input?.deprovision ?? (() => Effect.void), + deprovision: input?.deprovision ?? (() => Effect.succeed(true)), release: () => Effect.succeed(true), provision: () => Effect.succeed({ @@ -241,6 +242,7 @@ describe("EnvironmentLinker", () => { deprovision: (input) => Effect.sync(() => { deprovisionedEnvironmentId = input.environmentId; + return true; }), }), ), diff --git a/infra/relay/src/environments/ManagedEndpointAllocations.test.ts b/infra/relay/src/environments/ManagedEndpointAllocations.test.ts index ebf51de100c1..105b10cc6a9e 100644 --- a/infra/relay/src/environments/ManagedEndpointAllocations.test.ts +++ b/infra/relay/src/environments/ManagedEndpointAllocations.test.ts @@ -1,6 +1,8 @@ import { describe, expect, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; +import { PgDialect } from "drizzle-orm/pg-core"; import * as RelayDb from "../db.ts"; import { relayManagedEndpointAllocations } from "../persistence/schema.ts"; @@ -10,17 +12,253 @@ const layerWithDb = (db: RelayDb.RelayDb["Service"]) => ManagedEndpointAllocations.layer.pipe(Layer.provide(Layer.succeed(RelayDb.RelayDb, db))); describe("ManagedEndpointAllocations", () => { + it.effect("clears endpoint readiness and recovery only when the recorded tunnel changes", () => { + let updated: + | { + readonly tunnelId: string; + readonly readyAt: unknown; + readonly recoveryEnabledAt: unknown; + readonly recoveryEnvironmentPublicKey: unknown; + } + | undefined; + const fakeDb = { + update: (table: unknown) => { + expect(table).toBe(relayManagedEndpointAllocations); + return { + set: (values: { + readonly tunnelId: string; + readonly readyAt: unknown; + readonly recoveryEnabledAt: unknown; + readonly recoveryEnvironmentPublicKey: unknown; + }) => { + updated = values; + return { + where: () => ({ + returning: () => Effect.succeed([{ generation: 8 }]), + }), + }; + }, + }; + }, + } as unknown as RelayDb.RelayDb["Service"]; + + return Effect.gen(function* () { + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + expect( + yield* allocations.recordTunnel({ + userId: "user-1", + environmentId: "environment-1", + tunnelId: "replacement-tunnel", + generation: 7, + }), + ).toBe(8); + expect(updated?.tunnelId).toBe("replacement-tunnel"); + const query = new PgDialect().sqlToQuery(updated?.readyAt as never); + expect(query.sql).toBe( + 'case when "relay_managed_endpoint_allocations"."tunnel_id" = $1 then "relay_managed_endpoint_allocations"."ready_at" else null end', + ); + expect(query.params).toEqual(["replacement-tunnel"]); + for (const [column, value] of [ + ["recovery_enabled_at", updated?.recoveryEnabledAt], + ["recovery_environment_public_key", updated?.recoveryEnvironmentPublicKey], + ] as const) { + const recoveryQuery = new PgDialect().sqlToQuery(value as never); + expect(recoveryQuery.sql).toBe( + `case when "relay_managed_endpoint_allocations"."tunnel_id" = $1 then "relay_managed_endpoint_allocations"."${column}" else null end`, + ); + expect(recoveryQuery.params).toEqual(["replacement-tunnel"]); + } + }).pipe(Effect.provide(layerWithDb(fakeDb))); + }); + + it.effect("records recovery support and advances the allocation generation", () => { + let updated: + | { + readonly recoveryEnabledAt: string; + readonly recoveryEnvironmentPublicKey: string; + readonly updatedAt: string; + } + | undefined; + let condition: unknown; + const fakeDb = { + update: (table: unknown) => { + expect(table).toBe(relayManagedEndpointAllocations); + return { + set: (values: { + readonly recoveryEnabledAt: string; + readonly recoveryEnvironmentPublicKey: string; + readonly updatedAt: string; + }) => { + updated = values; + return { + where: (where: unknown) => { + condition = where; + return { + returning: () => Effect.succeed([{ environmentId: "environment-1" }]), + }; + }, + }; + }, + }; + }, + } as unknown as RelayDb.RelayDb["Service"]; + + return Effect.gen(function* () { + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + expect( + yield* allocations.enableRecovery({ + userId: "user-1", + environmentId: "environment-1", + tunnelId: "tunnel-1", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ).toBe(true); + + expect(updated?.recoveryEnabledAt).toBe(updated?.updatedAt); + expect(updated?.recoveryEnabledAt).toBeDefined(); + expect(updated?.recoveryEnvironmentPublicKey).toBe("public-key"); + const query = new PgDialect().sqlToQuery(condition as never); + expect(query.sql).toContain('"relay_managed_endpoint_allocations"."tunnel_id"'); + expect(query.sql).toContain('"relay_environment_links"."environment_public_key"'); + expect(query.sql).toContain('"relay_environment_links"."endpoint_provider_kind"'); + expect(query.sql).toContain('"relay_environment_links"."revoked_at" is null'); + expect(query.sql).toContain("for update"); + expect(query.params).toContain("tunnel-1"); + expect(query.params).toContain("public-key"); + expect(query.params).toContain("cloudflare_tunnel"); + }).pipe(Effect.provide(layerWithDb(fakeDb))); + }); + + it.effect("rejects recovery when the tunnel or active link no longer matches", () => { + const fakeDb = { + update: () => ({ + set: () => ({ + where: () => ({ + returning: () => Effect.succeed([]), + }), + }), + }), + } as unknown as RelayDb.RelayDb["Service"]; + + return Effect.gen(function* () { + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + expect( + yield* allocations.enableRecovery({ + userId: "user-1", + environmentId: "environment-1", + tunnelId: "missing-tunnel", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ).toBe(false); + }).pipe(Effect.provide(layerWithDb(fakeDb))); + }); + + it.effect("returns recovery support with tunnel allocation lookups", () => { + const base = { + userId: "user-1", + hostname: "environment.example.test", + tunnelName: "managed-tunnel", + dnsRecordId: "dns-1", + readyAt: "2026-08-25T12:00:00.000Z", + updatedAt: "2026-08-25T12:00:00.000Z", + generation: 1, + }; + const fakeDb = { + select: () => ({ + from: (table: unknown) => { + expect(table).toBe(relayManagedEndpointAllocations); + return { + leftJoin: () => ({ + where: () => + Effect.succeed([ + { + ...base, + environmentId: "environment-1", + tunnelId: "tunnel-1", + recoveryEnabledAt: "2026-08-25T12:00:00.000Z", + recoveryEnvironmentPublicKey: "current-key", + linkedEnvironmentPublicKey: "current-key", + }, + { + ...base, + environmentId: "environment-2", + tunnelId: "tunnel-2", + recoveryEnabledAt: null, + recoveryEnvironmentPublicKey: null, + linkedEnvironmentPublicKey: "current-key", + }, + { + ...base, + environmentId: "environment-3", + tunnelId: "tunnel-3", + recoveryEnabledAt: "2026-08-25T12:00:00.000Z", + recoveryEnvironmentPublicKey: "old-key", + linkedEnvironmentPublicKey: "new-key", + }, + ]), + }), + }; + }, + }), + } as unknown as RelayDb.RelayDb["Service"]; + + return Effect.gen(function* () { + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + const result = yield* allocations.listByTunnelNames([ + "first-tunnel", + "second-tunnel", + "third-tunnel", + ]); + + expect(result.map((entry) => [entry.tunnelId, entry.recoveryEnabled])).toEqual([ + ["tunnel-1", true], + ["tunnel-2", false], + ["tunnel-3", false], + ]); + }).pipe(Effect.provide(layerWithDb(fakeDb))); + }); + + it.effect("skips the database for an empty tunnel lookup", () => + Effect.gen(function* () { + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + expect(yield* allocations.listByTunnelNames([])).toEqual([]); + }).pipe(Effect.provide(layerWithDb({} as RelayDb.RelayDb["Service"]))), + ); + + it.effect("splits large tunnel lookups into bounded database queries", () => { + const batchSizes: number[] = []; + const fakeDb = { + select: () => ({ + from: () => ({ + leftJoin: () => ({ + where: (condition: unknown) => { + batchSizes.push(new PgDialect().sqlToQuery(condition as never).params.length); + return Effect.succeed([]); + }, + }), + }), + }), + } as unknown as RelayDb.RelayDb["Service"]; + + return Effect.gen(function* () { + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + const names = Array.from({ length: 1_001 }, (_, index) => `tunnel-${index}`); + expect(yield* allocations.listByTunnelNames(names)).toEqual([]); + expect(batchSizes).toEqual([500, 500, 1]); + }).pipe(Effect.provide(layerWithDb(fakeDb))); + }); + it.effect("returns a claim generation only when deprovision wins the allocation CAS", () => { - let claimedAt: string | undefined; const fakeDb = { update: (table: unknown) => { expect(table).toBe(relayManagedEndpointAllocations); return { - set: (values: { readonly updatedAt: string }) => { - claimedAt = values.updatedAt; + set: (_values: { readonly updatedAt: string }) => { return { where: () => ({ - returning: () => Effect.succeed([{ userId: "user-1" }]), + returning: () => Effect.succeed([{ generation: 8 }]), }), }; }, @@ -33,14 +271,58 @@ describe("ManagedEndpointAllocations", () => { const generation = yield* allocations.claimDeprovision({ userId: "user-1", environmentId: "environment-1", - updatedAt: "captured-generation", + generation: 7, }); - expect(generation).toBe(claimedAt); + expect(generation).toBe(8); expect(generation).not.toBeNull(); }).pipe(Effect.provide(layerWithDb(fakeDb))); }); + it.effect("holds the claimed allocation row while deleting its tunnel", () => { + const operations: string[] = []; + const fakeDb = { + $client: { + withTransaction: (effect: Effect.Effect) => + Effect.sync(() => { + operations.push("transaction"); + }).pipe(Effect.andThen(effect)), + }, + select: () => ({ + from: () => ({ + where: () => ({ + limit: () => ({ + for: (strength: string) => + Effect.sync(() => { + operations.push(`lock:${strength}`); + return [{ generation: 7 }]; + }), + }), + }), + }), + }), + } as unknown as RelayDb.RelayDb["Service"]; + + return Effect.gen(function* () { + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + const result = yield* allocations.withClaimedTunnel( + { + userId: "user-1", + environmentId: "environment-1", + tunnelId: "tunnel-1", + generation: 7, + }, + Effect.sync(() => { + operations.push("delete"); + return true; + }), + ); + + expect(Option.getOrNull(result)).toBe(true); + expect(operations).toEqual(["transaction", "lock:update", "delete"]); + }).pipe(Effect.provide(layerWithDb(fakeDb))); + }); + it.effect("does not remove an allocation superseded after a deprovision claim", () => { const fakeDb = { delete: (table: unknown) => { @@ -59,7 +341,7 @@ describe("ManagedEndpointAllocations", () => { yield* allocations.removeClaimed({ userId: "user-1", environmentId: "environment-1", - updatedAt: "outdated-claim-generation", + generation: 7, }), ).toBe(false); }).pipe(Effect.provide(layerWithDb(fakeDb))); diff --git a/infra/relay/src/environments/ManagedEndpointAllocations.ts b/infra/relay/src/environments/ManagedEndpointAllocations.ts index a8b5ecde62c0..588333d3e15a 100644 --- a/infra/relay/src/environments/ManagedEndpointAllocations.ts +++ b/infra/relay/src/environments/ManagedEndpointAllocations.ts @@ -1,14 +1,17 @@ -import type { RelayManagedEndpoint } from "@t3tools/contracts/relay"; -import { and, eq } from "drizzle-orm"; +import type { RelayManagedEndpoint, RelayManagedEndpointOrigin } from "@t3tools/contracts/relay"; +import { and, eq, exists, inArray, isNull, sql } from "drizzle-orm"; +import { QueryBuilder } from "drizzle-orm/pg-core"; import * as Context from "effect/Context"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; +import * as SqlError from "effect/unstable/sql/SqlError"; import * as RelayDb from "../db.ts"; import { isManagedEndpointHostname, managedEndpointForHostname } from "../deploymentConfig.ts"; -import { relayManagedEndpointAllocations } from "../persistence/schema.ts"; +import { relayEnvironmentLinks, relayManagedEndpointAllocations } from "../persistence/schema.ts"; export interface ManagedEndpointAllocation { readonly userId: string; @@ -18,13 +21,17 @@ export interface ManagedEndpointAllocation { readonly tunnelName: string; readonly dnsRecordId: string | null; readonly readyAt: string | null; - /** - * Doubles as the allocation's generation marker: every mutation rewrites it, - * so `claimRelease` can detect a provision that raced a release. - */ + readonly origin: RelayManagedEndpointOrigin | null; readonly updatedAt: string; + readonly generation: number; } +export interface ManagedEndpointTunnelAllocation extends ManagedEndpointAllocation { + readonly recoveryEnabled: boolean; +} + +export const MANAGED_ENDPOINT_ALLOCATION_LOOKUP_BATCH_SIZE = 500; + export function resolveReadyManagedEndpoint(input: { readonly allocation: ManagedEndpointAllocation; readonly baseDomain: string | undefined; @@ -50,6 +57,9 @@ export class ManagedEndpointAllocationPersistenceError extends Schema.TaggedErro "record-tunnel", "record-dns", "mark-ready", + "enable-recovery", + "list-tunnels", + "lock-tunnel", "claim-release", "claim-deprovision", "remove", @@ -82,23 +92,38 @@ interface ReserveManagedEndpointAllocationInput extends ManagedEndpointAllocatio interface RecordManagedEndpointTunnelInput extends ManagedEndpointAllocationKey { readonly tunnelId: string; + readonly generation: number; } interface RecordManagedEndpointDnsInput extends ManagedEndpointAllocationKey { readonly dnsRecordId: string; + readonly tunnelId: string; + readonly generation: number; +} + +interface MarkManagedEndpointReadyInput extends ManagedEndpointAllocationKey { + readonly tunnelId: string; + readonly generation: number; + readonly origin: RelayManagedEndpointOrigin; } interface ClaimManagedEndpointReleaseInput extends ManagedEndpointAllocationKey { readonly tunnelId: string; - readonly updatedAt: string; + readonly generation: number; +} + +interface EnableManagedEndpointRecoveryInput extends ManagedEndpointAllocationKey { + readonly tunnelId: string; + readonly environmentPublicKey: string; + readonly origin: RelayManagedEndpointOrigin; } interface ClaimManagedEndpointDeprovisionInput extends ManagedEndpointAllocationKey { - readonly updatedAt: string; + readonly generation: number; } interface RemoveClaimedManagedEndpointAllocationInput extends ManagedEndpointAllocationKey { - readonly updatedAt: string; + readonly generation: number; } export class ManagedEndpointAllocations extends Context.Service< @@ -112,23 +137,36 @@ export class ManagedEndpointAllocations extends Context.Service< ) => Effect.Effect; readonly recordTunnel: ( input: RecordManagedEndpointTunnelInput, - ) => Effect.Effect; + ) => Effect.Effect; readonly recordDns: ( input: RecordManagedEndpointDnsInput, - ) => Effect.Effect; + ) => Effect.Effect; readonly markReady: ( - input: ManagedEndpointAllocationKey, - ) => Effect.Effect; + input: MarkManagedEndpointReadyInput, + ) => Effect.Effect; + readonly enableRecovery: ( + input: EnableManagedEndpointRecoveryInput, + ) => Effect.Effect; + readonly listByTunnelNames: ( + tunnelNames: ReadonlyArray, + ) => Effect.Effect< + ReadonlyArray, + ManagedEndpointAllocationPersistenceError + >; /** * Atomically claims the right to delete the allocation's tunnel: succeeds * only while the recorded tunnel and generation still match what the - * caller loaded. A concurrent provision rewrites `updatedAt` when it + * caller loaded. A concurrent provision increments `generation` when it * records its tunnel, which makes a stale claim fail and keeps the freshly * issued tunnel alive. */ readonly claimRelease: ( input: ClaimManagedEndpointReleaseInput, - ) => Effect.Effect; + ) => Effect.Effect; + readonly withClaimedTunnel: ( + input: ClaimManagedEndpointReleaseInput, + effect: Effect.Effect, + ) => Effect.Effect, E | ManagedEndpointAllocationPersistenceError, R>; /** * Claims the complete allocation for teardown only if its generation still * matches the snapshot captured by the unlink operation. @@ -138,7 +176,7 @@ export class ManagedEndpointAllocations extends Context.Service< */ readonly claimDeprovision: ( input: ClaimManagedEndpointDeprovisionInput, - ) => Effect.Effect; + ) => Effect.Effect; readonly remove: ( input: ManagedEndpointAllocationKey, ) => Effect.Effect; @@ -156,7 +194,9 @@ const allocationSelection = { tunnelName: relayManagedEndpointAllocations.tunnelName, dnsRecordId: relayManagedEndpointAllocations.dnsRecordId, readyAt: relayManagedEndpointAllocations.readyAt, + origin: relayManagedEndpointAllocations.origin, updatedAt: relayManagedEndpointAllocations.updatedAt, + generation: relayManagedEndpointAllocations.generation, }; const whereAllocation = (input: ManagedEndpointAllocationKey) => @@ -248,14 +288,28 @@ export const make = Effect.gen(function* () { recordTunnel: Effect.fn("relay.managed_endpoint_allocations.record_tunnel")(function* ( input: RecordManagedEndpointTunnelInput, ) { - yield* db + return yield* db .update(relayManagedEndpointAllocations) .set({ tunnelId: input.tunnelId, + readyAt: sql`case when ${relayManagedEndpointAllocations.tunnelId} = ${input.tunnelId} then ${relayManagedEndpointAllocations.readyAt} else null end`, + origin: sql`case when ${relayManagedEndpointAllocations.tunnelId} = ${input.tunnelId} then ${relayManagedEndpointAllocations.origin} else null end`, + // Recovery registration is per tunnel: a replacement must register + // again before the reaper may treat it as recoverable. + recoveryEnabledAt: sql`case when ${relayManagedEndpointAllocations.tunnelId} = ${input.tunnelId} then ${relayManagedEndpointAllocations.recoveryEnabledAt} else null end`, + recoveryEnvironmentPublicKey: sql`case when ${relayManagedEndpointAllocations.tunnelId} = ${input.tunnelId} then ${relayManagedEndpointAllocations.recoveryEnvironmentPublicKey} else null end`, updatedAt: DateTime.formatIso(yield* DateTime.now), + generation: sql`${relayManagedEndpointAllocations.generation} + 1`, }) - .where(whereAllocation(input)) + .where( + and( + whereAllocation(input), + eq(relayManagedEndpointAllocations.generation, input.generation), + ), + ) + .returning({ generation: relayManagedEndpointAllocations.generation }) .pipe( + Effect.map((rows) => rows[0]?.generation ?? null), Effect.mapError( (cause) => new ManagedEndpointAllocationPersistenceError({ @@ -270,14 +324,23 @@ export const make = Effect.gen(function* () { recordDns: Effect.fn("relay.managed_endpoint_allocations.record_dns")(function* ( input: RecordManagedEndpointDnsInput, ) { - yield* db + return yield* db .update(relayManagedEndpointAllocations) .set({ dnsRecordId: input.dnsRecordId, updatedAt: DateTime.formatIso(yield* DateTime.now), + generation: sql`${relayManagedEndpointAllocations.generation} + 1`, }) - .where(whereAllocation(input)) + .where( + and( + whereAllocation(input), + eq(relayManagedEndpointAllocations.tunnelId, input.tunnelId), + eq(relayManagedEndpointAllocations.generation, input.generation), + ), + ) + .returning({ generation: relayManagedEndpointAllocations.generation }) .pipe( + Effect.map((rows) => rows[0]?.generation ?? null), Effect.mapError( (cause) => new ManagedEndpointAllocationPersistenceError({ @@ -290,17 +353,27 @@ export const make = Effect.gen(function* () { ); }), markReady: Effect.fn("relay.managed_endpoint_allocations.mark_ready")(function* ( - input: ManagedEndpointAllocationKey, + input: MarkManagedEndpointReadyInput, ) { const now = DateTime.formatIso(yield* DateTime.now); - yield* db + return yield* db .update(relayManagedEndpointAllocations) .set({ readyAt: now, + origin: input.origin, updatedAt: now, + generation: sql`${relayManagedEndpointAllocations.generation} + 1`, }) - .where(whereAllocation(input)) + .where( + and( + whereAllocation(input), + eq(relayManagedEndpointAllocations.tunnelId, input.tunnelId), + eq(relayManagedEndpointAllocations.generation, input.generation), + ), + ) + .returning({ environmentId: relayManagedEndpointAllocations.environmentId }) .pipe( + Effect.map((rows) => rows.length > 0), Effect.mapError( (cause) => new ManagedEndpointAllocationPersistenceError({ @@ -312,6 +385,124 @@ export const make = Effect.gen(function* () { ), ); }), + enableRecovery: Effect.fn("relay.managed_endpoint_allocations.enable_recovery")(function* ( + input: EnableManagedEndpointRecoveryInput, + ) { + const now = DateTime.formatIso(yield* DateTime.now); + return yield* db + .update(relayManagedEndpointAllocations) + .set({ + recoveryEnabledAt: now, + recoveryEnvironmentPublicKey: input.environmentPublicKey, + updatedAt: now, + generation: sql`${relayManagedEndpointAllocations.generation} + 1`, + }) + .where( + and( + whereAllocation(input), + eq(relayManagedEndpointAllocations.tunnelId, input.tunnelId), + eq(relayManagedEndpointAllocations.origin, input.origin), + exists( + new QueryBuilder() + .select({ userId: relayEnvironmentLinks.userId }) + .from(relayEnvironmentLinks) + .where( + and( + eq(relayEnvironmentLinks.userId, input.userId), + eq(relayEnvironmentLinks.environmentId, input.environmentId), + eq(relayEnvironmentLinks.environmentPublicKey, input.environmentPublicKey), + eq(relayEnvironmentLinks.endpointProviderKind, "cloudflare_tunnel"), + isNull(relayEnvironmentLinks.revokedAt), + ), + ) + .for("update"), + ), + ), + ) + .returning({ environmentId: relayManagedEndpointAllocations.environmentId }) + .pipe( + Effect.map((rows) => rows.length > 0), + Effect.mapError( + (cause) => + new ManagedEndpointAllocationPersistenceError({ + operation: "enable-recovery", + stage: "database-request", + ...input, + cause, + }), + ), + ); + }), + listByTunnelNames: Effect.fn("relay.managed_endpoint_allocations.list_by_tunnel_names")( + function* (tunnelNames: ReadonlyArray) { + if (tunnelNames.length === 0) { + return []; + } + const batches = Array.from( + { length: Math.ceil(tunnelNames.length / MANAGED_ENDPOINT_ALLOCATION_LOOKUP_BATCH_SIZE) }, + (_, index) => + tunnelNames.slice( + index * MANAGED_ENDPOINT_ALLOCATION_LOOKUP_BATCH_SIZE, + (index + 1) * MANAGED_ENDPOINT_ALLOCATION_LOOKUP_BATCH_SIZE, + ), + ); + const results = yield* Effect.forEach( + batches, + (batch) => + db + .select({ + ...allocationSelection, + recoveryEnabledAt: relayManagedEndpointAllocations.recoveryEnabledAt, + recoveryEnvironmentPublicKey: + relayManagedEndpointAllocations.recoveryEnvironmentPublicKey, + linkedEnvironmentPublicKey: relayEnvironmentLinks.environmentPublicKey, + }) + .from(relayManagedEndpointAllocations) + .leftJoin( + relayEnvironmentLinks, + and( + eq(relayEnvironmentLinks.userId, relayManagedEndpointAllocations.userId), + eq( + relayEnvironmentLinks.environmentId, + relayManagedEndpointAllocations.environmentId, + ), + isNull(relayEnvironmentLinks.revokedAt), + ), + ) + .where(inArray(relayManagedEndpointAllocations.tunnelName, batch)) + .pipe( + Effect.map((rows) => + rows.map( + ({ + recoveryEnabledAt, + recoveryEnvironmentPublicKey, + linkedEnvironmentPublicKey, + ...allocation + }) => ({ + ...allocation, + recoveryEnabled: + recoveryEnabledAt !== null && + recoveryEnvironmentPublicKey !== null && + recoveryEnvironmentPublicKey === linkedEnvironmentPublicKey, + }), + ), + ), + Effect.mapError( + (cause) => + new ManagedEndpointAllocationPersistenceError({ + operation: "list-tunnels", + stage: "database-request", + userId: "*", + environmentId: "*", + cause, + }), + ), + ), + { concurrency: 1 }, + ); + return results.flat(); + }, + ), claimRelease: Effect.fn("relay.managed_endpoint_allocations.claim_release")(function* ( input: ClaimManagedEndpointReleaseInput, ) { @@ -319,17 +510,18 @@ export const make = Effect.gen(function* () { .update(relayManagedEndpointAllocations) .set({ updatedAt: DateTime.formatIso(yield* DateTime.now), + generation: sql`${relayManagedEndpointAllocations.generation} + 1`, }) .where( and( whereAllocation(input), eq(relayManagedEndpointAllocations.tunnelId, input.tunnelId), - eq(relayManagedEndpointAllocations.updatedAt, input.updatedAt), + eq(relayManagedEndpointAllocations.generation, input.generation), ), ) - .returning({ userId: relayManagedEndpointAllocations.userId }) + .returning({ generation: relayManagedEndpointAllocations.generation }) .pipe( - Effect.map((rows) => rows.length > 0), + Effect.map((rows) => rows[0]?.generation ?? null), Effect.mapError( (cause) => new ManagedEndpointAllocationPersistenceError({ @@ -344,22 +536,64 @@ export const make = Effect.gen(function* () { ); return claimed; }), + withClaimedTunnel: Effect.fn("relay.managed_endpoint_allocations.with_claimed_tunnel")( + function* ( + input: ClaimManagedEndpointReleaseInput, + effect: Effect.Effect, + ): Effect.fn.Return, E | ManagedEndpointAllocationPersistenceError, R> { + const lockError = (cause: unknown) => + new ManagedEndpointAllocationPersistenceError({ + operation: "lock-tunnel", + stage: "database-request", + userId: input.userId, + environmentId: input.environmentId, + tunnelId: input.tunnelId, + cause, + }); + return yield* db.$client + .withTransaction( + db + .select({ generation: relayManagedEndpointAllocations.generation }) + .from(relayManagedEndpointAllocations) + .where( + and( + whereAllocation(input), + eq(relayManagedEndpointAllocations.tunnelId, input.tunnelId), + eq(relayManagedEndpointAllocations.generation, input.generation), + ), + ) + .limit(1) + .for("update") + .pipe( + Effect.mapError(lockError), + Effect.flatMap((rows) => + rows.length === 0 ? Effect.succeedNone : Effect.asSome(effect), + ), + ), + ) + .pipe( + Effect.mapError((cause) => (SqlError.isSqlError(cause) ? lockError(cause) : cause)), + ); + }, + ), claimDeprovision: Effect.fn("relay.managed_endpoint_allocations.claim_deprovision")(function* ( input: ClaimManagedEndpointDeprovisionInput, ) { - const claimedAt = DateTime.formatIso(yield* DateTime.now); const claimed = yield* db .update(relayManagedEndpointAllocations) - .set({ updatedAt: claimedAt }) + .set({ + updatedAt: DateTime.formatIso(yield* DateTime.now), + generation: sql`${relayManagedEndpointAllocations.generation} + 1`, + }) .where( and( whereAllocation(input), - eq(relayManagedEndpointAllocations.updatedAt, input.updatedAt), + eq(relayManagedEndpointAllocations.generation, input.generation), ), ) - .returning({ userId: relayManagedEndpointAllocations.userId }) + .returning({ generation: relayManagedEndpointAllocations.generation }) .pipe( - Effect.map((rows) => rows.length > 0), + Effect.map((rows) => rows[0]?.generation ?? null), Effect.mapError( (cause) => new ManagedEndpointAllocationPersistenceError({ @@ -371,7 +605,7 @@ export const make = Effect.gen(function* () { }), ), ); - return claimed ? claimedAt : null; + return claimed; }), remove: Effect.fn("relay.managed_endpoint_allocations.remove")(function* ( input: ManagedEndpointAllocationKey, @@ -399,7 +633,7 @@ export const make = Effect.gen(function* () { .where( and( whereAllocation(input), - eq(relayManagedEndpointAllocations.updatedAt, input.updatedAt), + eq(relayManagedEndpointAllocations.generation, input.generation), ), ) .returning({ userId: relayManagedEndpointAllocations.userId }) diff --git a/infra/relay/src/environments/ManagedEndpointProvider.test.ts b/infra/relay/src/environments/ManagedEndpointProvider.test.ts index 4d136658c8fd..d7cb7d67f9dd 100644 --- a/infra/relay/src/environments/ManagedEndpointProvider.test.ts +++ b/infra/relay/src/environments/ManagedEndpointProvider.test.ts @@ -33,7 +33,7 @@ const config = RelayConfiguration.RelayConfiguration.of({ }); interface TunnelCall { - readonly operation: "list" | "create" | "putConfiguration" | "getToken" | "delete"; + readonly operation: "get" | "list" | "create" | "putConfiguration" | "getToken" | "delete"; readonly input: unknown; } @@ -62,6 +62,16 @@ function allocationKey(input: { readonly userId: string; readonly environmentId: function makeTunnelClient(calls: TunnelCall[] = []) { return ManagedEndpointProvider.ManagedEndpointTunnelClient.of({ + get: (tunnelId) => + Effect.sync(() => { + calls.push({ operation: "get", input: tunnelId }); + return { + id: tunnelId, + name: "managed-tunnel", + status: "down", + connsInactiveAt: "2026-06-01T00:00:00.000Z", + }; + }), list: (request) => Effect.sync(() => { calls.push({ operation: "list", input: request }); @@ -91,6 +101,23 @@ function makeTunnelClient(calls: TunnelCall[] = []) { function makePersistentTunnelClient(calls: TunnelCall[] = []) { let tunnel: { readonly id: string; readonly name: string } | null = null; return ManagedEndpointProvider.ManagedEndpointTunnelClient.of({ + get: (tunnelId) => + Effect.suspend(() => { + calls.push({ operation: "get", input: tunnelId }); + return tunnel === null + ? Effect.fail( + new ManagedEndpointProvider.ManagedEndpointTunnelClientError({ + operation: "get", + tunnelId, + cause: { _tag: "NotFound" }, + }), + ) + : Effect.succeed({ + ...tunnel, + status: "down", + connsInactiveAt: "2026-06-01T00:00:00.000Z", + }); + }), list: (request) => Effect.sync(() => { calls.push({ operation: "list", input: request }); @@ -159,6 +186,7 @@ function makeDnsClient( function makeAllocations(calls: AllocationCall[] = []) { const allocations = new Map(); + const recoveryEnabled = new Set(); let generation = 0; const mutate = ( key: string, @@ -168,7 +196,11 @@ function makeAllocations(calls: AllocationCall[] = []) { ) => { const allocation = allocations.get(key); if (allocation !== undefined) { - allocations.set(key, { ...change(allocation), updatedAt: `generation-${++generation}` }); + allocations.set(key, { + ...change(allocation), + generation: allocation.generation + 1, + updatedAt: `generation-${++generation}`, + }); } }; return ManagedEndpointAllocations.ManagedEndpointAllocations.of({ @@ -185,7 +217,9 @@ function makeAllocations(calls: AllocationCall[] = []) { tunnelId: null, dnsRecordId: null, readyAt: null, + origin: null, updatedAt: `generation-${++generation}`, + generation: 0, }; allocations.set(allocationKey(input), allocation); return allocation; @@ -193,27 +227,62 @@ function makeAllocations(calls: AllocationCall[] = []) { recordTunnel: (input) => Effect.sync(() => { calls.push({ operation: "recordTunnel", input }); + const current = allocations.get(allocationKey(input)); + if (current?.generation !== input.generation) { + return null; + } mutate(allocationKey(input), (allocation) => ({ ...allocation, tunnelId: input.tunnelId, + readyAt: allocation.tunnelId === input.tunnelId ? allocation.readyAt : null, })); + return allocations.get(allocationKey(input))?.generation ?? null; }), recordDns: (input) => Effect.sync(() => { calls.push({ operation: "recordDns", input }); + const current = allocations.get(allocationKey(input)); + if (current?.generation !== input.generation || current.tunnelId !== input.tunnelId) { + return null; + } mutate(allocationKey(input), (allocation) => ({ ...allocation, dnsRecordId: input.dnsRecordId, })); + return allocations.get(allocationKey(input))?.generation ?? null; }), markReady: (input) => Effect.sync(() => { calls.push({ operation: "markReady", input }); + const current = allocations.get(allocationKey(input)); + if (current?.generation !== input.generation || current.tunnelId !== input.tunnelId) { + return false; + } mutate(allocationKey(input), (allocation) => ({ ...allocation, readyAt: "2026-06-02T00:00:00.000Z", + origin: input.origin, })); + return true; + }), + enableRecovery: (input) => + Effect.sync(() => { + const allocation = allocations.get(allocationKey(input)); + if (allocation?.tunnelId !== input.tunnelId) { + return false; + } + recoveryEnabled.add(allocationKey(input)); + return true; }), + listByTunnelNames: (tunnelNames) => + Effect.sync(() => + [...allocations.values()] + .filter((allocation) => tunnelNames.includes(allocation.tunnelName)) + .map((allocation) => ({ + ...allocation, + recoveryEnabled: recoveryEnabled.has(allocationKey(allocation)), + })), + ), claimRelease: (input) => Effect.sync(() => { calls.push({ operation: "claimRelease", input }); @@ -221,22 +290,29 @@ function makeAllocations(calls: AllocationCall[] = []) { if ( allocation === undefined || allocation.tunnelId !== input.tunnelId || - allocation.updatedAt !== input.updatedAt + allocation.generation !== input.generation ) { - return false; + return null; } mutate(allocationKey(input), (current) => current); - return true; + return allocations.get(allocationKey(input))?.generation ?? null; + }), + withClaimedTunnel: (input, effect) => + Effect.suspend(() => { + const current = allocations.get(allocationKey(input)); + return current?.tunnelId === input.tunnelId && current.generation === input.generation + ? Effect.asSome(effect) + : Effect.succeedNone; }), claimDeprovision: (input) => Effect.sync(() => { calls.push({ operation: "claimDeprovision", input }); const allocation = allocations.get(allocationKey(input)); - if (allocation === undefined || allocation.updatedAt !== input.updatedAt) { + if (allocation === undefined || allocation.generation !== input.generation) { return null; } mutate(allocationKey(input), (current) => current); - return allocations.get(allocationKey(input))?.updatedAt ?? null; + return allocations.get(allocationKey(input))?.generation ?? null; }), remove: (input) => Effect.sync(() => { @@ -247,7 +323,7 @@ function makeAllocations(calls: AllocationCall[] = []) { Effect.sync(() => { calls.push({ operation: "removeClaimed", input }); const allocation = allocations.get(allocationKey(input)); - if (allocation === undefined || allocation.updatedAt !== input.updatedAt) { + if (allocation === undefined || allocation.generation !== input.generation) { return false; } allocations.delete(allocationKey(input)); @@ -306,6 +382,7 @@ function expectedManagedTunnelName(environmentId: string, userId = "user_ABC"): describe("ManagedEndpointProvider", () => { it.effect("does not require the deployment RuntimeContext when building the Worker layer", () => { const tunnelClient = { + get: () => Effect.succeed({ id: "tunnel-id", name: "managed-tunnel" }), list: () => Effect.succeed({ result: [] }), create: (request: { readonly name: string }) => Effect.succeed({ id: "tunnel-id", name: request.name }), @@ -731,7 +808,8 @@ describe("ManagedEndpointProvider", () => { }).pipe(Effect.andThen(Effect.fail(failure))), deleteRecord: () => Effect.void, }); - const layer = providerLayer(makePersistentTunnelClient(), dnsClient, makeAllocations()); + const allocations = makeAllocations(); + const layer = providerLayer(makePersistentTunnelClient(), dnsClient, allocations); return Effect.gen(function* () { const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; @@ -754,6 +832,10 @@ describe("ManagedEndpointProvider", () => { "createRecord", "updateRecord", ]); + expect(yield* allocations.get(request)).toMatchObject({ + tunnelId: "tunnel-id", + readyAt: "2026-06-02T00:00:00.000Z", + }); }).pipe(Effect.provide(layer)); }); @@ -914,7 +996,7 @@ describe("ManagedEndpointProvider", () => { // longer matches what the release loaded, so the claim fails. const outdated = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ ...allocations, - claimRelease: () => Effect.succeed(false), + claimRelease: () => Effect.succeed(null), }); const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), outdated); @@ -939,6 +1021,457 @@ describe("ManagedEndpointProvider", () => { }).pipe(Effect.provide(layer)); }); + it.effect("does not release a tunnel when the requested tunnel id is outdated", () => { + const tunnelCalls: TunnelCall[] = []; + const layer = providerLayer( + makePersistentTunnelClient(tunnelCalls), + makeDnsClient(), + makeAllocations(), + ); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + + expect(yield* provider.release({ ...key, expectedTunnelId: "old-tunnel-id" })).toBe(false); + expect(tunnelCalls.map((call) => call.operation)).not.toContain("delete"); + }).pipe(Effect.provide(layer)); + }); + + it.effect("keeps a tunnel when a provision replaces an ordinary release generation", () => { + const tunnelCalls: TunnelCall[] = []; + const allocations = makeAllocations(); + let replaceAfterClaim = false; + const replaced = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + claimRelease: (input) => + allocations.claimRelease(input).pipe( + Effect.tap((claimedGeneration) => { + if (claimedGeneration === null || replaceAfterClaim) return Effect.void; + replaceAfterClaim = true; + return allocations + .recordTunnel({ + userId: input.userId, + environmentId: input.environmentId, + tunnelId: "replacement-tunnel", + generation: claimedGeneration, + }) + .pipe(Effect.asVoid); + }), + ), + }); + const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + + expect(yield* provider.release(key)).toBe(false); + expect(tunnelCalls.map((call) => call.operation)).not.toContain("delete"); + }).pipe(Effect.provide(layer)); + }); + + it.effect("does no Cloudflare work when the registered origin is unchanged", () => { + const tunnelCalls: TunnelCall[] = []; + const layer = providerLayer(makePersistentTunnelClient(tunnelCalls)); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + const origin = { localHttpHost: "127.0.0.1", localHttpPort: 3773 } as const; + const provisioned = yield* provider.provision({ ...key, origin }); + tunnelCalls.length = 0; + + expect( + yield* provider.reconcileOrigin({ + ...key, + tunnelId: provisioned.runtime.tunnelId!, + origin, + endpoint: provisioned.endpoint, + }), + ).toBe("ready"); + expect(tunnelCalls).toEqual([]); + }).pipe(Effect.provide(layer)); + }); + + it.effect("updates Cloudflare ingress once when the registered port changes", () => { + const tunnelCalls: TunnelCall[] = []; + const layer = providerLayer(makePersistentTunnelClient(tunnelCalls)); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + const provisioned = yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + tunnelCalls.length = 0; + + expect( + yield* provider.reconcileOrigin({ + ...key, + tunnelId: provisioned.runtime.tunnelId!, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 4884 }, + endpoint: provisioned.endpoint, + }), + ).toBe("ready"); + expect(tunnelCalls).toEqual([ + { + operation: "putConfiguration", + input: { + tunnelId: "tunnel-id", + tunnelConfig: { + ingress: [ + { + hostname: expectedManagedHostname("env_ABC"), + service: "http://127.0.0.1:4884", + }, + { service: "http_status:404" }, + ], + }, + }, + }, + ]); + }).pipe(Effect.provide(layer)); + }); + + it.effect("requires recovery when Cloudflare reports the registered tunnel is missing", () => { + const tunnelCalls: TunnelCall[] = []; + const baseTunnelClient = makePersistentTunnelClient(tunnelCalls); + let tunnelMissing = false; + const tunnelClient = ManagedEndpointProvider.ManagedEndpointTunnelClient.of({ + ...baseTunnelClient, + putConfiguration: (tunnelId, tunnelConfig) => + tunnelMissing + ? Effect.fail( + new ManagedEndpointProvider.ManagedEndpointTunnelClientError({ + operation: "put-configuration", + tunnelId, + cause: { _tag: "TunnelNotFound" }, + }), + ) + : baseTunnelClient.putConfiguration(tunnelId, tunnelConfig), + }); + const layer = providerLayer(tunnelClient); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + const provisioned = yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + tunnelMissing = true; + + expect( + yield* provider.reconcileOrigin({ + ...key, + tunnelId: provisioned.runtime.tunnelId!, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 4884 }, + endpoint: provisioned.endpoint, + }), + ).toBe("recovery_required"); + }).pipe(Effect.provide(layer)); + }); + + it.effect("fails origin sync when the allocation generation changes", () => { + const allocations = makeAllocations(); + let loseClaim = false; + const changed = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + withClaimedTunnel: (input, effect) => + loseClaim ? Effect.succeedNone : allocations.withClaimedTunnel(input, effect), + }); + const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + const provisioned = yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + loseClaim = true; + + const error = yield* Effect.flip( + provider.reconcileOrigin({ + ...key, + tunnelId: provisioned.runtime.tunnelId!, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 4884 }, + endpoint: provisioned.endpoint, + }), + ); + expect(error).toMatchObject({ + _tag: "ManagedEndpointProvisioningFailed", + stage: "sync-origin", + }); + }).pipe(Effect.provide(layer)); + }); + + it.effect("rejects an active endpoint that does not match the allocation hostname", () => { + const layer = providerLayer(makePersistentTunnelClient()); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + const provisioned = yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + + const error = yield* Effect.flip( + provider.reconcileOrigin({ + ...key, + tunnelId: provisioned.runtime.tunnelId!, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + endpoint: { + ...provisioned.endpoint, + httpBaseUrl: "https://different-host.t3code.test/", + }, + }), + ); + expect(error).toMatchObject({ + _tag: "ManagedEndpointProvisioningFailed", + stage: "verify-endpoint", + }); + }).pipe(Effect.provide(layer)); + }); + + it.effect( + "keeps a newly created tunnel available for retry after losing its allocation claim", + () => { + const tunnelCalls: TunnelCall[] = []; + const allocations = makeAllocations(); + let changeGeneration = true; + const changed = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + recordTunnel: (input) => + Effect.gen(function* () { + if (changeGeneration) { + changeGeneration = false; + yield* allocations.claimDeprovision(input); + } + return yield* allocations.recordTunnel(input); + }), + }); + const layer = providerLayer( + makePersistentTunnelClient(tunnelCalls), + makeDnsClient(), + changed, + ); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const input = { + userId: "user_ABC", + environmentId: "env_ABC", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }; + const error = yield* Effect.flip(provider.provision(input)); + + expect(error).toMatchObject({ + _tag: "ManagedEndpointProvisioningFailed", + stage: "record-tunnel", + }); + expect(tunnelCalls.map((call) => call.operation)).toEqual(["list", "create"]); + expect((yield* provider.provision(input)).runtime.tunnelId).toBe("tunnel-id"); + expect(tunnelCalls.filter((call) => call.operation === "create")).toHaveLength(1); + expect(tunnelCalls.filter((call) => call.operation === "delete")).toEqual([]); + }).pipe(Effect.provide(layer)); + }, + ); + + it.effect("does not overwrite DNS when tunnel ownership changes during provisioning", () => { + const allocations = makeAllocations(); + const changed = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + recordDns: () => Effect.succeed(null), + }); + const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const error = yield* Effect.flip( + provider.provision({ + userId: "user_ABC", + environmentId: "env_ABC", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + + expect(error).toMatchObject({ + _tag: "ManagedEndpointProvisioningFailed", + stage: "record-dns", + }); + }).pipe(Effect.provide(layer)); + }); + + it.effect("does not change tunnel ingress after another provision takes ownership", () => { + const tunnelCalls: TunnelCall[] = []; + const allocations = makeAllocations(); + const changed = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + withClaimedTunnel: () => Effect.succeedNone, + }); + const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), changed); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const error = yield* Effect.flip( + provider.provision({ + userId: "user_ABC", + environmentId: "env_ABC", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + + expect(error).toMatchObject({ + _tag: "ManagedEndpointProvisioningFailed", + stage: "configure-tunnel", + }); + expect(tunnelCalls.map((call) => call.operation)).not.toContain("putConfiguration"); + }).pipe(Effect.provide(layer)); + }); + + it.effect("does not change DNS after another provision takes ownership", () => { + const dnsCalls: DnsCall[] = []; + const allocations = makeAllocations(); + let lockCount = 0; + const changed = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + withClaimedTunnel: (input, effect) => + ++lockCount === 1 ? allocations.withClaimedTunnel(input, effect) : Effect.succeedNone, + }); + const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(dnsCalls), changed); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const error = yield* Effect.flip( + provider.provision({ + userId: "user_ABC", + environmentId: "env_ABC", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + + expect(error).toMatchObject({ + _tag: "ManagedEndpointProvisioningFailed", + stage: "record-dns", + }); + expect(dnsCalls).toEqual([]); + }).pipe(Effect.provide(layer)); + }); + + it.effect("does not mark a superseded tunnel allocation as ready", () => { + const allocations = makeAllocations(); + const changed = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + markReady: () => Effect.succeed(false), + }); + const layer = providerLayer(makePersistentTunnelClient(), makeDnsClient(), changed); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const error = yield* Effect.flip( + provider.provision({ + userId: "user_ABC", + environmentId: "env_ABC", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + + expect(error).toMatchObject({ + _tag: "ManagedEndpointProvisioningFailed", + stage: "mark-allocation-ready", + }); + }).pipe(Effect.provide(layer)); + }); + + it.effect("keeps a tunnel that reconnects before scheduled deletion", () => { + const tunnelCalls: TunnelCall[] = []; + const tunnelClient = ManagedEndpointProvider.ManagedEndpointTunnelClient.of({ + ...makePersistentTunnelClient(tunnelCalls), + get: (tunnelId) => + Effect.succeed({ + id: tunnelId, + name: expectedManagedTunnelName("env_ABC"), + status: "healthy", + connsInactiveAt: null, + }), + }); + const layer = providerLayer(tunnelClient, makeDnsClient(), makeAllocations()); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + + expect( + yield* provider.release({ + ...key, + expectedTunnelId: "tunnel-id", + expectedStatus: "down", + expectedInactiveBefore: "2026-06-01T00:05:00.000Z", + }), + ).toBe(false); + expect(tunnelCalls.map((call) => call.operation)).not.toContain("delete"); + }).pipe(Effect.provide(layer)); + }); + + it.effect("keeps a tunnel when a new provision replaces the release generation", () => { + const tunnelCalls: TunnelCall[] = []; + const allocations = makeAllocations(); + const replaced = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + ...allocations, + claimRelease: (input) => + allocations.claimRelease(input).pipe( + Effect.tap((claimedGeneration) => + claimedGeneration === null + ? Effect.void + : allocations + .recordTunnel({ + userId: input.userId, + environmentId: input.environmentId, + tunnelId: "replacement-tunnel", + generation: claimedGeneration, + }) + .pipe(Effect.asVoid), + ), + ), + }); + const layer = providerLayer(makePersistentTunnelClient(tunnelCalls), makeDnsClient(), replaced); + + return Effect.gen(function* () { + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const key = { userId: "user_ABC", environmentId: "env_ABC" } as const; + yield* provider.provision({ + ...key, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + + expect( + yield* provider.release({ + ...key, + expectedTunnelId: "tunnel-id", + expectedStatus: "down", + expectedInactiveBefore: "2026-06-01T00:05:00.000Z", + }), + ).toBe(false); + expect(tunnelCalls.map((call) => call.operation)).not.toContain("delete"); + }).pipe(Effect.provide(layer)); + }); + it.effect("treats an already deleted tunnel as successfully released", () => { const notFound = { _tag: "NotFound" } as const; const tunnelClient = ManagedEndpointProvider.ManagedEndpointTunnelClient.of({ diff --git a/infra/relay/src/environments/ManagedEndpointProvider.ts b/infra/relay/src/environments/ManagedEndpointProvider.ts index 977568c2745c..892ac09a6e50 100644 --- a/infra/relay/src/environments/ManagedEndpointProvider.ts +++ b/infra/relay/src/environments/ManagedEndpointProvider.ts @@ -3,6 +3,7 @@ import * as Cloudflare from "alchemy/Cloudflare"; import * as Arr from "effect/Array"; import * as Context from "effect/Context"; import * as Crypto from "effect/Crypto"; +import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Encoding from "effect/Encoding"; import * as Layer from "effect/Layer"; @@ -52,6 +53,9 @@ const ManagedEndpointProvisioningStage = Schema.Literals([ "record-dns", "get-tunnel-token", "mark-allocation-ready", + "load-allocation", + "verify-endpoint", + "sync-origin", ]); export class ManagedEndpointProvisioningFailed extends Schema.TaggedError()( @@ -76,6 +80,7 @@ export class ManagedEndpointProvisioningFailed extends Schema.TaggedError Effect.Effect; + readonly reconcileOrigin: (input: { + readonly userId: string; + readonly environmentId: string; + readonly tunnelId: string; + readonly origin: RelayManagedEndpointOrigin; + readonly endpoint: RelayManagedEndpoint; + }) => Effect.Effect; /** * Captures the allocation generation owned by an unlink before its link * revocation commits. Passing this target to `deprovision` prevents a @@ -150,7 +164,7 @@ export class ManagedEndpointProvider extends Context.Service< readonly userId: string; readonly environmentId: string; readonly target?: ManagedEndpointDeprovisionTarget | null; - }) => Effect.Effect; + }) => Effect.Effect; /** * Deletes the provisioned Cloudflare tunnel while keeping the allocation * (hostname + tunnel name reservation) and DNS record. Cloudflare bills per @@ -166,16 +180,34 @@ export class ManagedEndpointProvider extends Context.Service< readonly release: (input: { readonly userId: string; readonly environmentId: string; + readonly expectedTunnelId?: string; + readonly expectedInactiveBefore?: string; + readonly expectedStatus?: "inactive" | "down"; }) => Effect.Effect; } >()("t3code-relay/environments/ManagedEndpointProvider") {} -interface ManagedEndpointTunnel { +export interface ManagedEndpointTunnel { readonly id?: string | null; readonly name?: string | null; + readonly status?: string | null; + readonly createdAt?: string | null; + readonly connsInactiveAt?: string | null; +} + +export interface ManagedEndpointTunnelListRequest { + readonly isDeleted: false; + readonly name?: string; + readonly includePrefix?: string; + readonly status?: "inactive" | "down"; + readonly existedAt?: string; + readonly wasInactiveAt?: string; + readonly page?: number; + readonly perPage?: number; } const ManagedEndpointTunnelClientOperation = Schema.Literals([ + "get", "list", "create", "put-configuration", @@ -201,11 +233,18 @@ export class ManagedEndpointTunnelClientError extends Schema.TaggedError Effect.Effect< - { readonly result: ReadonlyArray }, + readonly get: ( + tunnelId: string, + ) => Effect.Effect; + readonly list: (request: ManagedEndpointTunnelListRequest) => Effect.Effect< + { + readonly result: ReadonlyArray; + readonly resultInfo?: { + readonly page?: number | null; + readonly perPage?: number | null; + readonly totalCount?: number | null; + } | null; + }, ManagedEndpointTunnelClientError >; readonly create: (request: { @@ -333,17 +372,17 @@ function isLoopbackOrigin(origin: RelayManagedEndpointOrigin): boolean { ); } -function isNotFoundCause(cause: unknown): boolean { +export function isManagedEndpointNotFound(cause: unknown): boolean { if (typeof cause !== "object" || cause === null) { return false; } - if ("_tag" in cause && cause._tag === "NotFound") { + if ("_tag" in cause && (cause._tag === "NotFound" || cause._tag === "TunnelNotFound")) { return true; } if ("status" in cause && cause.status === 404) { return true; } - return "cause" in cause && isNotFoundCause(cause.cause); + return "cause" in cause && isManagedEndpointNotFound(cause.cause); } type ManagedEndpointClientError = ManagedEndpointTunnelClientError | ManagedEndpointDnsClientError; @@ -355,9 +394,9 @@ const ignoreNotFound = ( Effect.asVoid, Effect.catchTags({ ManagedEndpointTunnelClientError: (error) => - isNotFoundCause(error.cause) ? Effect.void : Effect.fail(error), + isManagedEndpointNotFound(error.cause) ? Effect.void : Effect.fail(error), ManagedEndpointDnsClientError: (error) => - isNotFoundCause(error.cause) ? Effect.void : Effect.fail(error), + isManagedEndpointNotFound(error.cause) ? Effect.void : Effect.fail(error), }), ); @@ -399,7 +438,7 @@ export const make = Effect.gen(function* () { Effect.as(true), Effect.catchTags({ ManagedEndpointDnsClientError: (error) => - isNotFoundCause(error.cause) ? Effect.succeed(false) : Effect.fail(error), + isManagedEndpointNotFound(error.cause) ? Effect.succeed(false) : Effect.fail(error), }), ); if (checkpointedRecordUpdated) { @@ -432,8 +471,9 @@ export const make = Effect.gen(function* () { ? updateExistingDnsRecords(records, preferredDnsRecordId, dnsRecord) : Effect.fail(createError), ), - Effect.flatMap((dnsRecordId) => - dnsRecordId === null ? Effect.fail(createError) : Effect.succeed(dnsRecordId), + Effect.filterOrFail( + (dnsRecordId) => dnsRecordId !== null, + () => createError, ), ), }), @@ -455,8 +495,129 @@ export const make = Effect.gen(function* () { }, ); + const reconcileOrigin = Effect.fn("relay.managed_endpoint_provider.reconcile_origin")( + function* (input: { + readonly userId: string; + readonly environmentId: string; + readonly tunnelId: string; + readonly origin: RelayManagedEndpointOrigin; + readonly endpoint: RelayManagedEndpoint; + }) { + if (!isLoopbackOrigin(input.origin)) { + return yield* new ManagedEndpointOriginNotAllowed({ + userId: input.userId, + environmentId: input.environmentId, + host: input.origin.localHttpHost, + port: input.origin.localHttpPort, + }); + } + const allocation = yield* allocations.get(input).pipe( + Effect.mapError( + (cause) => + new ManagedEndpointProvisioningFailed({ + ...input, + stage: "load-allocation", + cause, + }), + ), + ); + if ( + allocation === null || + allocation.tunnelId !== input.tunnelId || + allocation.dnsRecordId === null || + allocation.readyAt === null + ) { + return "recovery_required"; + } + const cf = yield* requireCloudflareSettings(config, input); + const recordedEndpoint = ManagedEndpointAllocations.resolveReadyManagedEndpoint({ + allocation, + baseDomain: cf.baseDomain, + }); + if ( + recordedEndpoint === null || + recordedEndpoint.httpBaseUrl !== input.endpoint.httpBaseUrl || + recordedEndpoint.wsBaseUrl !== input.endpoint.wsBaseUrl || + recordedEndpoint.providerKind !== input.endpoint.providerKind + ) { + return yield* new ManagedEndpointProvisioningFailed({ + ...input, + stage: "verify-endpoint", + hostname: allocation.hostname, + }); + } + if ( + allocation.origin?.localHttpHost === input.origin.localHttpHost && + allocation.origin.localHttpPort === input.origin.localHttpPort + ) { + return "ready"; + } + + const updated = yield* allocations + .withClaimedTunnel( + { + userId: input.userId, + environmentId: input.environmentId, + tunnelId: input.tunnelId, + generation: allocation.generation, + }, + tunnels + .putConfiguration(input.tunnelId, { + ingress: [ + { + hostname: allocation.hostname, + service: formatOriginService(input.origin), + }, + { service: "http_status:404" }, + ], + }) + .pipe( + Effect.as("configured" as const), + Effect.catchTags({ + ManagedEndpointTunnelClientError: (error) => + isManagedEndpointNotFound(error.cause) + ? Effect.succeed("missing" as const) + : Effect.fail(error), + }), + Effect.filterOrElse( + (result): result is "missing" => result === "missing", + () => + allocations + .markReady({ + ...input, + generation: allocation.generation, + }) + .pipe( + Effect.map((updated) => + updated ? ("configured" as const) : ("stale" as const), + ), + ), + ), + ), + ) + .pipe( + Effect.mapError( + (cause) => + new ManagedEndpointProvisioningFailed({ + ...input, + stage: "sync-origin", + cause, + }), + ), + ); + if (Option.isNone(updated) || updated.value === "stale") { + return yield* new ManagedEndpointProvisioningFailed({ + ...input, + stage: "sync-origin", + }); + } + return updated.value === "configured" ? "ready" : "recovery_required"; + }, + ); + return ManagedEndpointProvider.of({ prepareDeprovision, + reconcileOrigin, deprovision: Effect.fn("relay.managed_endpoint_provider.deprovision")(function* (input) { yield* Effect.annotateCurrentSpan({ "relay.user_id": input.userId, @@ -465,13 +626,13 @@ export const make = Effect.gen(function* () { const allocation = input.target === undefined ? yield* prepareDeprovision(input) : input.target; if (allocation === null) { - return; + return true; } - const claimedAt = yield* allocations + const claimedGeneration = yield* allocations .claimDeprovision({ userId: input.userId, environmentId: input.environmentId, - updatedAt: allocation.updatedAt, + generation: allocation.generation, }) .pipe( Effect.mapError( @@ -485,55 +646,86 @@ export const make = Effect.gen(function* () { }), ), ); - if (claimedAt === null) { - return; - } - const dnsRecordId = allocation.dnsRecordId; - if (dnsRecordId !== null) { - yield* ignoreNotFound(dns.deleteRecord(dnsRecordId)).pipe( - Effect.mapError( - (cause) => - new ManagedEndpointDeprovisioningFailed({ - ...input, - stage: "delete-dns-record", - dnsRecordId, - cause, - }), - ), - ); + if (claimedGeneration === null) { + return false; } const tunnelId = allocation.tunnelId; - if (tunnelId !== null) { - yield* ignoreNotFound(tunnels.delete(tunnelId)).pipe( - Effect.mapError( - (cause) => - new ManagedEndpointDeprovisioningFailed({ - ...input, - stage: "delete-tunnel", - tunnelId, - cause, - }), - ), - ); + const deprovision = Effect.gen(function* () { + const dnsRecordId = allocation.dnsRecordId; + if (dnsRecordId !== null) { + yield* ignoreNotFound(dns.deleteRecord(dnsRecordId)).pipe( + Effect.mapError( + (cause) => + new ManagedEndpointDeprovisioningFailed({ + ...input, + stage: "delete-dns-record", + dnsRecordId, + cause, + }), + ), + ); + } + if (tunnelId !== null) { + yield* ignoreNotFound(tunnels.delete(tunnelId)).pipe( + Effect.mapError( + (cause) => + new ManagedEndpointDeprovisioningFailed({ + ...input, + stage: "delete-tunnel", + tunnelId, + cause, + }), + ), + ); + } + return yield* allocations + .removeClaimed({ + userId: input.userId, + environmentId: input.environmentId, + generation: claimedGeneration, + }) + .pipe( + Effect.mapError( + (cause) => + new ManagedEndpointDeprovisioningFailed({ + ...input, + stage: "remove-allocation", + ...(allocation.tunnelId === null ? {} : { tunnelId: allocation.tunnelId }), + ...(allocation.dnsRecordId === null + ? {} + : { dnsRecordId: allocation.dnsRecordId }), + cause, + }), + ), + ); + }); + if (tunnelId === null) { + return yield* deprovision; } - yield* allocations - .removeClaimed({ - userId: input.userId, - environmentId: input.environmentId, - updatedAt: claimedAt, - }) + const removed = yield* allocations + .withClaimedTunnel( + { + userId: input.userId, + environmentId: input.environmentId, + tunnelId, + generation: claimedGeneration, + }, + deprovision, + ) .pipe( - Effect.mapError( - (cause) => - new ManagedEndpointDeprovisioningFailed({ - ...input, - stage: "remove-allocation", - ...(allocation.tunnelId === null ? {} : { tunnelId: allocation.tunnelId }), - ...(allocation.dnsRecordId === null ? {} : { dnsRecordId: allocation.dnsRecordId }), - cause, - }), - ), + Effect.catchTags({ + ManagedEndpointAllocationPersistenceError: (cause) => + Effect.fail( + new ManagedEndpointDeprovisioningFailed({ + ...input, + stage: "claim-deprovision", + tunnelId, + cause, + }), + ), + }), ); + return Option.getOrElse(removed, () => false); }), release: Effect.fn("relay.managed_endpoint_provider.release")(function* (input) { yield* Effect.annotateCurrentSpan({ @@ -554,19 +746,22 @@ export const make = Effect.gen(function* () { if (allocation === null || tunnelId === null) { return true; } + if (input.expectedTunnelId !== undefined && input.expectedTunnelId !== tunnelId) { + return false; + } // Claim the release against the allocation's current generation before // touching Cloudflare. A provision racing this release (fast environment - // restart) rewrites updatedAt when it records its tunnel, so a stale + // restart) increments the generation when it records its tunnel, so a stale // claim means the recorded tunnel may already back a fresh connector and // must be left alive. A provision that starts after the claim instead // fails loudly on the deleted tunnel and the client-side retry // provisions a replacement. - const claimed = yield* allocations + const claimedGeneration = yield* allocations .claimRelease({ userId: input.userId, environmentId: input.environmentId, tunnelId, - updatedAt: allocation.updatedAt, + generation: allocation.generation, }) .pipe( Effect.mapError( @@ -579,10 +774,10 @@ export const make = Effect.gen(function* () { }), ), ); - if (!claimed) { + if (claimedGeneration === null) { return false; } - yield* ignoreNotFound(tunnels.delete(tunnelId)).pipe( + const deleteTunnel = ignoreNotFound(tunnels.delete(tunnelId)).pipe( Effect.mapError( (cause) => new ManagedEndpointDeprovisioningFailed({ @@ -593,13 +788,103 @@ export const make = Effect.gen(function* () { }), ), ); + if (input.expectedInactiveBefore !== undefined && input.expectedStatus !== undefined) { + const expectedStatus = input.expectedStatus; + const inactiveBefore = input.expectedInactiveBefore; + const currentTunnel = yield* tunnels.get(tunnelId).pipe( + Effect.asSome, + Effect.catchTags({ + ManagedEndpointTunnelClientError: (cause) => + isManagedEndpointNotFound(cause.cause) + ? Effect.succeedNone + : Effect.fail( + new ManagedEndpointDeprovisioningFailed({ + ...input, + stage: "load-tunnel", + tunnelId, + cause, + }), + ), + }), + ); + if (Option.isNone(currentTunnel)) { + return true; + } + const inactiveAt = + expectedStatus === "down" + ? currentTunnel.value.connsInactiveAt + : currentTunnel.value.createdAt; + if ( + currentTunnel.value.id !== tunnelId || + currentTunnel.value.status !== expectedStatus || + typeof inactiveAt !== "string" + ) { + return false; + } + const inactiveTime = DateTime.make(inactiveAt); + const cutoff = DateTime.make(inactiveBefore); + if ( + Option.isNone(inactiveTime) || + Option.isNone(cutoff) || + inactiveTime.value.epochMilliseconds > cutoff.value.epochMilliseconds + ) { + return false; + } + } + const released = yield* allocations + .withClaimedTunnel( + { + userId: input.userId, + environmentId: input.environmentId, + tunnelId, + generation: claimedGeneration, + }, + Effect.gen(function* () { + const finalGeneration = yield* allocations + .claimRelease({ + userId: input.userId, + environmentId: input.environmentId, + tunnelId, + generation: claimedGeneration, + }) + .pipe( + Effect.mapError( + (cause) => + new ManagedEndpointDeprovisioningFailed({ + ...input, + stage: "claim-release", + tunnelId, + cause, + }), + ), + ); + if (finalGeneration === null) { + return false; + } + yield* deleteTunnel; + return true; + }), + ) + .pipe( + Effect.catchTags({ + ManagedEndpointAllocationPersistenceError: (cause) => + Effect.fail( + new ManagedEndpointDeprovisioningFailed({ + ...input, + stage: "claim-release", + tunnelId, + cause, + }), + ), + }), + ); // The recorded tunnelId is now stale, but the allocation row is left // untouched deliberately: connect/status authorization requires a fully // recorded allocation, and an offline environment must keep reporting // "offline" (health probe fails) rather than "not authorized". The next // provision lists tunnels by name, finds none, creates a replacement and // re-records the fresh id. - return true; + return Option.getOrElse(released, () => false); }), provision: Effect.fn("relay.managed_endpoint_provider.provision")(function* (input) { yield* Effect.annotateCurrentSpan({ @@ -717,11 +1002,12 @@ export const make = Effect.gen(function* () { }); } const tunnel = { id: tunnelResponse.id, name: tunnelResponse.name }; - yield* allocations + const tunnelGeneration = yield* allocations .recordTunnel({ userId: input.userId, environmentId: input.environmentId, tunnelId: tunnel.id, + generation: allocation.generation, }) .pipe( Effect.mapError( @@ -737,31 +1023,78 @@ export const make = Effect.gen(function* () { }), ), ); + if (tunnelGeneration === null) { + // A newer provision can adopt this tunnel by name at any point after + // our claim fails. Leave it available for that provision or a retry. + return yield* new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "record-tunnel", + hostname, + tunnelName, + tunnelId: tunnel.id, + }); + } - yield* tunnels - .putConfiguration(tunnel.id, { - ingress: [ - { - hostname, - service: formatOriginService(input.origin), - }, - { service: "http_status:404" }, - ], - }) + const configured = yield* allocations + .withClaimedTunnel( + { + userId: input.userId, + environmentId: input.environmentId, + tunnelId: tunnel.id, + generation: tunnelGeneration, + }, + tunnels + .putConfiguration(tunnel.id, { + ingress: [ + { + hostname, + service: formatOriginService(input.origin), + }, + { service: "http_status:404" }, + ], + }) + .pipe( + Effect.mapError( + (cause) => + new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "configure-tunnel", + hostname, + tunnelName, + tunnelId: tunnel.id, + cause, + }), + ), + ), + ) .pipe( - Effect.mapError( - (cause) => - new ManagedEndpointProvisioningFailed({ - userId: input.userId, - environmentId: input.environmentId, - stage: "configure-tunnel", - hostname, - tunnelName, - tunnelId: tunnel.id, - cause, - }), - ), + Effect.catchTags({ + ManagedEndpointAllocationPersistenceError: (cause) => + Effect.fail( + new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "configure-tunnel", + hostname, + tunnelName, + tunnelId: tunnel.id, + cause, + }), + ), + }), ); + if (Option.isNone(configured)) { + return yield* new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "configure-tunnel", + hostname, + tunnelName, + tunnelId: tunnel.id, + }); + } const dnsRecord = { type: "CNAME", @@ -771,31 +1104,61 @@ export const make = Effect.gen(function* () { proxied: true, } as const; - const dnsRecordId = yield* ensureDnsRecord(hostname, allocation.dnsRecordId, dnsRecord).pipe( - Effect.mapError( - (cause) => - new ManagedEndpointProvisioningFailed({ - userId: input.userId, - environmentId: input.environmentId, - stage: "ensure-dns-record", + const recordedDns = yield* allocations + .withClaimedTunnel( + { + userId: input.userId, + environmentId: input.environmentId, + tunnelId: tunnel.id, + generation: tunnelGeneration, + }, + Effect.gen(function* () { + const dnsRecordId = yield* ensureDnsRecord( hostname, - tunnelName, - tunnelId: tunnel.id, - ...(allocation.dnsRecordId === null ? {} : { dnsRecordId: allocation.dnsRecordId }), - cause, - }), - ), - ); - yield* allocations - .recordDns({ - userId: input.userId, - environmentId: input.environmentId, - dnsRecordId, - }) - .pipe( - Effect.mapError( - (cause) => - new ManagedEndpointProvisioningFailed({ + allocation.dnsRecordId, + dnsRecord, + ).pipe( + Effect.mapError( + (cause) => + new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "ensure-dns-record", + hostname, + tunnelName, + tunnelId: tunnel.id, + ...(allocation.dnsRecordId === null + ? {} + : { dnsRecordId: allocation.dnsRecordId }), + cause, + }), + ), + ); + const dnsGeneration = yield* allocations + .recordDns({ + userId: input.userId, + environmentId: input.environmentId, + dnsRecordId, + tunnelId: tunnel.id, + generation: tunnelGeneration, + }) + .pipe( + Effect.mapError( + (cause) => + new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "record-dns", + hostname, + tunnelName, + tunnelId: tunnel.id, + dnsRecordId, + cause, + }), + ), + ); + if (dnsGeneration === null) { + return yield* new ManagedEndpointProvisioningFailed({ userId: input.userId, environmentId: input.environmentId, stage: "record-dns", @@ -803,10 +1166,38 @@ export const make = Effect.gen(function* () { tunnelName, tunnelId: tunnel.id, dnsRecordId, - cause, - }), - ), + }); + } + return { dnsRecordId, dnsGeneration }; + }), + ) + .pipe( + Effect.catchTags({ + ManagedEndpointAllocationPersistenceError: (cause) => + Effect.fail( + new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "record-dns", + hostname, + tunnelName, + tunnelId: tunnel.id, + cause, + }), + ), + }), ); + if (Option.isNone(recordedDns)) { + return yield* new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "record-dns", + hostname, + tunnelName, + tunnelId: tunnel.id, + }); + } + const { dnsRecordId, dnsGeneration } = recordedDns.value; const connectorToken = yield* tunnels.getToken(tunnel.id).pipe( Effect.mapError( @@ -823,10 +1214,13 @@ export const make = Effect.gen(function* () { }), ), ); - yield* allocations + const ready = yield* allocations .markReady({ userId: input.userId, environmentId: input.environmentId, + tunnelId: tunnel.id, + generation: dnsGeneration, + origin: input.origin, }) .pipe( Effect.mapError( @@ -843,6 +1237,17 @@ export const make = Effect.gen(function* () { }), ), ); + if (!ready) { + return yield* new ManagedEndpointProvisioningFailed({ + userId: input.userId, + environmentId: input.environmentId, + stage: "mark-allocation-ready", + hostname, + tunnelName, + tunnelId: tunnel.id, + dnsRecordId, + }); + } return { endpoint: managedEndpointForHostname(hostname), @@ -865,16 +1270,30 @@ export const layerCloudflareBindings = ( alchemyRuntimeContext: Alchemy.BaseRuntimeContext, ) => layer.pipe( - Layer.provide( + Layer.provideMerge( Layer.mergeAll( layerTunnelClient({ + get: (tunnelId) => + tunnelClient.get(tunnelId).pipe( + Effect.timeout("8 seconds"), + Effect.mapError( + (cause) => + new ManagedEndpointTunnelClientError({ + operation: "get", + tunnelId, + cause, + }), + ), + Effect.provideService(Alchemy.RuntimeContext, alchemyRuntimeContext), + ), list: (request) => tunnelClient.list(request).pipe( + Effect.timeout("8 seconds"), Effect.mapError( (cause) => new ManagedEndpointTunnelClientError({ operation: "list", - tunnelName: request.name, + ...(request.name === undefined ? {} : { tunnelName: request.name }), cause, }), ), @@ -882,6 +1301,7 @@ export const layerCloudflareBindings = ( ), create: (request) => tunnelClient.create(request).pipe( + Effect.timeout("8 seconds"), Effect.mapError( (cause) => new ManagedEndpointTunnelClientError({ @@ -894,6 +1314,7 @@ export const layerCloudflareBindings = ( ), putConfiguration: (tunnelId, config) => tunnelClient.putConfiguration(tunnelId, config).pipe( + Effect.timeout("8 seconds"), Effect.mapError( (cause) => new ManagedEndpointTunnelClientError({ @@ -906,6 +1327,7 @@ export const layerCloudflareBindings = ( ), getToken: (tunnelId) => tunnelClient.getToken(tunnelId).pipe( + Effect.timeout("8 seconds"), Effect.mapError( (cause) => new ManagedEndpointTunnelClientError({ @@ -918,6 +1340,7 @@ export const layerCloudflareBindings = ( ), delete: (tunnelId) => tunnelClient.delete(tunnelId).pipe( + Effect.timeout("8 seconds"), Effect.mapError( (cause) => new ManagedEndpointTunnelClientError({ @@ -932,6 +1355,7 @@ export const layerCloudflareBindings = ( layerDnsClient({ listRecords: (hostname) => dnsClient.listDnsRecords({ search: hostname }).pipe( + Effect.timeout("8 seconds"), Effect.map((response) => response.result.filter( (record): record is typeof record & { readonly id: string } => @@ -951,6 +1375,7 @@ export const layerCloudflareBindings = ( ), createRecord: (request) => dnsClient.createDnsRecord(request).pipe( + Effect.timeout("8 seconds"), Effect.map((response) => ({ id: response.id })), Effect.mapError( (cause) => @@ -964,6 +1389,7 @@ export const layerCloudflareBindings = ( ), updateRecord: (dnsRecordId, request) => dnsClient.updateDnsRecord(dnsRecordId, request).pipe( + Effect.timeout("8 seconds"), Effect.mapError( (cause) => new ManagedEndpointDnsClientError({ @@ -977,6 +1403,7 @@ export const layerCloudflareBindings = ( ), deleteRecord: (dnsRecordId) => dnsClient.deleteDnsRecord(dnsRecordId).pipe( + Effect.timeout("8 seconds"), Effect.mapError( (cause) => new ManagedEndpointDnsClientError({ diff --git a/infra/relay/src/environments/ManagedEndpointReaper.test.ts b/infra/relay/src/environments/ManagedEndpointReaper.test.ts new file mode 100644 index 000000000000..9a2d62e36067 --- /dev/null +++ b/infra/relay/src/environments/ManagedEndpointReaper.test.ts @@ -0,0 +1,810 @@ +import { describe, expect, it } from "@effect/vitest"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Redacted from "effect/Redacted"; +import * as TestClock from "effect/testing/TestClock"; +import * as Tracer from "effect/Tracer"; + +import * as RelayConfiguration from "../Config.ts"; +import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; +import * as ManagedEndpointProvider from "./ManagedEndpointProvider.ts"; +import * as ManagedEndpointReaper from "./ManagedEndpointReaper.ts"; + +const NOW = "2026-08-25T12:00:00.000Z"; +const NOW_MILLIS = DateTime.makeUnsafe(NOW).epochMilliseconds; +const PREFIX = "t3coderelay-managedendpoint-prod-"; + +function tunnel(input: { + readonly id: string; + readonly suffix: string; + readonly status: "down" | "inactive" | "healthy" | "degraded"; + readonly timestamp?: string | null; + readonly prefix?: string; +}): ManagedEndpointProvider.ManagedEndpointTunnel { + return { + id: input.id, + name: `${input.prefix ?? PREFIX}${input.suffix}`, + status: input.status, + ...(input.timestamp === undefined + ? {} + : input.status === "inactive" + ? { createdAt: input.timestamp } + : { connsInactiveAt: input.timestamp }), + }; +} + +function recoverableOwners( + tunnels: ReadonlyArray, +): ReadonlyArray { + return tunnels.map((entry) => allocation({ tunnelId: entry.id!, recoveryEnabled: true })); +} + +function allocation(input: { + readonly tunnelId: string | null; + readonly recoveryEnabled: boolean; +}): ManagedEndpointAllocations.ManagedEndpointTunnelAllocation { + return { + userId: "user-1", + environmentId: `environment-${input.tunnelId ?? "pending"}`, + hostname: `${input.tunnelId ?? "pending"}.example.test`, + tunnelId: input.tunnelId, + tunnelName: `${PREFIX}aaaaaaaaaaaaaaaa`, + dnsRecordId: "dns-1", + readyAt: "2026-08-25T11:00:00.000Z", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + updatedAt: "2026-08-25T11:00:00.000Z", + generation: 1, + recoveryEnabled: input.recoveryEnabled, + }; +} + +function harness(input?: { + readonly tunnels?: ReadonlyArray; + readonly allocations?: ReadonlyArray; + readonly namespace?: string; + readonly failTunnelId?: string; + readonly rateLimitedTunnelId?: string; + readonly failAllDeletes?: boolean; + readonly failDeleteWhen?: (tunnelId: string) => boolean; + readonly missingOnDeleteTunnelId?: string; + readonly missingOnGetTunnelId?: string; + readonly reserveOnGetTunnelId?: string; + readonly refreshedTunnels?: ReadonlyMap; + readonly skipTunnelId?: string; + readonly cleanupMode?: RelayConfiguration.ManagedEndpointCleanupMode; +}) { + const listRequests: ManagedEndpointProvider.ManagedEndpointTunnelListRequest[] = []; + const deleted: string[] = []; + const releases: Array< + Parameters[0] + > = []; + const remaining = [...(input?.tunnels ?? [])]; + const recorded = (input?.allocations ?? []).map((entry) => { + const matching = remaining.find((candidate) => candidate.id === entry.tunnelId); + return typeof matching?.name === "string" ? { ...entry, tunnelName: matching.name } : entry; + }); + const tunnelClient = ManagedEndpointProvider.ManagedEndpointTunnelClient.of({ + get: (tunnelId) => + Effect.suspend(() => { + if (tunnelId === input?.missingOnGetTunnelId) { + return Effect.fail( + new ManagedEndpointProvider.ManagedEndpointTunnelClientError({ + operation: "get", + tunnelId, + cause: { _tag: "NotFound" }, + }), + ); + } + const found = + input?.refreshedTunnels?.get(tunnelId) ?? + remaining.find((candidate) => candidate.id === tunnelId); + if (found === undefined) { + return Effect.fail( + new ManagedEndpointProvider.ManagedEndpointTunnelClientError({ + operation: "get", + tunnelId, + cause: { _tag: "NotFound" }, + }), + ); + } + if (tunnelId === input?.reserveOnGetTunnelId && typeof found.name === "string") { + recorded.push({ + ...allocation({ tunnelId, recoveryEnabled: false }), + tunnelName: found.name, + }); + } + return Effect.succeed(found); + }), + list: (request) => + Effect.sync(() => { + listRequests.push(request); + const matching = remaining.filter((entry) => entry.status === request.status); + const start = ((request.page ?? 1) - 1) * (request.perPage ?? 100); + return { + result: matching.slice(start, start + (request.perPage ?? 100)), + resultInfo: { + page: request.page ?? 1, + perPage: request.perPage ?? 100, + totalCount: matching.length, + }, + }; + }), + create: () => Effect.die("unused"), + putConfiguration: () => Effect.die("unused"), + getToken: () => Effect.die("unused"), + delete: (tunnelId) => + input?.failAllDeletes === true || + input?.failDeleteWhen?.(tunnelId) === true || + tunnelId === input?.failTunnelId || + tunnelId === input?.rateLimitedTunnelId || + tunnelId === input?.missingOnDeleteTunnelId + ? Effect.fail( + new ManagedEndpointProvider.ManagedEndpointTunnelClientError({ + operation: "delete", + tunnelId, + cause: + tunnelId === input?.missingOnDeleteTunnelId + ? { _tag: "NotFound" } + : tunnelId === input?.rateLimitedTunnelId + ? { + cause: { + _tag: "TooManyRequests", + message: "Cloudflare rate limit exceeded", + retryAfter: 60, + }, + } + : "Cloudflare refused the deletion", + }), + ) + : Effect.sync(() => { + deleted.push(tunnelId); + const index = remaining.findIndex((candidate) => candidate.id === tunnelId); + if (index !== -1) { + remaining.splice(index, 1); + } + }), + }); + const allocationService = ManagedEndpointAllocations.ManagedEndpointAllocations.of({ + get: () => Effect.die("unused"), + reserve: () => Effect.die("unused"), + recordTunnel: () => Effect.die("unused"), + recordDns: () => Effect.die("unused"), + markReady: () => Effect.die("unused"), + enableRecovery: () => Effect.die("unused"), + listByTunnelNames: (tunnelNames) => + Effect.succeed(recorded.filter((entry) => tunnelNames.includes(entry.tunnelName))), + claimRelease: () => Effect.die("unused"), + withClaimedTunnel: () => Effect.die("unused"), + claimDeprovision: () => Effect.die("unused"), + remove: () => Effect.die("unused"), + removeClaimed: () => Effect.die("unused"), + }); + const provider = ManagedEndpointProvider.ManagedEndpointProvider.of({ + provision: () => Effect.die("unused"), + reconcileOrigin: () => Effect.die("unused"), + prepareDeprovision: () => Effect.die("unused"), + deprovision: () => Effect.die("unused"), + release: (request) => + Effect.gen(function* () { + releases.push(request); + if (request.expectedTunnelId === input?.skipTunnelId) { + return false; + } + if (request.expectedTunnelId !== undefined) { + // Surface Cloudflare failures the same way the real release does. + yield* tunnelClient.delete(request.expectedTunnelId).pipe( + Effect.mapError( + (cause) => + new ManagedEndpointProvider.ManagedEndpointDeprovisioningFailed({ + stage: "delete-tunnel", + userId: request.userId, + environmentId: request.environmentId, + tunnelId: request.expectedTunnelId!, + cause, + }), + ), + ); + } + return true; + }), + }); + const config = RelayConfiguration.RelayConfiguration.of({ + relayIssuer: "https://relay.example.test", + apns: { + environment: "sandbox", + teamId: "team-id", + keyId: "key-id", + privateKey: Redacted.make("private-key"), + bundleId: "com.t3tools.t3code.dev", + }, + apnsDeliveryJobSigningSecret: Redacted.make("job-secret"), + clerkSecretKey: Redacted.make("clerk-secret"), + clerkPublishableKey: "pk_test_test", + clerkJwtAudience: "t3-code-relay", + cloudMintPrivateKey: Redacted.make("cloud-private-key"), + cloudMintPublicKey: "cloud-public-key", + managedEndpointBaseDomain: "example.test", + managedEndpointNamespace: input?.namespace ?? "prod", + managedEndpointCleanupMode: input?.cleanupMode ?? "enabled", + }); + + return { + listRequests, + deleted, + releases, + layer: ManagedEndpointReaper.layer.pipe( + Layer.provide( + Layer.mergeAll( + RelayConfiguration.layer(config), + ManagedEndpointProvider.layerTunnelClient(tunnelClient), + Layer.succeed(ManagedEndpointProvider.ManagedEndpointProvider, provider), + Layer.succeed(ManagedEndpointAllocations.ManagedEndpointAllocations, allocationService), + ), + ), + ), + }; +} + +describe("ManagedEndpointReaper", () => { + it.effect("removes expired down and inactive tunnels from recoverable environments", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "down-1", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:55:00.000Z", + }), + tunnel({ + id: "inactive-1", + suffix: "bbbbbbbbbbbbbbbb", + status: "inactive", + timestamp: "2026-08-25T10:59:00.000Z", + }), + ], + allocations: [ + allocation({ tunnelId: "down-1", recoveryEnabled: true }), + allocation({ tunnelId: "inactive-1", recoveryEnabled: true }), + ], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + scanned: 2, + deleted: 2, + skippedLegacy: 0, + failed: 0, + }); + expect(state.deleted).toEqual(["down-1", "inactive-1"]); + expect(state.releases.map((request) => request.expectedTunnelId)).toEqual([ + "down-1", + "inactive-1", + ]); + expect(state.listRequests).toEqual([ + { + isDeleted: false, + includePrefix: PREFIX, + status: "down", + existedAt: "2026-08-25T11:55:00.000Z", + wasInactiveAt: "2026-08-25T11:55:00.000Z", + page: 1, + perPage: 100, + }, + { + isDeleted: false, + includePrefix: PREFIX, + status: "inactive", + existedAt: "2026-08-25T11:00:00.000Z", + page: 1, + perPage: 100, + }, + ]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("keeps a tunnel that never connected until it is an hour old", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "pairing", + suffix: "cccccccccccccccc", + status: "inactive", + timestamp: "2026-08-25T11:30:00.000Z", + }), + ], + allocations: [allocation({ tunnelId: "pairing", recoveryEnabled: true })], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect((yield* reaper.sweep).deleted).toBe(0); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("keeps recent tunnels, other stages, and tunnels without valid timestamps", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "recent", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:55:01.000Z", + }), + tunnel({ + id: "other-stage", + prefix: `${PREFIX}julius-`, + suffix: "bbbbbbbbbbbbbbbb", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + tunnel({ + id: "missing-time", + suffix: "cccccccccccccccc", + status: "inactive", + timestamp: null, + }), + ], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + scanned: 0, + deleted: 0, + skippedLegacy: 0, + failed: 0, + }); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("keeps tunnels owned by environments that cannot recover yet", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "legacy", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ], + allocations: [allocation({ tunnelId: "legacy", recoveryEnabled: false })], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + scanned: 1, + deleted: 0, + skippedLegacy: 1, + failed: 0, + }); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("counts an expired tunnel with no allocation instead of deleting it", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "orphan", + suffix: "cccccccccccccccc", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + scanned: 1, + wouldDelete: 0, + deleted: 0, + skippedOrphan: 1, + }); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + it.effect("keeps an expired tunnel while its allocation is incomplete", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "unrecorded", + suffix: "aaaaaaaaaaaaaaaa", + status: "inactive", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ], + allocations: [allocation({ tunnelId: null, recoveryEnabled: false })], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect((yield* reaper.sweep).deleted).toBe(0); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("does not count a tunnel that was replaced before its release", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "replaced", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ], + allocations: [allocation({ tunnelId: "replaced", recoveryEnabled: true })], + skipTunnelId: "replaced", + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect((yield* reaper.sweep).deleted).toBe(0); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("stops the sweep after a structured Cloudflare rate limit error", () => { + const state = harness({ + tunnels: [ + tunnel({ + id: "limited", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + tunnel({ + id: "next", + suffix: "bbbbbbbbbbbbbbbb", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ], + allocations: [ + allocation({ tunnelId: "limited", recoveryEnabled: true }), + allocation({ tunnelId: "next", recoveryEnabled: true }), + ], + rateLimitedTunnelId: "limited", + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + attempted: 1, + deleted: 0, + failed: 1, + truncated: true, + }); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("continues past a page of older hosts to find recoverable tunnels", () => { + const entries = Array.from({ length: 101 }, (_, index) => + tunnel({ + id: `tunnel-${index}`, + suffix: index.toString(16).padStart(16, "0"), + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ); + const state = harness({ + tunnels: entries, + allocations: entries.map((entry, index) => + allocation({ tunnelId: entry.id!, recoveryEnabled: index === 100 }), + ), + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + scanned: 101, + deleted: 1, + skippedLegacy: 100, + failed: 0, + }); + expect(state.deleted).toEqual(["tunnel-100"]); + expect( + state.listRequests + .filter((request) => request.status === "down") + .map((request) => request.page), + ).toEqual([1, 2]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("collects every page before deletions shift Cloudflare pagination", () => { + const entries = Array.from({ length: 120 }, (_, index) => + tunnel({ + id: `tunnel-${index}`, + suffix: index.toString(16).padStart(16, "0"), + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ); + const state = harness({ + tunnels: entries, + allocations: entries.map((entry, index) => + allocation({ tunnelId: entry.id!, recoveryEnabled: index < 50 || index >= 100 }), + ), + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + scanned: 120, + deleted: 70, + skippedLegacy: 50, + failed: 0, + }); + expect(state.deleted).toContain("tunnel-119"); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("limits each cleanup run to 100 tunnel deletions", () => { + const entries = Array.from({ length: 105 }, (_, index) => + tunnel({ + id: `tunnel-${index}`, + suffix: index.toString(16).padStart(16, "0"), + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ); + const state = harness({ tunnels: entries, allocations: recoverableOwners(entries) }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect((yield* reaper.sweep).deleted).toBe(100); + expect(state.deleted).toHaveLength(100); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("records dry-run counters on the sweep span", () => { + const spans: Array = []; + const tracer = Tracer.make({ + span: (options) => { + const span = new Tracer.NativeSpan(options); + spans.push(span); + return span; + }, + }); + const expired = [ + tunnel({ + id: "recoverable", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + tunnel({ + id: "legacy", + suffix: "bbbbbbbbbbbbbbbb", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ]; + const state = harness({ + cleanupMode: "dry-run", + tunnels: expired, + allocations: [ + allocation({ tunnelId: "recoverable", recoveryEnabled: true }), + allocation({ tunnelId: "legacy", recoveryEnabled: false }), + ], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + yield* reaper.sweep; + const sweepSpan = spans.find((span) => span.name === "relay.managed_endpoint_reaper.sweep"); + expect(Object.fromEntries(sweepSpan?.attributes ?? [])).toMatchObject({ + "relay.managed_endpoint_reaper.mode": "dry-run", + "relay.managed_endpoint_reaper.scanned": 2, + "relay.managed_endpoint_reaper.wouldDelete": 1, + "relay.managed_endpoint_reaper.skippedLegacy": 1, + "relay.managed_endpoint_reaper.deleted": 0, + }); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer), Effect.withTracer(tracer)); + }); + + it.effect("does no Cloudflare work while cleanup is off", () => { + const state = harness({ + cleanupMode: "off", + tunnels: [ + tunnel({ + id: "expired", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ], + allocations: [allocation({ tunnelId: "expired", recoveryEnabled: true })], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toEqual({ + mode: "off", + listRequests: 0, + scanned: 0, + attempted: 0, + deleted: 0, + wouldDelete: 0, + skippedLegacy: 0, + skippedOrphan: 0, + failed: 0, + truncated: false, + }); + expect(state.listRequests).toEqual([]); + expect(state.deleted).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("reports candidates without mutating them in dry-run mode", () => { + const state = harness({ + cleanupMode: "dry-run", + tunnels: [ + tunnel({ + id: "expired", + suffix: "aaaaaaaaaaaaaaaa", + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ], + allocations: [allocation({ tunnelId: "expired", recoveryEnabled: true })], + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + mode: "dry-run", + scanned: 1, + attempted: 0, + deleted: 0, + wouldDelete: 1, + }); + expect(state.deleted).toEqual([]); + expect(state.releases).toEqual([]); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("caps failed deletion attempts and Cloudflare list pages", () => { + const entries = Array.from({ length: 250 }, (_, index) => + tunnel({ + id: `failed-${index}`, + suffix: index.toString(16).padStart(16, "0"), + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ); + const state = harness({ + tunnels: entries, + allocations: recoverableOwners(entries), + failAllDeletes: true, + }); + + return Effect.gen(function* () { + yield* TestClock.setTime(NOW_MILLIS); + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + expect(yield* reaper.sweep).toMatchObject({ + attempted: 100, + deleted: 0, + failed: 100, + truncated: true, + }); + expect(state.listRequests.length).toBeLessThanOrEqual( + ManagedEndpointReaper.MANAGED_ENDPOINT_SWEEP_LIST_REQUEST_LIMIT, + ); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("rotates the deletion order so persistent failures do not starve later tunnels", () => { + const entries = Array.from({ length: 200 }, (_, index) => + tunnel({ + id: `tunnel-${index}`, + suffix: index.toString(16).padStart(16, "0"), + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ); + // The first 100 candidates always fail to delete. + const state = harness({ + tunnels: entries, + allocations: recoverableOwners(entries), + failDeleteWhen: (id) => Number(id.split("-")[1]) < 100, + }); + + return Effect.gen(function* () { + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + yield* TestClock.setTime(NOW_MILLIS); + yield* reaper.sweep; + yield* TestClock.setTime(NOW_MILLIS + 5 * 60 * 1_000); + yield* reaper.sweep; + const later = state.deleted.filter((id) => Number(id.split("-")[1]) >= 100); + expect(later.length).toBeGreaterThan(0); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("rotates bounded pages across a large legacy prefix", () => { + const entries = Array.from({ length: 1_000 }, (_, index) => + tunnel({ + id: `legacy-${index}`, + suffix: index.toString(16).padStart(16, "0"), + status: "down", + timestamp: "2026-08-25T11:00:00.000Z", + }), + ); + const state = harness({ + cleanupMode: "dry-run", + tunnels: entries, + allocations: entries.map((entry) => + allocation({ tunnelId: entry.id!, recoveryEnabled: false }), + ), + }); + + return Effect.gen(function* () { + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + yield* TestClock.setTime(NOW_MILLIS); + yield* reaper.sweep; + const firstPages = state.listRequests + .filter((request) => request.status === "down") + .map((request) => request.page); + state.listRequests.length = 0; + yield* TestClock.setTime(NOW_MILLIS + 5 * 60 * 1_000); + yield* reaper.sweep; + const secondPages = state.listRequests + .filter((request) => request.status === "down") + .map((request) => request.page); + expect(firstPages).not.toEqual(secondPages); + expect(firstPages).toHaveLength(5); + expect(secondPages).toHaveLength(5); + }).pipe(Effect.provide(state.layer)); + }); + + it.effect("rotates the attempt budget across both statuses over consecutive sweeps", () => { + const entries = (["down", "inactive"] as const).flatMap((status) => + Array.from({ length: 100 }, (_, index) => + tunnel({ + id: `${status}-${index}`, + suffix: `${status === "down" ? "a" : "b"}${index.toString(16).padStart(15, "0")}`, + status, + timestamp: "2026-08-25T10:00:00.000Z", + }), + ), + ); + const state = harness({ tunnels: entries, allocations: recoverableOwners(entries) }); + + return Effect.gen(function* () { + const reaper = yield* ManagedEndpointReaper.ManagedEndpointReaper; + yield* TestClock.setTime(NOW_MILLIS); + yield* reaper.sweep; + const firstStatus = state.deleted[0]!.split("-")[0]; + expect(state.deleted).toHaveLength(100); + expect(state.deleted.every((id) => id.startsWith(`${firstStatus}-`))).toBe(true); + + state.deleted.length = 0; + yield* TestClock.setTime(NOW_MILLIS + 5 * 60 * 1_000); + yield* reaper.sweep; + expect(state.deleted).toHaveLength(100); + expect(state.deleted.every((id) => !id.startsWith(`${firstStatus}-`))).toBe(true); + }).pipe(Effect.provide(state.layer)); + }); +}); diff --git a/infra/relay/src/environments/ManagedEndpointReaper.ts b/infra/relay/src/environments/ManagedEndpointReaper.ts new file mode 100644 index 000000000000..33524dbf0c5d --- /dev/null +++ b/infra/relay/src/environments/ManagedEndpointReaper.ts @@ -0,0 +1,308 @@ +import * as Context from "effect/Context"; +import * as DateTime from "effect/DateTime"; +import * as Effect from "effect/Effect"; +import * as Layer from "effect/Layer"; +import * as Option from "effect/Option"; + +import type { ManagedEndpointCleanupMode } from "../Config.ts"; +import * as RelayConfiguration from "../Config.ts"; +import { managedEndpointTunnelNamePrefix } from "../deploymentConfig.ts"; +import * as ManagedEndpointAllocations from "./ManagedEndpointAllocations.ts"; +import * as ManagedEndpointProvider from "./ManagedEndpointProvider.ts"; + +export const MANAGED_ENDPOINT_GRACE_PERIOD_MINUTES = 5; +// A tunnel that never connected is usually a link still being set up: a slow +// cloudflared download or a user who walked away mid-pairing. Give it an hour. +export const MANAGED_ENDPOINT_INACTIVE_GRACE_PERIOD_MINUTES = 60; +export const MANAGED_ENDPOINT_SWEEP_PAGE_SIZE = 100; +export const MANAGED_ENDPOINT_SWEEP_ATTEMPT_LIMIT = 100; +export const MANAGED_ENDPOINT_SWEEP_LIST_REQUEST_LIMIT = 10; + +export interface ManagedEndpointSweepResult { + readonly mode: ManagedEndpointCleanupMode; + readonly listRequests: number; + readonly scanned: number; + readonly attempted: number; + readonly deleted: number; + readonly wouldDelete: number; + readonly skippedLegacy: number; + readonly skippedOrphan: number; + readonly failed: number; + readonly truncated: boolean; +} + +export class ManagedEndpointReaper extends Context.Service< + ManagedEndpointReaper, + { + readonly sweep: Effect.Effect< + ManagedEndpointSweepResult, + | ManagedEndpointProvider.ManagedEndpointTunnelClientError + | ManagedEndpointAllocations.ManagedEndpointAllocationPersistenceError + >; + } +>()("t3code-relay/environments/ManagedEndpointReaper") {} + +function isExpiredManagedTunnel(input: { + readonly tunnel: ManagedEndpointProvider.ManagedEndpointTunnel; + readonly status: "down" | "inactive"; + readonly prefix: string; + readonly cutoff: DateTime.Utc; +}): input is typeof input & { + readonly tunnel: ManagedEndpointProvider.ManagedEndpointTunnel & { + readonly id: string; + readonly name: string; + }; +} { + const { tunnel, status, prefix, cutoff } = input; + if ( + typeof tunnel.id !== "string" || + typeof tunnel.name !== "string" || + tunnel.status !== status || + !tunnel.name.startsWith(prefix) || + !/^[a-f0-9]{16}$/u.test(tunnel.name.slice(prefix.length)) + ) { + return false; + } + const inactiveAt = status === "down" ? tunnel.connsInactiveAt : tunnel.createdAt; + if (typeof inactiveAt !== "string") { + return false; + } + const timestamp = DateTime.make(inactiveAt); + return Option.isSome(timestamp) && timestamp.value.epochMilliseconds <= cutoff.epochMilliseconds; +} + +function isRateLimited(cause: unknown): boolean { + if (typeof cause !== "object" || cause === null) { + return false; + } + if ("_tag" in cause && cause._tag === "TooManyRequests") { + return true; + } + if ("status" in cause && cause.status === 429) { + return true; + } + return "cause" in cause && isRateLimited(cause.cause); +} + +function rotatedPages(input: { + readonly totalCount: number | undefined; + readonly slot: number; + readonly limit: number; +}): ReadonlyArray { + if (input.limit <= 0) return []; + if (input.totalCount === undefined) { + return Array.from({ length: input.limit }, (_, index) => index + 2); + } + const laterPageCount = Math.max( + 0, + Math.ceil(input.totalCount / MANAGED_ENDPOINT_SWEEP_PAGE_SIZE) - 1, + ); + if (laterPageCount === 0) return []; + const count = Math.min(input.limit, laterPageCount); + const start = input.slot % laterPageCount; + return Array.from({ length: count }, (_, index) => 2 + ((start + index) % laterPageCount)); +} + +const emptyResult = (mode: ManagedEndpointCleanupMode): ManagedEndpointSweepResult => ({ + mode, + listRequests: 0, + scanned: 0, + attempted: 0, + deleted: 0, + wouldDelete: 0, + skippedLegacy: 0, + skippedOrphan: 0, + failed: 0, + truncated: false, +}); + +export const make = Effect.gen(function* () { + const config = yield* RelayConfiguration.RelayConfiguration; + const tunnels = yield* ManagedEndpointProvider.ManagedEndpointTunnelClient; + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + const provider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + + const sweep = Effect.gen(function* () { + const mode = config.managedEndpointCleanupMode ?? "off"; + const namespace = config.managedEndpointNamespace; + if (mode === "off" || !namespace) return emptyResult(mode); + + const now = yield* DateTime.now; + const cutoffFor = (status: "down" | "inactive") => + DateTime.subtract(now, { + minutes: + status === "down" + ? MANAGED_ENDPOINT_GRACE_PERIOD_MINUTES + : MANAGED_ENDPOINT_INACTIVE_GRACE_PERIOD_MINUTES, + }); + const prefix = managedEndpointTunnelNamePrefix(namespace); + const slot = Math.floor( + now.epochMilliseconds / (MANAGED_ENDPOINT_GRACE_PERIOD_MINUTES * 60 * 1_000), + ); + let listRequests = 0; + let truncated = false; + const expired: Array<{ + readonly tunnel: ManagedEndpointProvider.ManagedEndpointTunnel & { + readonly id: string; + readonly name: string; + }; + readonly status: "down" | "inactive"; + readonly cutoff: DateTime.Utc; + }> = []; + + for (const status of ["down", "inactive"] as const) { + const cutoff = cutoffFor(status); + const cutoffIso = DateTime.formatIso(cutoff); + const listPage = (page: number) => { + listRequests += 1; + return tunnels.list({ + isDeleted: false, + includePrefix: prefix, + status, + existedAt: cutoffIso, + ...(status === "down" ? { wasInactiveAt: cutoffIso } : {}), + page, + perPage: MANAGED_ENDPOINT_SWEEP_PAGE_SIZE, + }); + }; + const first = yield* listPage(1); + const totalCount = + typeof first.resultInfo?.totalCount === "number" ? first.resultInfo.totalCount : undefined; + const pages = rotatedPages({ + totalCount, + slot, + limit: Math.floor(MANAGED_ENDPOINT_SWEEP_LIST_REQUEST_LIMIT / 2) - 1, + }); + const responses = [first, ...(yield* Effect.forEach(pages, listPage, { concurrency: 1 }))]; + if ( + totalCount !== undefined && + Math.ceil(totalCount / MANAGED_ENDPOINT_SWEEP_PAGE_SIZE) > responses.length + ) { + truncated = true; + } else if ( + totalCount === undefined && + responses.at(-1)?.result.length === MANAGED_ENDPOINT_SWEEP_PAGE_SIZE + ) { + truncated = true; + } + for (const response of responses) { + expired.push( + ...response.result + .map((tunnel) => ({ tunnel, status, prefix, cutoff })) + .filter(isExpiredManagedTunnel) + .map(({ tunnel }) => ({ tunnel, status, cutoff })), + ); + } + } + + const collected = [...new Map(expired.map((entry) => [entry.tunnel.id, entry])).values()]; + // Start each sweep one attempt budget further along so a run of + // candidates whose deletes keep failing cannot hold the budget forever + // and starve everything listed after them. + const offset = + collected.length === 0 ? 0 : (slot * MANAGED_ENDPOINT_SWEEP_ATTEMPT_LIMIT) % collected.length; + const uniqueExpired = [...collected.slice(offset), ...collected.slice(0, offset)]; + const recorded = yield* allocations.listByTunnelNames( + uniqueExpired.map(({ tunnel }) => tunnel.name), + ); + const recordedByTunnelName = new Map( + recorded.map((allocation) => [allocation.tunnelName, allocation]), + ); + let attempted = 0; + let deleted = 0; + let wouldDelete = 0; + let skippedLegacy = 0; + let skippedOrphan = 0; + let failed = 0; + + for (const { tunnel, status, cutoff } of uniqueExpired) { + const cutoffIso = DateTime.formatIso(cutoff); + const allocation = recordedByTunnelName.get(tunnel.name); + if ( + allocation !== undefined && + allocation.tunnelId !== null && + allocation.tunnelId !== tunnel.id + ) { + continue; + } + const owner = allocation?.tunnelId === tunnel.id ? allocation : undefined; + if (owner !== undefined && !owner.recoveryEnabled) { + skippedLegacy += 1; + continue; + } + if (allocation !== undefined && owner === undefined) continue; + // A tunnel with no allocation row cannot be claimed, so a relink that + // adopts it by name races any delete here. Count it and leave it for a + // manual sweep instead. + if (owner === undefined) { + skippedOrphan += 1; + continue; + } + wouldDelete += 1; + if (mode === "dry-run") continue; + if (attempted >= MANAGED_ENDPOINT_SWEEP_ATTEMPT_LIMIT) { + truncated = true; + break; + } + attempted += 1; + const result = yield* provider + .release({ + userId: owner.userId, + environmentId: owner.environmentId, + expectedTunnelId: tunnel.id, + expectedInactiveBefore: cutoffIso, + expectedStatus: status, + }) + .pipe(Effect.result); + if (result._tag === "Failure") { + failed += 1; + yield* Effect.logWarning("Failed to delete an inactive managed tunnel", { + tunnelId: tunnel.id, + tunnelName: tunnel.name, + cause: result.failure, + }); + if (isRateLimited(result.failure)) { + truncated = true; + break; + } + } else if (result.success) { + deleted += 1; + yield* Effect.logInfo("Deleted an inactive managed tunnel", { + tunnelId: tunnel.id, + tunnelName: tunnel.name, + status, + }); + } + } + + return { + mode, + listRequests, + scanned: uniqueExpired.length, + attempted, + deleted, + wouldDelete, + skippedLegacy, + skippedOrphan, + failed, + truncated, + }; + }).pipe( + // Dry-run rollout reads these counters from the exported span. + Effect.tap((result) => + Effect.annotateCurrentSpan( + Object.fromEntries( + Object.entries(result).map(([key, value]) => [ + `relay.managed_endpoint_reaper.${key}`, + value, + ]), + ), + ), + ), + Effect.withSpan("relay.managed_endpoint_reaper.sweep"), + ); + + return ManagedEndpointReaper.of({ sweep }); +}); + +export const layer = Layer.effect(ManagedEndpointReaper, make); diff --git a/infra/relay/src/http/Api.test.ts b/infra/relay/src/http/Api.test.ts index af220de52619..7e8458949a80 100644 --- a/infra/relay/src/http/Api.test.ts +++ b/infra/relay/src/http/Api.test.ts @@ -7,13 +7,15 @@ import { import * as EnvironmentLinker from "../environments/EnvironmentLinker.ts"; import * as RelayTokens from "../auth/RelayTokens.ts"; import * as Devices from "../agentActivity/Devices.ts"; +import * as NodeCrypto from "node:crypto"; import { createClerkClient, verifyToken } from "@clerk/backend"; import * as NodeHttpPlatform from "@effect/platform-node/NodeHttpPlatform"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { describe, expect, it } from "@effect/vitest"; import { vi } from "vite-plus/test"; import * as Context from "effect/Context"; -import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; +import * as NodeCryptoLayer from "@effect/platform-node/NodeCrypto"; +import * as DateTime from "effect/DateTime"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Fiber from "effect/Fiber"; @@ -35,6 +37,7 @@ import { RelayEnvironmentPrincipal, RelayApi, } from "@t3tools/contracts/relay"; +import { RELAY_MANAGED_TUNNEL_RECOVERY_TYP, signRelayJwt } from "@t3tools/shared/relayJwt"; import { RELAY_HTTP_ROUTER_CONFIG, @@ -44,18 +47,22 @@ import { relayDocsRedirectRoute, relayEnvironmentAuthLayer, relayNotFoundRoute, + recoverEnvironmentTunnelRecord, + registerEnvironmentTunnelRecovery, relayDpopFailureReason, revokeEnvironmentLinkRecord, serverApi, traceRelayHttpRequestWith, unlinkEnvironmentRecord, verifyRelayClientBearerToken, + verifyEnvironmentTunnelRecoveryProof, withoutCapturedParentSpan, } from "./Api.ts"; import * as RelayConfiguration from "../Config.ts"; import * as RelayDb from "../db.ts"; import * as EnvironmentCredentials from "../environments/EnvironmentCredentials.ts"; import * as EnvironmentLinks from "../environments/EnvironmentLinks.ts"; +import * as ManagedEndpointAllocations from "../environments/ManagedEndpointAllocations.ts"; import * as ManagedEndpointProvider from "../environments/ManagedEndpointProvider.ts"; import * as AgentActivityPublisher from "../agentActivity/AgentActivityPublisher.ts"; import * as EnvironmentPublishSignatures from "../environments/EnvironmentPublishSignatures.ts"; @@ -122,7 +129,7 @@ describe("device listing compatibility", () => { Layer.provide( Layer.mergeAll( Layer.succeed(RelayConfiguration.RelayConfiguration, relaySettings), - NodeCrypto.layer, + NodeCryptoLayer.layer, Layer.mock(RelayTokens.RelayTokens, { resolveDpopAccessTokenScopes: () => null }), Layer.mock(EnvironmentLinker.EnvironmentLinker, {}), Layer.mock(EnvironmentLinks.EnvironmentLinks, {}), @@ -307,6 +314,9 @@ function relayUnlinkTestLayer(input?: { readonly revokeCredential?: EnvironmentCredentials.EnvironmentCredentials["Service"]["revokeForEnvironmentPublicKey"]; readonly prepareDeprovision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["prepareDeprovision"]; readonly deprovision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["deprovision"]; + readonly provision?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["provision"]; + readonly reconcileOrigin?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["reconcileOrigin"]; + readonly release?: ManagedEndpointProvider.ManagedEndpointProvider["Service"]["release"]; }) { return Layer.mergeAll( Layer.succeed( @@ -336,10 +346,11 @@ function relayUnlinkTestLayer(input?: { Layer.succeed( ManagedEndpointProvider.ManagedEndpointProvider, ManagedEndpointProvider.ManagedEndpointProvider.of({ - provision: () => Effect.die("unused provision"), + provision: input?.provision ?? (() => Effect.die("unused provision")), + reconcileOrigin: input?.reconcileOrigin ?? (() => Effect.succeed("ready")), prepareDeprovision: input?.prepareDeprovision ?? (() => Effect.succeed(null)), - deprovision: input?.deprovision ?? (() => Effect.void), - release: () => Effect.die("unused release"), + deprovision: input?.deprovision ?? (() => Effect.succeed(true)), + release: input?.release ?? (() => Effect.die("unused release")), }), ), ); @@ -357,6 +368,502 @@ const linkedEnvironmentRecord = { linkedAt: "2026-07-28T00:00:00.000Z", } as const; +describe("relay managed tunnel recovery", () => { + it.effect("binds recovery requests to the host, cloud user, and T3 service origin", () => + Effect.gen(function* () { + const keyPair = NodeCrypto.generateKeyPairSync("ed25519", { + privateKeyEncoding: { format: "pem", type: "pkcs8" }, + publicKeyEncoding: { format: "pem", type: "spki" }, + }); + const now = yield* DateTime.now; + const issuedAt = Math.floor(now.epochMilliseconds / 1_000); + const proof = yield* signRelayJwt({ + privateKey: keyPair.privateKey, + typ: RELAY_MANAGED_TUNNEL_RECOVERY_TYP, + payload: { + iss: "t3-env:environment-1", + aud: "https://relay.example.test", + sub: "environment-1", + jti: "recovery-proof", + iat: issuedAt, + exp: issuedAt + 60, + action: "recover", + environmentId: "environment-1", + cloudUserId: "user-1", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }, + }); + const request = { + action: "recover" as const, + proof, + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: keyPair.publicKey, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }; + + yield* verifyEnvironmentTunnelRecoveryProof(request); + + const wrongOwner = yield* Effect.flip( + verifyEnvironmentTunnelRecoveryProof({ ...request, userId: "user-2" }), + ); + expect(wrongOwner).toMatchObject({ _tag: "Unauthorized" }); + + const wrongOrigin = yield* Effect.flip( + verifyEnvironmentTunnelRecoveryProof({ + ...request, + origin: { localHttpHost: "127.0.0.1", localHttpPort: 5432 }, + }), + ); + expect(wrongOrigin).toMatchObject({ _tag: "Unauthorized" }); + + const wrongAction = yield* Effect.flip( + verifyEnvironmentTunnelRecoveryProof({ + action: "register", + proof, + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: keyPair.publicKey, + tunnelId: "existing-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + expect(wrongAction).toMatchObject({ _tag: "Unauthorized" }); + }).pipe(Effect.provideService(RelayConfiguration.RelayConfiguration, relaySettings)), + ); + + it.effect("rejects a signed registration proof for a different origin", () => + Effect.gen(function* () { + const keyPair = NodeCrypto.generateKeyPairSync("ed25519", { + privateKeyEncoding: { format: "pem", type: "pkcs8" }, + publicKeyEncoding: { format: "pem", type: "spki" }, + }); + const now = yield* DateTime.now; + const issuedAt = Math.floor(now.epochMilliseconds / 1_000); + const proof = yield* signRelayJwt({ + privateKey: keyPair.privateKey, + typ: RELAY_MANAGED_TUNNEL_RECOVERY_TYP, + payload: { + iss: "t3-env:environment-1", + aud: "https://relay.example.test", + sub: "environment-1", + jti: "registration-origin-proof", + iat: issuedAt, + exp: issuedAt + 60, + action: "register", + environmentId: "environment-1", + cloudUserId: "user-1", + tunnelId: "existing-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }, + }); + + const error = yield* Effect.flip( + verifyEnvironmentTunnelRecoveryProof({ + action: "register", + proof, + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: keyPair.publicKey, + tunnelId: "existing-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 5432 }, + }), + ); + + expect(error).toMatchObject({ _tag: "Unauthorized" }); + }).pipe(Effect.provideService(RelayConfiguration.RelayConfiguration, relaySettings)), + ); + + it.effect("registers recovery for an existing tunnel without provisioning it", () => { + let recoveryEnabledFor: { + readonly userId: string; + readonly environmentId: string; + readonly tunnelId: string; + readonly environmentPublicKey: string; + readonly origin: { readonly localHttpHost: string; readonly localHttpPort: number }; + } | null = null; + + return Effect.gen(function* () { + expect( + yield* registerEnvironmentTunnelRecovery({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "public-key", + tunnelId: "existing-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ).toEqual({ status: "ready" }); + expect(recoveryEnabledFor).toEqual({ + userId: "user-1", + environmentId: "environment-1", + tunnelId: "existing-tunnel", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => Effect.succeed(linkedEnvironmentRecord), + provision: () => Effect.die("registration must not provision a tunnel"), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: (input) => + Effect.sync(() => { + recoveryEnabledFor = input; + return true; + }), + }), + ), + ), + ); + }); + + it.effect("requests recovery without enabling a stale tunnel", () => { + let recoveryEnabled = false; + + return Effect.gen(function* () { + expect( + yield* registerEnvironmentTunnelRecovery({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "public-key", + tunnelId: "deleted-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ).toEqual({ status: "recovery_required" }); + expect(recoveryEnabled).toBe(false); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => Effect.succeed(linkedEnvironmentRecord), + reconcileOrigin: () => Effect.succeed("recovery_required"), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: () => + Effect.sync(() => { + recoveryEnabled = true; + return true; + }), + }), + ), + ), + ); + }); + + it.effect("rejects recovery registration for a different environment key", () => { + let recoveryEnabled = false; + + return Effect.gen(function* () { + const error = yield* Effect.flip( + registerEnvironmentTunnelRecovery({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "different-public-key", + tunnelId: "existing-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + + expect(error).toMatchObject({ _tag: "Unauthorized" }); + expect(recoveryEnabled).toBe(false); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => Effect.succeed(linkedEnvironmentRecord), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: () => + Effect.sync(() => { + recoveryEnabled = true; + return true; + }), + }), + ), + ), + ); + }); + + it.effect("rejects recovery registration when the recorded tunnel changed", () => + Effect.gen(function* () { + const error = yield* Effect.flip( + registerEnvironmentTunnelRecovery({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "public-key", + tunnelId: "stale-tunnel", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + + expect(error).toMatchObject({ _tag: "Unauthorized" }); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => Effect.succeed(linkedEnvironmentRecord), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: () => Effect.succeed(false), + }), + ), + ), + ), + ); + + it.effect("recovers a linked environment and marks its tunnel as recoverable", () => { + let recoveryEnabledFor: { + readonly userId: string; + readonly environmentId: string; + readonly tunnelId: string; + readonly environmentPublicKey: string; + } | null = null; + const runtime = { + providerKind: "cloudflare_tunnel" as const, + connectorToken: "replacement-token", + tunnelId: "replacement-tunnel", + }; + + return Effect.gen(function* () { + expect( + yield* recoverEnvironmentTunnelRecord({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ).toEqual({ + endpoint: linkedEnvironmentRecord.endpoint, + endpointRuntime: runtime, + }); + expect(recoveryEnabledFor).toEqual({ + userId: "user-1", + environmentId: "environment-1", + tunnelId: "replacement-tunnel", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => Effect.succeed(linkedEnvironmentRecord), + provision: () => + Effect.succeed({ + endpoint: linkedEnvironmentRecord.endpoint, + runtime, + }), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: (input) => + Effect.sync(() => { + recoveryEnabledFor = input; + return true; + }), + }), + ), + ), + ); + }); + + it.effect("rejects a credential from a different environment owner", () => { + let provisioned = false; + + return Effect.gen(function* () { + const error = yield* Effect.flip( + recoverEnvironmentTunnelRecord({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "different-public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + expect(error).toMatchObject({ _tag: "Unauthorized" }); + expect(provisioned).toBe(false); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => Effect.succeed(linkedEnvironmentRecord), + provision: () => + Effect.sync(() => { + provisioned = true; + return { + endpoint: linkedEnvironmentRecord.endpoint, + runtime: { providerKind: "cloudflare_tunnel", connectorToken: "token" }, + }; + }), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: () => Effect.die("unused"), + }), + ), + ), + ); + }); + + it.effect("does not recover a publish-only environment", () => + Effect.gen(function* () { + const error = yield* Effect.flip( + recoverEnvironmentTunnelRecord({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + expect(error).toMatchObject({ _tag: "Unauthorized" }); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => + Effect.succeed({ + ...linkedEnvironmentRecord, + endpoint: { + ...linkedEnvironmentRecord.endpoint, + providerKind: "manual" as const, + }, + }), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: () => Effect.die("unused"), + }), + ), + ), + ), + ); + + it.effect("rejects a recovered tunnel that changes the linked endpoint", () => { + let recoveryEnabled = false; + const cleaned: Array = []; + + return Effect.gen(function* () { + const error = yield* Effect.flip( + recoverEnvironmentTunnelRecord({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + expect(error).toMatchObject({ _tag: "Unauthorized" }); + expect(recoveryEnabled).toBe(false); + expect(cleaned).toEqual(["replacement-tunnel"]); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => Effect.succeed(linkedEnvironmentRecord), + provision: () => + Effect.succeed({ + endpoint: { + httpBaseUrl: "https://different.example.test/", + wsBaseUrl: "wss://different.example.test/ws", + providerKind: "cloudflare_tunnel", + }, + runtime: { + providerKind: "cloudflare_tunnel", + connectorToken: "token", + tunnelId: "replacement-tunnel", + }, + }), + prepareDeprovision: () => Effect.die("must keep the active allocation"), + deprovision: () => Effect.die("must keep the active link DNS"), + release: ({ expectedTunnelId }) => + Effect.sync(() => { + if (expectedTunnelId) { + cleaned.push(expectedTunnelId); + } + return true; + }), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: () => + Effect.sync(() => { + recoveryEnabled = true; + return true; + }), + }), + ), + ), + ); + }); + + it.effect.each([ + { state: "removed", currentLink: null }, + { + state: "publish-only", + currentLink: { + ...linkedEnvironmentRecord, + endpoint: { + ...linkedEnvironmentRecord.endpoint, + providerKind: "manual" as const, + }, + }, + }, + ])("removes a recovered tunnel when its link becomes $state", ({ currentLink }) => { + let lookups = 0; + const cleaned: Array = []; + const target = { + userId: "user-1", + environmentId: "environment-1", + hostname: "environment-1.example.test", + tunnelId: "replacement-tunnel", + tunnelName: "environment-1-tunnel", + dnsRecordId: "dns-1", + readyAt: "2026-07-28T00:00:00.000Z", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + updatedAt: "replacement-generation", + generation: 3, + } satisfies ManagedEndpointProvider.ManagedEndpointDeprovisionTarget; + + return Effect.gen(function* () { + const error = yield* Effect.flip( + recoverEnvironmentTunnelRecord({ + userId: "user-1", + environmentId: "environment-1", + environmentPublicKey: "public-key", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + }), + ); + expect(error).toMatchObject({ _tag: "Unauthorized" }); + expect(cleaned).toEqual(["replacement-tunnel"]); + }).pipe( + Effect.provide( + Layer.merge( + relayUnlinkTestLayer({ + getForUser: () => + Effect.sync(() => (++lookups === 1 ? linkedEnvironmentRecord : currentLink)), + provision: () => + Effect.succeed({ + endpoint: linkedEnvironmentRecord.endpoint, + runtime: { + providerKind: "cloudflare_tunnel", + connectorToken: "replacement-token", + tunnelId: "replacement-tunnel", + }, + }), + prepareDeprovision: () => Effect.succeed(target), + deprovision: ({ target: captured }) => + Effect.sync(() => { + if (captured?.tunnelId) { + cleaned.push(captured.tunnelId); + } + return true; + }), + }), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations)({ + enableRecovery: () => Effect.succeed(false), + }), + ), + ), + ); + }); +}); + describe("relay environment unlink", () => { it.effect("revokes the link and its credentials in one database transaction", () => { const calls: Array = []; @@ -401,7 +908,9 @@ describe("relay environment unlink", () => { tunnelName: "environment-1-tunnel", dnsRecordId: "dns-1", readyAt: "2026-07-28T00:00:00.000Z", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, updatedAt: "generation-before-unlink", + generation: 1, } satisfies ManagedEndpointProvider.ManagedEndpointDeprovisionTarget; return Effect.gen(function* () { @@ -450,6 +959,7 @@ describe("relay environment unlink", () => { Effect.sync(() => { expect(request.target).toBe(deprovisionTarget); calls.push("deprovision"); + return true; }), }), ), @@ -498,6 +1008,7 @@ describe("relay environment unlink", () => { deprovision: () => Effect.sync(() => { calls.push("deprovision"); + return true; }), }), ), @@ -525,6 +1036,47 @@ describe("relay environment unlink", () => { deprovision: () => Effect.sync(() => { calls.push("deprovision"); + return true; + }), + }), + ), + ); + }); + + it.effect("retries unlink cleanup when a concurrent tunnel release wins the first claim", () => { + let lookups = 0; + const targets: Array = []; + const target = { + userId: "user-1", + environmentId: "environment-1", + hostname: "environment-1.example.test", + tunnelId: "tunnel-1", + tunnelName: "environment-1-tunnel", + dnsRecordId: "dns-1", + readyAt: "2026-07-28T00:00:00.000Z", + origin: { localHttpHost: "127.0.0.1", localHttpPort: 3773 }, + updatedAt: "original-generation", + generation: 1, + } satisfies ManagedEndpointProvider.ManagedEndpointDeprovisionTarget; + + return Effect.gen(function* () { + expect( + yield* unlinkEnvironmentRecord({ + userId: "user-1", + environmentId: "environment-1", + }), + ).toBe(true); + expect(targets).toEqual([target, target]); + }).pipe( + Effect.provide( + relayUnlinkTestLayer({ + getForUser: () => Effect.sync(() => (++lookups === 1 ? linkedEnvironmentRecord : null)), + revokeForUser: () => Effect.succeed(true), + prepareDeprovision: () => Effect.succeed(target), + deprovision: ({ target: captured }) => + Effect.sync(() => { + targets.push(captured ?? undefined); + return targets.length > 1; }), }), ), @@ -652,7 +1204,19 @@ describe("relay routing fallback", () => { const routes = HttpApiBuilder.layer( HttpApi.make("RelayApi").add(RelayApi.groups.server), ).pipe( - Layer.provide(serverApi.pipe(Layer.provide([publisher, signatures]))), + Layer.provide( + serverApi.pipe( + HttpRouter.provideRequest( + Layer.mergeAll( + Layer.succeed(RelayConfiguration.RelayConfiguration, relaySettings), + Layer.mock(EnvironmentLinks.EnvironmentLinks, {}), + Layer.mock(ManagedEndpointAllocations.ManagedEndpointAllocations, {}), + Layer.mock(ManagedEndpointProvider.ManagedEndpointProvider, {}), + ), + ), + Layer.provide([publisher, signatures]), + ), + ), Layer.provide(auth), Layer.provide([NodeServices.layer, NodeHttpPlatform.layer, Etag.layerWeak]), ); diff --git a/infra/relay/src/http/Api.ts b/infra/relay/src/http/Api.ts index fbe867b7792a..42fd2cebf25b 100644 --- a/infra/relay/src/http/Api.ts +++ b/infra/relay/src/http/Api.ts @@ -47,10 +47,16 @@ import { RelayEnvironmentLinkLimitExceededError, RelayEnvironmentPrincipal, type RelayEnvironmentConnectRequest, + type RelayManagedEndpointOrigin, + RelayManagedEndpointRecoveryProofPayload, type RelayDpopAccessTokenScope, RelayInternalError, } from "@t3tools/contracts/relay"; -import { normalizeRelayIssuer } from "@t3tools/shared/relayJwt"; +import { + normalizeRelayIssuer, + RELAY_MANAGED_TUNNEL_RECOVERY_TYP, + verifyRelayJwt, +} from "@t3tools/shared/relayJwt"; import * as DeliveryAttempts from "../agentActivity/DeliveryAttempts.ts"; import * as AgentActivityRows from "../agentActivity/AgentActivityRows.ts"; @@ -99,6 +105,10 @@ const relayCorsPreflightHeaders = { "access-control-max-age": "86400", } as const; +const decodeManagedTunnelRecoveryProof = Schema.decodeUnknownEffect( + RelayManagedEndpointRecoveryProofPayload, +); + const appendRelayCredentialResponseHeaders = HttpEffect.appendPreResponseHandler( (_request, response) => Effect.succeed( @@ -462,15 +472,207 @@ export const unlinkEnvironmentRecord = Effect.fn("relay.api.client.unlinkEnviron // revocation commits so a database failure leaves a fully usable active // link. Still run teardown when the link is already revoked, allowing a // retry to finish cleanup after an earlier Cloudflare failure. - yield* managedEndpointProvider.deprovision({ + const deprovisioned = yield* managedEndpointProvider.deprovision({ userId: input.userId, environmentId: input.environmentId, target: deprovisionTarget, }); + if (!deprovisioned) { + const retryTarget = yield* managedEndpointProvider.prepareDeprovision(input); + if (retryTarget !== null && (yield* links.getForUser(input)) === null) { + yield* managedEndpointProvider.deprovision({ ...input, target: retryTarget }); + } + } return unlinked; }, ); +type EnvironmentTunnelRecoveryProofInput = { + readonly proof: string; + readonly userId: string; + readonly environmentId: string; + readonly environmentPublicKey: string; +} & ( + | { + readonly action: "register"; + readonly tunnelId: string; + readonly origin: RelayManagedEndpointOrigin; + } + | { readonly action: "recover"; readonly origin: RelayManagedEndpointOrigin } +); + +export const verifyEnvironmentTunnelRecoveryProof = Effect.fn( + "relay.api.server.verifyEnvironmentTunnelRecoveryProof", +)(function* (input: EnvironmentTunnelRecoveryProofInput) { + const config = yield* RelayConfiguration.RelayConfiguration; + const now = yield* DateTime.now; + const verified = yield* verifyRelayJwt({ + publicKey: input.environmentPublicKey, + token: input.proof, + typ: RELAY_MANAGED_TUNNEL_RECOVERY_TYP, + issuer: `t3-env:${input.environmentId}`, + audience: normalizeRelayIssuer(config.relayIssuer), + nowEpochSeconds: Math.floor(now.epochMilliseconds / 1_000), + }).pipe( + Effect.flatMap(decodeManagedTunnelRecoveryProof), + Effect.mapError(() => new HttpApiError.Unauthorized({})), + ); + + if ( + verified.environmentId !== input.environmentId || + verified.sub !== input.environmentId || + verified.cloudUserId !== input.userId || + verified.action !== input.action + ) { + return yield* new HttpApiError.Unauthorized({}); + } + if (input.action === "register") { + if ( + verified.action !== "register" || + verified.tunnelId !== input.tunnelId || + verified.origin.localHttpHost !== input.origin.localHttpHost || + verified.origin.localHttpPort !== input.origin.localHttpPort + ) { + return yield* new HttpApiError.Unauthorized({}); + } + return; + } + if ( + verified.action !== "recover" || + verified.origin.localHttpHost !== input.origin.localHttpHost || + verified.origin.localHttpPort !== input.origin.localHttpPort + ) { + return yield* new HttpApiError.Unauthorized({}); + } +}); + +export const registerEnvironmentTunnelRecovery = Effect.fn( + "relay.api.server.registerEnvironmentTunnelRecovery", +)(function* (input: { + readonly userId: string; + readonly environmentId: string; + readonly environmentPublicKey: string; + readonly tunnelId: string; + readonly origin: RelayManagedEndpointOrigin; +}) { + const links = yield* EnvironmentLinks.EnvironmentLinks; + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + const managedEndpointProvider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const link = yield* links.getForUser({ + userId: input.userId, + environmentId: input.environmentId, + }); + if ( + link === null || + link.environmentPublicKey !== input.environmentPublicKey || + link.endpoint.providerKind !== "cloudflare_tunnel" + ) { + return yield* new HttpApiError.Unauthorized({}); + } + const status = yield* managedEndpointProvider.reconcileOrigin({ + userId: input.userId, + environmentId: input.environmentId, + tunnelId: input.tunnelId, + origin: input.origin, + endpoint: link.endpoint, + }); + if (status === "recovery_required") { + return { status }; + } + if (!(yield* allocations.enableRecovery(input))) { + return yield* new HttpApiError.Unauthorized({}); + } + return { status }; +}); + +export const recoverEnvironmentTunnelRecord = Effect.fn( + "relay.api.server.recoverEnvironmentTunnelRecord", +)(function* (input: { + readonly userId: string; + readonly environmentId: string; + readonly environmentPublicKey: string; + readonly origin: RelayManagedEndpointOrigin; +}) { + const links = yield* EnvironmentLinks.EnvironmentLinks; + const allocations = yield* ManagedEndpointAllocations.ManagedEndpointAllocations; + const managedEndpointProvider = yield* ManagedEndpointProvider.ManagedEndpointProvider; + const link = yield* links.getForUser({ + userId: input.userId, + environmentId: input.environmentId, + }); + if ( + link === null || + link.environmentPublicKey !== input.environmentPublicKey || + link.endpoint.providerKind !== "cloudflare_tunnel" + ) { + return yield* new HttpApiError.Unauthorized({}); + } + + const recovered = yield* managedEndpointProvider.provision({ + userId: input.userId, + environmentId: input.environmentId, + origin: input.origin, + }); + const recoveredTunnelId = recovered.runtime.tunnelId; + if ( + recoveredTunnelId === undefined || + recovered.endpoint.httpBaseUrl !== link.endpoint.httpBaseUrl || + recovered.endpoint.wsBaseUrl !== link.endpoint.wsBaseUrl + ) { + if (recoveredTunnelId !== undefined) { + yield* managedEndpointProvider + .release({ + userId: input.userId, + environmentId: input.environmentId, + expectedTunnelId: recoveredTunnelId, + }) + .pipe( + Effect.catch((cause) => + Effect.logWarning("Failed to clean up a tunnel with a mismatched endpoint", { + userId: input.userId, + environmentId: input.environmentId, + tunnelId: recoveredTunnelId, + cause, + }), + ), + ); + } + return yield* new HttpApiError.Unauthorized({}); + } + + const enabled = yield* allocations.enableRecovery({ + userId: input.userId, + environmentId: input.environmentId, + tunnelId: recoveredTunnelId, + environmentPublicKey: input.environmentPublicKey, + origin: input.origin, + }); + if (!enabled) { + const owner = { userId: input.userId, environmentId: input.environmentId }; + const target = yield* managedEndpointProvider.prepareDeprovision(owner); + const currentLink = target === null ? null : yield* links.getForUser(input); + if ( + target !== null && + (currentLink === null || currentLink.endpoint.providerKind !== "cloudflare_tunnel") + ) { + yield* managedEndpointProvider.deprovision({ ...owner, target }).pipe( + Effect.catch((cause) => + Effect.logWarning("Failed to clean up a tunnel after its managed link was removed", { + userId: input.userId, + environmentId: input.environmentId, + cause, + }), + ), + ); + } + return yield* new HttpApiError.Unauthorized({}); + } + return { + endpoint: recovered.endpoint, + endpointRuntime: recovered.runtime, + }; +}); + export const mobileApi = HttpApiBuilder.group( RelayApi, "mobile", @@ -879,7 +1081,7 @@ export const serverApi = HttpApiBuilder.group( Effect.fnUntraced(function* (handlers) { const publisher = yield* AgentActivityPublisher.AgentActivityPublisher; const publishSignatures = yield* EnvironmentPublishSignatures.EnvironmentPublishSignatures; - return handlers.handle( + const activityHandlers = handlers.handle( "publishAgentActivity", Effect.fn("relay.api.server.publishAgentActivity")( function* (args) { @@ -1013,6 +1215,82 @@ export const serverApi = HttpApiBuilder.group( mapRelayCommonApiErrors("not_authorized"), ), ); + + return activityHandlers + .handle( + "registerManagedEndpointRecovery", + Effect.fn("relay.api.server.registerManagedEndpointRecovery")( + function* ({ params, payload }) { + const principal = yield* RelayEnvironmentPrincipal; + if (principal.environmentId !== params.environmentId) { + return yield* new HttpApiError.Unauthorized({}); + } + yield* verifyEnvironmentTunnelRecoveryProof({ + action: "register", + proof: payload.proof, + userId: payload.cloudUserId, + environmentId: params.environmentId, + environmentPublicKey: principal.environmentPublicKey, + tunnelId: payload.tunnelId, + origin: payload.origin, + }); + yield* appendRelayCredentialResponseHeaders; + return yield* registerEnvironmentTunnelRecovery({ + userId: payload.cloudUserId, + environmentId: params.environmentId, + environmentPublicKey: principal.environmentPublicKey, + tunnelId: payload.tunnelId, + origin: payload.origin, + }); + }, + Effect.catchTags({ + ManagedEndpointOriginNotAllowed: () => Effect.fail(new HttpApiError.Unauthorized({})), + ManagedEndpointProvisioningNotConfigured: () => + relayInternalErrorResponse("upstream_unavailable"), + ManagedEndpointProvisioningFailed: () => + relayInternalErrorResponse("upstream_unavailable"), + ManagedTunnelLimitExceeded: () => relayInternalErrorResponse("upstream_unavailable"), + }), + mapRelayCommonApiErrors("not_authorized"), + ), + ) + .handle( + "recoverManagedEndpoint", + Effect.fn("relay.api.server.recoverManagedEndpoint")( + function* ({ params, payload }) { + const principal = yield* RelayEnvironmentPrincipal; + if (principal.environmentId !== params.environmentId) { + return yield* new HttpApiError.Unauthorized({}); + } + yield* verifyEnvironmentTunnelRecoveryProof({ + action: "recover", + proof: payload.proof, + userId: payload.cloudUserId, + environmentId: params.environmentId, + environmentPublicKey: principal.environmentPublicKey, + origin: payload.origin, + }); + yield* appendRelayCredentialResponseHeaders; + return yield* recoverEnvironmentTunnelRecord({ + userId: payload.cloudUserId, + environmentId: params.environmentId, + environmentPublicKey: principal.environmentPublicKey, + origin: payload.origin, + }); + }, + Effect.catchTags({ + ManagedEndpointOriginNotAllowed: () => Effect.fail(new HttpApiError.Unauthorized({})), + ManagedEndpointProvisioningNotConfigured: () => + relayInternalErrorResponse("upstream_unavailable"), + ManagedEndpointProvisioningFailed: () => + relayInternalErrorResponse("upstream_unavailable"), + ManagedEndpointDeprovisioningFailed: () => + relayInternalErrorResponse("upstream_unavailable"), + ManagedTunnelLimitExceeded: () => relayInternalErrorResponse("upstream_unavailable"), + }), + mapRelayCommonApiErrors("not_authorized"), + ), + ); }), ); diff --git a/infra/relay/src/observability.test.ts b/infra/relay/src/observability.test.ts index 3958b28b95dd..d77c01277f1c 100644 --- a/infra/relay/src/observability.test.ts +++ b/infra/relay/src/observability.test.ts @@ -62,6 +62,11 @@ it.effect("exports schema error fields as span attributes", () => const request = yield* Deferred.await(exportedRequest).pipe(Effect.timeout("1 second")); const payload = (yield* decodeJson(request.body)) as OtlpTracer.TraceData; + const resourceAttributes = Object.fromEntries( + payload.resourceSpans + .flatMap((resourceSpan) => resourceSpan.resource.attributes) + .map((attribute) => [attribute.key, otlpAttributeValue(attribute.value)]), + ); const span = payload.resourceSpans .flatMap((resourceSpan) => resourceSpan.scopeSpans) .flatMap((scopeSpan) => scopeSpan.spans) @@ -75,6 +80,10 @@ it.effect("exports schema error fields as span attributes", () => expect(request.authorization).toBe("Bearer test-token"); expect(request.dataset).toBe("relay-test-traces"); + expect(resourceAttributes).toMatchObject({ + "service.name": "t3code-relay", + "service.namespace": "t3code", + }); expect(attributes).toMatchObject({ "error.type": "EnvironmentConnectNotAuthorized", "error.environmentId": "environment-1", diff --git a/infra/relay/src/observability.ts b/infra/relay/src/observability.ts index ca091d8cce6b..2325d7e0432a 100644 --- a/infra/relay/src/observability.ts +++ b/infra/relay/src/observability.ts @@ -222,8 +222,9 @@ export const makeRelayTraceLayer = (input: { OtlpTracer.make({ url: input.tracesEndpoint, resource: { - serviceName: "t3-code-relay-worker", + serviceName: "t3code-relay", attributes: { + "service.namespace": "t3code", "service.runtime": "cloudflare-worker", "service.component": "relay", }, diff --git a/infra/relay/src/persistence/schema.ts b/infra/relay/src/persistence/schema.ts index fd766c758989..1f5d8c4d94e2 100644 --- a/infra/relay/src/persistence/schema.ts +++ b/infra/relay/src/persistence/schema.ts @@ -2,6 +2,7 @@ import type { RelayAgentActivityAggregateState, RelayAgentActivityState, RelayAgentAwarenessPreferences, + RelayManagedEndpointOrigin, } from "@t3tools/contracts/relay"; import { boolean, @@ -94,6 +95,10 @@ export const relayManagedEndpointAllocations = pgTable( tunnelName: text("tunnel_name").notNull(), dnsRecordId: varchar("dns_record_id", { length: 191 }), readyAt: varchar("ready_at", { length: 64 }), + recoveryEnabledAt: varchar("recovery_enabled_at", { length: 64 }), + recoveryEnvironmentPublicKey: text("recovery_environment_public_key"), + origin: jsonb("origin").$type(), + generation: integer("generation").notNull().default(0), createdAt: varchar("created_at", { length: 64 }).notNull(), updatedAt: varchar("updated_at", { length: 64 }).notNull(), }, diff --git a/infra/relay/src/worker.ts b/infra/relay/src/worker.ts index c9f343822fb0..d4d674d3342b 100644 --- a/infra/relay/src/worker.ts +++ b/infra/relay/src/worker.ts @@ -2,6 +2,7 @@ import * as Alchemy from "alchemy"; import * as Cloudflare from "alchemy/Cloudflare"; import * as Drizzle from "alchemy/Drizzle/Postgres"; import * as Config from "effect/Config"; +import * as Cause from "effect/Cause"; import * as DateTime from "effect/DateTime"; import * as Crypto from "effect/Crypto"; import * as Effect from "effect/Effect"; @@ -69,6 +70,7 @@ import * as EnvironmentConnector from "./environments/EnvironmentConnector.ts"; import * as EnvironmentLinker from "./environments/EnvironmentLinker.ts"; import * as EnvironmentPublishSignatures from "./environments/EnvironmentPublishSignatures.ts"; import * as ManagedEndpointProvider from "./environments/ManagedEndpointProvider.ts"; +import * as ManagedEndpointReaper from "./environments/ManagedEndpointReaper.ts"; import * as ManagedTunnelLimits from "./environments/ManagedTunnelLimits.ts"; import * as MobileRegistrations from "./agentActivity/MobileRegistrations.ts"; @@ -180,6 +182,7 @@ export const ApiLive = Api.make( yield* yield* relayApiZone.zoneId; const managedEndpointDnsBinding = yield* Cloudflare.DNS.ReadWriteDns(managedEndpointZone); const managedEndpointZoneName = yield* managedEndpointZone.name; + const managedEndpointCleanupMode = yield* RelayConfiguration.managedEndpointCleanupModeConfig; // // 3. Runtime layers and app construction @@ -199,6 +202,7 @@ export const ApiLive = Api.make( cloudMintPublicKey: yield* cloudMintPublicKey, managedEndpointBaseDomain: yield* managedEndpointZoneName, managedEndpointNamespace: stage, + managedEndpointCleanupMode, }); }); @@ -215,7 +219,9 @@ export const ApiLive = Api.make( Layer.provideMerge(AgentActivityPublisher.layer), Layer.provideMerge(EnvironmentConnector.layer), Layer.provideMerge(EnvironmentLinker.layer), - Layer.provideMerge(EnvironmentPublishSignatures.layer), + Layer.provideMerge( + Layer.merge(EnvironmentPublishSignatures.layer, ManagedEndpointReaper.layer), + ), Layer.provideMerge( ManagedEndpointProvider.layerCloudflareBindings( managedEndpointTunnelBinding, @@ -317,21 +323,45 @@ export const ApiLive = Api.make( ); yield* Cloudflare.Workers.cron("*/5 * * * *", () => - DpopProofs.DpopProofReplay.pipe( - Effect.flatMap((dpopProofs) => dpopProofs.pruneExpired), - // Terminal thread rows are kept briefly so finished agents show as - // Done/Failed in the Live Activity; sweep them once they age out. - Effect.andThen( - Effect.all([AgentActivityRows.AgentActivityRows, DateTime.now]).pipe( - Effect.flatMap(([activityRows, now]) => - activityRows.pruneTerminal({ - updatedBefore: DateTime.formatIso(DateTime.subtract(now, { minutes: 30 })), - }), + Effect.all( + [ + DpopProofs.DpopProofReplay.pipe( + Effect.flatMap((dpopProofs) => dpopProofs.pruneExpired), + // Keep completed thread rows long enough to show their final state. + Effect.andThen( + Effect.all([AgentActivityRows.AgentActivityRows, DateTime.now]).pipe( + Effect.flatMap(([activityRows, now]) => + activityRows.pruneTerminal({ + updatedBefore: DateTime.formatIso(DateTime.subtract(now, { minutes: 30 })), + }), + ), + ), + ), + Effect.catchCause((cause) => + Cause.hasInterrupts(cause) + ? Effect.interrupt + : Effect.logWarning("Failed to prune expired relay state", { cause }), ), ), - ), + ManagedEndpointReaper.ManagedEndpointReaper.pipe( + Effect.flatMap((reaper) => reaper.sweep.pipe(Effect.timeout("2 minutes"))), + Effect.tap((result) => + result.scanned > 0 + ? Effect.logInfo("Finished managed tunnel cleanup", result) + : Effect.void, + ), + Effect.catchCause((cause) => + Cause.hasInterrupts(cause) + ? Effect.interrupt + : Effect.logWarning("Failed to clean up inactive managed tunnels", { cause }), + ), + ), + ], + { concurrency: 2, discard: true }, + ).pipe( Effect.withSpan("relay.cron.prune_expired_state"), - Effect.provide(runtimeLayer), + // Export cron spans to Axiom like HTTP spans; the scope flushes them before the run ends. + Effect.provide(Layer.merge(runtimeLayer, relayTraceLayer)), ), ); diff --git a/knip.jsonc b/knip.jsonc index 5e87b4d5bc55..a6e4f94143a0 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -13,8 +13,9 @@ "vitest": { "entry": [".github/**/*.test.cjs"] }, }, "scripts": { - // Knip loads its preprocessor through a CLI option; native verification runs directly. - "entry": ["knip-schemas.ts", "mobile-native-client.ts"], + // Knip loads its preprocessor through a CLI option; native verification and + // worktree setup (from t3.json) run directly. + "entry": ["knip-schemas.ts", "mobile-native-client.ts", "setup-worktree.ts"], }, "apps/server": { // Vite+ pack entries and the launcher used by installed background services. @@ -38,6 +39,8 @@ "src/snapShot/GlobalShiftShortcutWorker.ts!", "src/snapShot/RegionSnapShotWorker.ts!", "src/snapShot/SnapShotAccessibilityWorker.ts!", + "src/boot.ts!", + "src/compileCache.ts!", "src/main.ts!", "src/preload.ts!", "src/preview-pick-preload.ts!", @@ -50,7 +53,7 @@ "ignoreDependencies": ["playwright-core", "electron-builder"], }, "apps/web": { - // Worktree setup invokes this directly from t3.json. + // Worktree setup (scripts/setup-worktree.ts) runs this directly. "entry": ["scripts/warm-dep-cache.ts"], // UI component modules are copied and adapted as cohesive sets. Keep their // named subcomponents even before they have callers; the file audit still diff --git a/packages/client-runtime/package.json b/packages/client-runtime/package.json index 1bf05a358c02..925e8b4b507f 100644 --- a/packages/client-runtime/package.json +++ b/packages/client-runtime/package.json @@ -123,6 +123,10 @@ "types": "./src/textPaste.ts", "default": "./src/textPaste.ts" }, + "./delayed-status": { + "types": "./src/delayedStatus.ts", + "default": "./src/delayedStatus.ts" + }, "./state/connections": { "types": "./src/state/connections.ts", "default": "./src/state/connections.ts" @@ -294,6 +298,14 @@ "./device/model": { "types": "./src/device/model.ts", "default": "./src/device/model.ts" + }, + "./device/duo-viewer": { + "types": "./src/device/duoViewer.ts", + "default": "./src/device/duoViewer.ts" + }, + "./device/duo-control": { + "types": "./src/device/duoControl.ts", + "default": "./src/device/duoControl.ts" } }, "scripts": { diff --git a/packages/client-runtime/src/authorization/layer.test.ts b/packages/client-runtime/src/authorization/layer.test.ts index 5cd2d89d1ac2..d31f37db2eb7 100644 --- a/packages/client-runtime/src/authorization/layer.test.ts +++ b/packages/client-runtime/src/authorization/layer.test.ts @@ -199,7 +199,7 @@ const makeHarness = Effect.fn("TestRemoteAuthorization.makeHarness")(function* ( clerkToken: input.clerkToken ?? Effect.succeed("clerk-session"), }), Layer.succeed(ClientCapabilities.RelayDeviceIdentity, { - deviceId: Effect.succeed(Option.some("device-1")), + deviceId: Effect.succeedSome("device-1"), }), Layer.succeed(TokenStore.RemoteDpopAccessTokenStore, tokenStore), Layer.succeed( diff --git a/packages/client-runtime/src/connection/layer.ts b/packages/client-runtime/src/connection/layer.ts index 46dcdcf569b9..b64e58c4fef1 100644 --- a/packages/client-runtime/src/connection/layer.ts +++ b/packages/client-runtime/src/connection/layer.ts @@ -32,11 +32,18 @@ export const watchDiscoveredCompatibility = Effect.fn("connection.watchDiscovere if (!current.environments.has(environmentId)) seenChecks.delete(environmentId); } } + const registered = yield* SubscriptionRef.get(registry.entries); for (const entry of current.environments.values()) { const status = Option.getOrNull(entry.status); const descriptor = status?.descriptor; if (status === null || descriptor === undefined) continue; const environmentId = entry.environment.environmentId; + // Discovery describes the server behind the relay route. A direct + // connection (the desktop's own server, a saved URL, SSH) can reach + // a different server with the same environment id, such as a + // preview app that shares the home directory. Its socket handshake + // already checks the protocol. + if (registered.get(environmentId)?.target._tag !== "RelayConnectionTarget") continue; const previous = seenChecks.get(environmentId); const fresh = previous?.checkedAt !== status.checkedAt || diff --git a/packages/client-runtime/src/connection/registry.test.ts b/packages/client-runtime/src/connection/registry.test.ts index 729d27060187..8a71344ad173 100644 --- a/packages/client-runtime/src/connection/registry.test.ts +++ b/packages/client-runtime/src/connection/registry.test.ts @@ -275,12 +275,12 @@ const makeHarness = Effect.fn("TestEnvironmentRegistry.makeHarness")(function* ( next.set(environmentId, snapshot); return next; }), - loadThread: (_environmentId, _threadId) => Effect.succeed(Option.none()), + loadThread: (_environmentId, _threadId) => Effect.succeedNone, saveThread: (_environmentId, _thread) => Effect.void, removeThread: (_environmentId, _threadId) => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, @@ -683,7 +683,7 @@ describe("EnvironmentRegistry", () => { it.effect("only a fresh health check for the rejected environment unlocks it", () => Effect.gen(function* () { - const harness = yield* makeHarness([RELAY_TARGET], [], [], { + const harness = yield* makeHarness([RELAY_TARGET, SECOND_RELAY_TARGET], [], [], { initialDisabled: [RELAY_TARGET.environmentId], }); const descriptor = (environmentId: EnvironmentId): ExecutionEnvironmentDescriptor => ({ @@ -778,8 +778,8 @@ describe("EnvironmentRegistry", () => { yield* SubscriptionRef.update(discoveryState, (state) => ({ ...state, environments: new Map(state.environments).set( - SECOND_TARGET.environmentId, - discovered(descriptor(SECOND_TARGET.environmentId)), + SECOND_RELAY_TARGET.environmentId, + discovered(descriptor(SECOND_RELAY_TARGET.environmentId)), ), })); yield* Deferred.await(unrelated); @@ -798,8 +798,8 @@ describe("EnvironmentRegistry", () => { environments: new Map([ [RELAY_TARGET.environmentId, discovered(descriptor(RELAY_TARGET.environmentId))], [ - SECOND_TARGET.environmentId, - discovered(descriptor(SECOND_TARGET.environmentId), "2026-09-15T00:01:00Z"), + SECOND_RELAY_TARGET.environmentId, + discovered(descriptor(SECOND_RELAY_TARGET.environmentId), "2026-09-15T00:01:00Z"), ], ]), })); @@ -827,6 +827,92 @@ describe("EnvironmentRegistry", () => { }), ); + it.effect("discovery leaves direct connections that share an environment id alone", () => + Effect.gen(function* () { + const harness = yield* makeHarness([TARGET, RELAY_TARGET]); + const discovered = (environmentId: EnvironmentId) => { + const endpoint = { + httpBaseUrl: "https://relay.example.test", + wsBaseUrl: "wss://relay.example.test", + providerKind: "manual" as const, + }; + return { + environment: { + environmentId, + label: "Preview server", + endpoint, + linkedAt: "2026-09-25T00:00:00Z", + }, + availability: "online" as const, + status: Option.some({ + environmentId, + endpoint, + status: "online", + checkedAt: "2026-09-25T00:00:00Z", + descriptor: { + environmentId, + label: "Preview server", + platform: { os: "darwin", arch: "arm64" }, + serverVersion: "2.0.0", + orchestrationProtocolVersion: ORCHESTRATION_PROTOCOL_VERSION + 1, + capabilities: { repositoryIdentity: true }, + }, + }), + error: Option.none(), + }; + }; + yield* Effect.gen(function* () { + const registry = yield* EnvironmentRegistry.EnvironmentRegistry; + yield* registry.start; + yield* awaitConnectionState( + registry, + TARGET.environmentId, + (state) => state.phase === "connected", + ); + const relayChecked = yield* Deferred.make(); + yield* watchDiscoveredCompatibility().pipe( + Effect.provideService(EnvironmentRegistry.EnvironmentRegistry, { + ...registry, + setCompatibility: (environmentId, error) => + registry + .setCompatibility(environmentId, error) + .pipe( + Effect.andThen( + environmentId === RELAY_TARGET.environmentId + ? Deferred.succeed(relayChecked, undefined) + : Effect.void, + ), + ), + }), + Effect.provideService( + RelayEnvironmentDiscovery.RelayEnvironmentDiscovery, + RelayEnvironmentDiscovery.RelayEnvironmentDiscovery.of({ + state: + yield* SubscriptionRef.make( + { + ...RelayEnvironmentDiscovery.EMPTY_RELAY_ENVIRONMENT_DISCOVERY_STATE, + environments: new Map([ + [TARGET.environmentId, discovered(TARGET.environmentId)], + [RELAY_TARGET.environmentId, discovered(RELAY_TARGET.environmentId)], + ]), + }, + ), + refresh: Effect.void, + }), + ), + Effect.forkScoped, + ); + yield* Deferred.await(relayChecked); + + const entries = yield* SubscriptionRef.get(registry.entries); + expect(entries.get(RELAY_TARGET.environmentId)).toMatchObject({ enabled: false }); + expect(entries.get(TARGET.environmentId)).toMatchObject({ enabled: true }); + expect(entries.get(TARGET.environmentId)?.unsupportedReason).toBeUndefined(); + expect((yield* registry.state(TARGET.environmentId)).phase).toBe("connected"); + }).pipe(Effect.provide(harness.layer), Effect.scoped); + }), + ); + it.effect("discovery keeps unsupported environments off until compatibility changes", () => Effect.gen(function* () { const harness = yield* makeHarness([RELAY_TARGET], [], [], { diff --git a/packages/client-runtime/src/connection/resolver.ts b/packages/client-runtime/src/connection/resolver.ts index af1a417fc594..120f63cc07ef 100644 --- a/packages/client-runtime/src/connection/resolver.ts +++ b/packages/client-runtime/src/connection/resolver.ts @@ -110,10 +110,9 @@ const makeBearerBroker = Effect.fn("clientRuntime.connection.broker.makeBearer") entry: ConnectionCatalogEntry & { readonly target: BearerConnectionTarget }, ) { const target = entry.target; - const profile = yield* Option.match(entry.profile, { - onNone: () => Effect.fail(profileMissingError(target.connectionId)), - onSome: Effect.succeed, - }); + const profile = yield* Effect.fromOption(entry.profile, () => + profileMissingError(target.connectionId), + ); if (!isBearerProfile(profile)) { return yield* new ConnectionBlockedError({ reason: "configuration", @@ -186,10 +185,9 @@ const makeSshBroker = Effect.fn("clientRuntime.connection.broker.makeSsh")(funct entry: ConnectionCatalogEntry & { readonly target: SshConnectionTarget }, ) { const target = entry.target; - const profile = yield* Option.match(entry.profile, { - onNone: () => Effect.fail(profileMissingError(target.connectionId)), - onSome: Effect.succeed, - }); + const profile = yield* Effect.fromOption(entry.profile, () => + profileMissingError(target.connectionId), + ); if (!isSshProfile(profile)) { return yield* new ConnectionBlockedError({ reason: "configuration", diff --git a/packages/client-runtime/src/connection/supervisor.test.ts b/packages/client-runtime/src/connection/supervisor.test.ts index 882e67aac33c..7ad709c3a54b 100644 --- a/packages/client-runtime/src/connection/supervisor.test.ts +++ b/packages/client-runtime/src/connection/supervisor.test.ts @@ -1290,11 +1290,11 @@ describe("EnvironmentSupervisor", () => { Layer.succeed(ManagedRelayDpopSigner, signer), Layer.succeed(ManagedRelayClient, relay), Layer.succeed(ClientCapabilities.CloudSession, { - identity: Effect.succeed(Option.some({ accountId: "test-account" })), + identity: Effect.succeedSome({ accountId: "test-account" }), clerkToken: Effect.succeed("clerk-token"), }), Layer.succeed(ClientCapabilities.RelayDeviceIdentity, { - deviceId: Effect.succeed(Option.none()), + deviceId: Effect.succeedNone, }), TokenStore.layer({ get: () => Ref.get(token), diff --git a/packages/client-runtime/src/connection/supervisor.ts b/packages/client-runtime/src/connection/supervisor.ts index 45ef02292056..b75413bbb7de 100644 --- a/packages/client-runtime/src/connection/supervisor.ts +++ b/packages/client-runtime/src/connection/supervisor.ts @@ -606,6 +606,7 @@ export const make = Effect.fn("EnvironmentSupervisor.make")(function* ( }, Effect.ensuring(clearLease)); const waitForRetrySignal = Effect.fnUntraced(function* (delayMs: number) { + // @effect-diagnostics-next-line raceFirstWithSleepToTimeout:off - the sleep is the retry delay (false), not a timeout around the signal loop return yield* Effect.raceFirst( Effect.sleep(delayMs).pipe(Effect.as(false)), Effect.gen(function* () { diff --git a/packages/client-runtime/src/delayedStatus.test.ts b/packages/client-runtime/src/delayedStatus.test.ts new file mode 100644 index 000000000000..4d48d2fe211f --- /dev/null +++ b/packages/client-runtime/src/delayedStatus.test.ts @@ -0,0 +1,65 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test"; + +import { + createDelayedStatus, + STATUS_MIN_VISIBLE_MS, + STATUS_SHOW_DELAY_MS, + type ShownStatus, +} from "./delayedStatus.ts"; + +function track() { + const changes: Array | null> = []; + const status = createDelayedStatus((shown) => changes.push(shown)); + return { changes, status }; +} + +describe("createDelayedStatus", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + afterEach(() => { + vi.useRealTimers(); + }); + + it("never shows a status that clears before the show delay", () => { + const { changes, status } = track(); + status.update("a", "syncing"); + vi.advanceTimersByTime(STATUS_SHOW_DELAY_MS - 1); + status.update("a", null); + vi.runAllTimers(); + + expect(changes).toEqual([]); + }); + + it("holds each shown status for the minimum time, then hides it at once", () => { + const { changes, status } = track(); + status.update("a", "loading"); + vi.advanceTimersByTime(STATUS_SHOW_DELAY_MS); + expect(changes).toEqual([{ key: "a", value: "loading" }]); + + // A new label gets its own full hold, even during the first label's hold. + vi.advanceTimersByTime(1); + status.update("a", "syncing"); + status.update("a", null); + vi.advanceTimersByTime(STATUS_MIN_VISIBLE_MS - 1); + expect(changes.at(-1)).toEqual({ key: "a", value: "syncing" }); + vi.advanceTimersByTime(1); + expect(changes.at(-1)).toBeNull(); + + status.update("a", "syncing"); + vi.advanceTimersByTime(STATUS_SHOW_DELAY_MS + STATUS_MIN_VISIBLE_MS); + status.update("a", null); + expect(changes.at(-1)).toBeNull(); + }); + + it("drops the shown status at once when the key changes", () => { + const { changes, status } = track(); + status.update("a", "syncing"); + vi.advanceTimersByTime(STATUS_SHOW_DELAY_MS); + status.update("b", "syncing"); + expect(changes.at(-1)).toBeNull(); + + vi.advanceTimersByTime(STATUS_SHOW_DELAY_MS); + expect(changes.at(-1)).toEqual({ key: "b", value: "syncing" }); + }); +}); diff --git a/packages/client-runtime/src/delayedStatus.ts b/packages/client-runtime/src/delayedStatus.ts new file mode 100644 index 000000000000..faca3d2f9ff5 --- /dev/null +++ b/packages/client-runtime/src/delayedStatus.ts @@ -0,0 +1,86 @@ +// @effect-diagnostics globalTimers:off - Display timing for React hooks, outside an Effect runtime. + +/** How long a status must last before a client shows it. */ +export const STATUS_SHOW_DELAY_MS = 400; +/** How long a shown status stays up, so it cannot flash at the show delay. */ +export const STATUS_MIN_VISIBLE_MS = 400; + +/** The status a client should show, and the key it belongs to. */ +export interface ShownStatus { + readonly key: string; + readonly value: A; +} + +export interface DelayedStatus { + /** Reports the real status for `key`. A new key drops the shown status at once. */ + readonly update: (key: string, value: A | null) => void; + /** Cancels pending timers. A later `update` starts again from the real status. */ + readonly dispose: () => void; +} + +/** + * Turns a real status (for example the thread sync phase) into the status a + * client shows. A status that clears within `STATUS_SHOW_DELAY_MS` is never + * shown. A shown status stays for at least `STATUS_MIN_VISIBLE_MS`. Values + * compare by identity, so use strings or other stable values. + * + * Web and mobile wrap this in a small `useDelayedStatus` hook. + */ +export function createDelayedStatus( + onChange: (shown: ShownStatus | null) => void, +): DelayedStatus { + let key = ""; + let latest: A | null = null; + let shown: A | null = null; + // While hidden, this is the show delay. While shown, the minimum visible time. + let timer: ReturnType | undefined; + + const clearTimer = () => { + clearTimeout(timer); + timer = undefined; + }; + const hide = () => { + shown = null; + onChange(null); + }; + // Every shown value, including a new label, gets the full minimum visible time. + const show = (value: A) => { + shown = value; + onChange({ key, value }); + clearTimer(); + timer = setTimeout(() => { + timer = undefined; + if (latest === null) hide(); + }, STATUS_MIN_VISIBLE_MS); + }; + + return { + update: (nextKey, value) => { + if (nextKey !== key) { + key = nextKey; + clearTimer(); + if (shown !== null) hide(); + } + latest = value; + + if (shown === null) { + if (value === null) { + clearTimer(); + } else if (timer === undefined) { + timer = setTimeout(() => { + timer = undefined; + if (latest !== null) show(latest); + }, STATUS_SHOW_DELAY_MS); + } + return; + } + + if (value !== null) { + if (value !== shown) show(value); + } else if (timer === undefined) { + hide(); + } + }, + dispose: clearTimer, + }; +} diff --git a/packages/client-runtime/src/device/androidFoldScene.test.ts b/packages/client-runtime/src/device/androidFoldScene.test.ts new file mode 100644 index 000000000000..e1e237a9f131 --- /dev/null +++ b/packages/client-runtime/src/device/androidFoldScene.test.ts @@ -0,0 +1,78 @@ +import { Box3, Mesh, PerspectiveCamera, Texture, Vector3 } from "three"; +import { describe, expect, it } from "vite-plus/test"; +import { createAndroidFoldScene } from "./androidFoldScene.ts"; +import { phoneDisplayLayout } from "./phoneScene.ts"; + +describe("Android fold scene", () => { + it("moves one physical half around the hinge while preserving both screen halves", () => { + const texture = new Texture(); + const scene = createAndroidFoldScene( + texture, + phoneDisplayLayout({ width: 2200, height: 1840, orientation: "landscape_left" }, 2200, 1840), + 180, + ); + const moving = scene.orientation.children[0]!; + const fixed = scene.orientation.children[1]!; + const openWidth = new Box3().setFromObject(scene.root).getSize(new Vector3()).x; + const leftScreen = scene.root.getObjectByName("left-inner-screen") as Mesh; + const rightScreen = scene.root.getObjectByName("right-inner-screen") as Mesh; + leftScreen.geometry.computeBoundingBox(); + rightScreen.geometry.computeBoundingBox(); + const creaseWidth = + rightScreen.geometry.boundingBox!.min.x - leftScreen.geometry.boundingBox!.max.x; + expect(creaseWidth).toBeLessThan(0.01); + const continuousScreen = scene.root.getObjectByName("continuous-inner-screen") as Mesh; + const positions = continuousScreen.geometry.getAttribute("position"); + expect(continuousScreen.geometry.index).not.toBeNull(); + expect(Array.from({ length: positions.count }, (_, i) => positions.getX(i))).toContain(0); + scene.setAngle(90); + expect(moving.rotation.y).toBeCloseTo(Math.PI / 2); + expect(fixed.rotation.y).toBe(0); + scene.setAngle(0); + const closedWidth = new Box3().setFromObject(scene.root).getSize(new Vector3()).x; + expect(closedWidth).toBeLessThan(openWidth * 0.7); + expect(scene.root.getObjectByName("cover-screen")?.visible).toBe(true); + scene.dispose(); + texture.dispose(); + }); + + it("maps touches on each open half and the closed cover to the live frame", () => { + const texture = new Texture(); + const scene = createAndroidFoldScene(texture, phoneDisplayLayout(null, 2200, 1840), 180); + const camera = new PerspectiveCamera(32, 1, 0.1, 30); + camera.position.z = 6; + camera.updateMatrixWorld(true); + const project = (x: number) => { + const point = new Vector3(x, 0, 0.041).project(camera); + return scene.screenPoint((point.x + 1) / 2, (1 - point.y) / 2, camera); + }; + expect(project(-0.52)?.x).toBeCloseTo(0.25, 1); + expect(project(0.52)?.x).toBeCloseTo(0.75, 1); + expect(scene.screenPoint(0.99, 0.5, camera, true)?.x).toBe(1); + scene.setAngle(0); + expect(project(0.52)?.x).toBeCloseTo(0.5, 1); + scene.dispose(); + texture.dispose(); + }); + + it("shapes the inner display to the raw frame, portrait or landscape", () => { + const texture = new Texture(); + for (const [width, height] of [ + [2076, 2152], + [2208, 1840], + ] as const) { + const scene = createAndroidFoldScene( + texture, + phoneDisplayLayout(null, width, height), + 180, + width / height, + ); + const screen = scene.root.getObjectByName("continuous-inner-screen") as Mesh; + screen.geometry.computeBoundingBox(); + const size = screen.geometry.boundingBox!.getSize(new Vector3()); + expect(size.x / size.y).toBeCloseTo(width / height, 2); + scene.dispose(); + } + texture.dispose(); + }); +}); diff --git a/packages/client-runtime/src/device/androidFoldScene.ts b/packages/client-runtime/src/device/androidFoldScene.ts new file mode 100644 index 000000000000..b2c1f84fe038 --- /dev/null +++ b/packages/client-runtime/src/device/androidFoldScene.ts @@ -0,0 +1,420 @@ +import { + BoxGeometry, + CircleGeometry, + CylinderGeometry, + ExtrudeGeometry, + Group, + Mesh, + MeshBasicMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + PlaneGeometry, + Raycaster, + Shape, + ShapeGeometry, + Vector2, + Vector3, + type Camera, + type Texture, +} from "three"; +import type { PhoneDisplayLayout } from "./phoneScene.ts"; + +const HEIGHT = 2.2; +const DEPTH = 0.075; +const INSET = 0.026; +const CREASE = 0.004; +const BEVEL = 0.008; +// The hinge axis sits just above the inner screens so closed halves meet face to face. +const PIVOT_Z = DEPTH / 2 + 0.005; +const SPINE_RADIUS = PIVOT_Z + DEPTH / 2 - 0.002; +/** Width over height of the unfolded inner display until a live frame reports its own. */ +export const DEFAULT_FOLD_INNER_ASPECT = 2076 / 2152; +/** + * Unfolded inner displays are near square in either orientation. Cover displays are + * phone shaped (about 0.4-0.5, or 2-2.6 when rotated), so they never retune the body. + */ +export const isFoldInnerAspect = (aspect: number) => + Number.isFinite(aspect) && aspect > 0.75 && aspect < 1.5; + +function panelPath( + halfWidth: number, + side: "left" | "right", + inset: number, + radius: number, + hingeInset = 0, +) { + const left = side === "left" ? -halfWidth + inset : CREASE / 2 + hingeInset; + const right = side === "left" ? -CREASE / 2 - hingeInset : halfWidth - inset; + const bottom = -HEIGHT / 2 + inset; + const top = HEIGHT / 2 - inset; + const path = new Shape(); + if (side === "left") { + path.moveTo(left + radius, bottom); + path.lineTo(right, bottom); + path.lineTo(right, top); + path.lineTo(left + radius, top); + path.quadraticCurveTo(left, top, left, top - radius); + path.lineTo(left, bottom + radius); + path.quadraticCurveTo(left, bottom, left + radius, bottom); + } else { + path.moveTo(left, bottom); + path.lineTo(right - radius, bottom); + path.quadraticCurveTo(right, bottom, right, bottom + radius); + path.lineTo(right, top - radius); + path.quadraticCurveTo(right, top, right - radius, top); + path.lineTo(left, top); + } + path.closePath(); + return path; +} + +function roundedRectPath(width: number, height: number, radius: number) { + const path = new Shape(); + path.moveTo(-width / 2 + radius, -height / 2); + path.lineTo(width / 2 - radius, -height / 2); + path.quadraticCurveTo(width / 2, -height / 2, width / 2, -height / 2 + radius); + path.lineTo(width / 2, height / 2 - radius); + path.quadraticCurveTo(width / 2, height / 2, width / 2 - radius, height / 2); + path.lineTo(-width / 2 + radius, height / 2); + path.quadraticCurveTo(-width / 2, height / 2, -width / 2, height / 2 - radius); + path.lineTo(-width / 2, -height / 2 + radius); + path.quadraticCurveTo(-width / 2, -height / 2, -width / 2 + radius, -height / 2); + path.closePath(); + return path; +} + +function coverPath(halfWidth: number) { + return roundedRectPath(halfWidth - INSET * 2 - 0.02, HEIGHT - INSET * 2 - 0.04, 0.07); +} + +/** + * A procedural book-style foldable: one fixed half, one half rotating around a shared hinge. + * The inner display keeps the raw framebuffer's native aspect, portrait or landscape. + */ +export function createAndroidFoldScene( + texture: Texture, + layout: PhoneDisplayLayout, + initialAngle: number, + innerAspect = DEFAULT_FOLD_INNER_ASPECT, +) { + const screenHeight = HEIGHT - 2 * INSET; + const halfWidth = (innerAspect * screenHeight) / 2 + INSET; + const root = new Group(); + const orientation = new Group(); + root.add(orientation); + const left = new Group(); + const right = new Group(); + orientation.add(left, right); + const frameMetal = new MeshStandardMaterial({ + color: 0xa3abb2, + metalness: 0.9, + roughness: 0.28, + }); + const polishedMetal = new MeshStandardMaterial({ + color: 0xc4cad0, + metalness: 0.95, + roughness: 0.16, + }); + const bezel = new MeshPhysicalMaterial({ + color: 0x0b0d10, + metalness: 0.1, + roughness: 0.2, + clearcoat: 1, + }); + const backGlass = new MeshPhysicalMaterial({ + color: 0x2c3237, + metalness: 0.35, + roughness: 0.52, + clearcoat: 0.4, + clearcoatRoughness: 0.6, + }); + const island = new MeshPhysicalMaterial({ + color: 0x1a1e22, + metalness: 0.55, + roughness: 0.3, + clearcoat: 1, + }); + const lensMaterial = new MeshPhysicalMaterial({ + color: 0x061022, + metalness: 0.6, + roughness: 0.1, + clearcoat: 1, + }); + const flashMaterial = new MeshBasicMaterial({ color: 0xf2ead6 }); + const displayMaterial = new MeshBasicMaterial({ map: texture, toneMapped: false }); + const hitMaterial = new MeshBasicMaterial({ colorWrite: false, depthWrite: false }); + const coverMaterial = new MeshBasicMaterial({ map: texture, toneMapped: false }); + const materials = [ + frameMetal, + polishedMetal, + bezel, + backGlass, + island, + lensMaterial, + flashMaterial, + displayMaterial, + hitMaterial, + coverMaterial, + ]; + + // Each half's meshes live in body coordinates; `left` pivots them around the hinge axis. + left.position.z = PIVOT_Z; + const leftBody = new Group(); + leftBody.position.z = -PIVOT_Z; + left.add(leftBody); + + function half(group: Group, side: "left" | "right", back: MeshPhysicalMaterial) { + const body = new Mesh( + new ExtrudeGeometry(panelPath(halfWidth, side, BEVEL, 0.1, BEVEL), { + depth: DEPTH - BEVEL * 2, + bevelEnabled: true, + bevelSize: BEVEL, + bevelThickness: BEVEL, + bevelSegments: 3, + curveSegments: 12, + }), + frameMetal, + ); + body.position.z = -DEPTH / 2 + BEVEL; + group.add(body); + const frame = new Mesh( + new ShapeGeometry(panelPath(halfWidth, side, 0.01, 0.095, 0.002), 12), + bezel, + ); + frame.position.z = DEPTH / 2 + 0.001; + group.add(frame); + // A back-facing shape mirrors X, so it is drawn from the opposite side's outline. + const rear = new Mesh( + new ShapeGeometry( + panelPath(halfWidth, side === "left" ? "right" : "left", 0.01, 0.095, 0.002), + 12, + ), + back, + ); + rear.name = `${side}-back`; + rear.rotation.y = Math.PI; + rear.position.set(0, 0, -DEPTH / 2 - 0.001); + group.add(rear); + const geometry = new ShapeGeometry(panelPath(halfWidth, side, INSET, 0.076)); + geometry.computeBoundingBox(); + const display = new Mesh(geometry, hitMaterial); + display.name = `${side}-inner-screen`; + display.position.z = DEPTH / 2 + 0.003; + group.add(display); + return display; + } + + const innerLeft = half(leftBody, "left", bezel); + const innerRight = half(right, "right", backGlass); + // One indexed surface keeps adjacent pixels joined at the crease. The + // physical halves move separately underneath it. + const screenWidth = 2 * (halfWidth - INSET); + const screenGeometry = new PlaneGeometry(screenWidth, screenHeight, 40, 48); + const screenPositions = screenGeometry.getAttribute("position"); + const screenUvs = screenGeometry.getAttribute("uv"); + const baseX = new Float32Array(screenPositions.count); + for (let i = 0; i < screenPositions.count; i++) { + const y = screenPositions.getY(i); + const outerX = screenWidth / 2; + const outerY = screenHeight / 2; + const radius = 0.076; + const cornerY = Math.max(0, Math.abs(y) - (outerY - radius)); + const limit = outerX - radius + Math.sqrt(Math.max(0, radius * radius - cornerY * cornerY)); + const x = Math.max(-limit, Math.min(limit, screenPositions.getX(i))); + baseX[i] = x; + screenUvs.setXY(i, x / screenWidth + 0.5, y / screenHeight + 0.5); + } + screenUvs.needsUpdate = true; + const innerSurface = new Mesh(screenGeometry, displayMaterial); + innerSurface.name = "continuous-inner-screen"; + orientation.add(innerSurface); + // The outer half of the hinge housing. It tucks behind the back glass when + // open and becomes the rounded spine when closed. + const spineSlack = 0.15; + const spine = new Mesh( + new CylinderGeometry( + SPINE_RADIUS, + SPINE_RADIUS, + HEIGHT - 0.012, + 32, + 1, + false, + Math.PI / 2 + spineSlack, + Math.PI - spineSlack * 2, + ), + polishedMetal, + ); + spine.name = "hinge-spine"; + spine.position.z = PIVOT_Z; + orientation.add(spine); + + const cover = new Mesh(new ShapeGeometry(coverPath(halfWidth)), coverMaterial); + cover.name = "cover-screen"; + cover.position.set(-halfWidth / 2, 0, -DEPTH / 2 - 0.003); + cover.rotation.y = Math.PI; + leftBody.add(cover); + + // Rear components use back-surface coordinates, with outward positive Z. + const rearCamera = new Group(); + rearCamera.name = "rear-camera"; + const islandWidth = 0.46; + const islandHeight = 0.2; + rearCamera.position.set( + halfWidth - 0.07 - islandWidth / 2, + HEIGHT / 2 - 0.08 - islandHeight / 2, + -DEPTH / 2 - 0.002, + ); + rearCamera.rotation.y = Math.PI; + right.add(rearCamera); + const plateDepth = 0.02; + const plate = new Mesh( + new ExtrudeGeometry(roundedRectPath(islandWidth, islandHeight, 0.07), { + depth: plateDepth, + bevelEnabled: true, + bevelSize: 0.008, + bevelThickness: 0.006, + bevelSegments: 3, + curveSegments: 12, + }), + island, + ); + plate.name = "camera-plate"; + rearCamera.add(plate); + const plateFront = plateDepth + 0.006; + for (const [x, radius] of [ + [-0.14, 0.05], + [-0.01, 0.05], + [0.105, 0.036], + ] as const) { + const ring = new Mesh( + new CylinderGeometry(radius + 0.012, radius + 0.012, 0.012, 32), + frameMetal, + ); + ring.rotation.x = Math.PI / 2; + ring.position.set(x, 0, plateFront + 0.004); + rearCamera.add(ring); + const lens = new Mesh(new CircleGeometry(radius, 32), lensMaterial); + lens.name = "camera-lens"; + lens.position.set(x, 0, plateFront + 0.0105); + rearCamera.add(lens); + } + const flash = new Mesh(new CircleGeometry(0.018, 20), flashMaterial); + flash.position.set(0.185, 0.045, plateFront + 0.0005); + rearCamera.add(flash); + + // Power and volume keys sit on the fixed half's outer edge. + for (const [y, length] of [ + [0.52, 0.16], + [0.2, 0.3], + ] as const) { + const key = new Mesh(new BoxGeometry(0.02, length, DEPTH * 0.45), frameMetal); + key.position.set(halfWidth + 0.008, y, 0); + right.add(key); + } + + const raycaster = new Raycaster(); + const pointer = new Vector2(); + const local = new Vector3(); + let capturedDisplay: Mesh | null = null; + let activeLayout = layout; + let angle = initialAngle; + const updateVisibleScreen = () => { + const innerActive = angle >= 90; + innerSurface.visible = innerActive; + innerLeft.visible = innerActive; + innerRight.visible = innerActive; + cover.visible = !innerActive; + }; + const setAngle = (next: number) => { + angle = Math.max(0, Math.min(180, next)); + left.rotation.y = Math.PI * (1 - angle / 180); + spine.rotation.y = left.rotation.y / 2; + const radians = left.rotation.y; + const cosine = Math.cos(radians); + const sine = Math.sin(radians); + const frontZ = DEPTH / 2 + 0.004 - PIVOT_Z; + for (let i = 0; i < screenPositions.count; i++) { + const x = baseX[i]!; + if (x < 0) { + screenPositions.setXYZ( + i, + x * cosine + frontZ * sine, + screenPositions.getY(i), + -x * sine + frontZ * cosine + PIVOT_Z, + ); + } else { + screenPositions.setXYZ(i, x, screenPositions.getY(i), frontZ + PIVOT_Z); + } + } + screenPositions.needsUpdate = true; + screenGeometry.computeBoundingBox(); + screenGeometry.computeBoundingSphere(); + updateVisibleScreen(); + }; + const setDisplay = (nextTexture: Texture, nextLayout: PhoneDisplayLayout) => { + activeLayout = nextLayout; + displayMaterial.map = nextTexture; + coverMaterial.map = nextTexture; + cover.geometry.computeBoundingBox(); + const bounds = cover.geometry.boundingBox!; + const uv = cover.geometry.getAttribute("uv"); + const position = cover.geometry.getAttribute("position"); + for (let i = 0; i < uv.count; i++) { + const u = (position.getX(i) - bounds.min.x) / (bounds.max.x - bounds.min.x); + const v = (position.getY(i) - bounds.min.y) / (bounds.max.y - bounds.min.y); + uv.setXY(i, u, v); + } + uv.needsUpdate = true; + updateVisibleScreen(); + }; + setAngle(initialAngle); + setDisplay(texture, layout); + + return { + root, + orientation, + width: halfWidth * 2, + height: HEIGHT, + innerAspect, + setAngle, + setDisplay, + screenPoint(x: number, y: number, camera: Camera, captured = false) { + orientation.updateWorldMatrix(true, true); + camera.updateMatrixWorld(true); + pointer.set(x * 2 - 1, 1 - y * 2); + raycaster.setFromCamera(pointer, camera); + const screens = cover.visible ? [cover] : [innerLeft, innerRight]; + const hit = raycaster.intersectObjects(screens, false)[0]; + if (!hit && !captured) { + capturedDisplay = null; + return null; + } + const display = + (hit?.object as Mesh | undefined) ?? + (capturedDisplay?.visible ? capturedDisplay : screens[0]); + if (!display) return null; + if (hit) capturedDisplay = display; + if (hit) local.copy(hit.point); + else { + const plane = new Vector3(0, 0, 1).transformDirection(display.matrixWorld); + const point = new Vector3().setFromMatrixPosition(display.matrixWorld); + const distance = + plane.dot(point.clone().sub(raycaster.ray.origin)) / plane.dot(raycaster.ray.direction); + if (!Number.isFinite(distance)) return null; + local.copy(raycaster.ray.direction).multiplyScalar(distance).add(raycaster.ray.origin); + } + display.worldToLocal(local); + const bounds = display.geometry.boundingBox!; + const u = Math.max(0, Math.min(1, (local.x - bounds.min.x) / (bounds.max.x - bounds.min.x))); + const v = Math.max(0, Math.min(1, (bounds.max.y - local.y) / (bounds.max.y - bounds.min.y))); + const across = display === cover ? u : (display === innerLeft ? u : 1 + u) / 2; + return activeLayout.rotation === Math.PI ? { x: 1 - across, y: 1 - v } : { x: across, y: v }; + }, + dispose() { + root.traverse((object) => { + if (object instanceof Mesh) object.geometry.dispose(); + }); + for (const material of materials) material.dispose(); + }, + }; +} diff --git a/packages/client-runtime/src/device/duoControl.test.ts b/packages/client-runtime/src/device/duoControl.test.ts new file mode 100644 index 000000000000..019c383a56dd --- /dev/null +++ b/packages/client-runtime/src/device/duoControl.test.ts @@ -0,0 +1,109 @@ +import { afterEach, expect, it, vi } from "vite-plus/test"; +import { createDuoControl, createDuoPinch, type DuoCommand } from "./duoControl.ts"; +afterEach(() => vi.useRealTimers()); + +it("keeps a failed send visible, including a disconnect while draining queued motion", () => { + const send = vi.fn((_request: { requestId: number; command: DuoCommand }) => false); + const onChange = vi.fn(); + const queue = createDuoControl({ send, onChange }); + queue.enqueue({ control: "angle", value: 40 }); + expect(onChange).toHaveBeenLastCalledWith({ + pending: false, + requested: null, + error: "Device is disconnected.", + }); + send.mockReturnValueOnce(true); + queue.enqueue({ control: "pose", value: "book" }); + queue.enqueue({ control: "angle", value: 60 }); + queue.receive({ requestId: 2, ok: true }); + expect(onChange).toHaveBeenLastCalledWith({ + pending: false, + requested: null, + error: "Device is disconnected.", + }); +}); + +it("coalesces hinge edits behind acknowledgements and lets a preset replace queued edits", () => { + const send = vi.fn((_request: { requestId: number; command: DuoCommand }) => true); + const onChange = vi.fn(); + const queue = createDuoControl({ send, onChange }); + queue.enqueue({ control: "angle", value: 40 }); + queue.enqueue({ control: "angle", value: 50 }); + queue.enqueue({ control: "angle", value: 60 }); + expect(send).toHaveBeenCalledTimes(1); + queue.receive({ requestId: 1, ok: true }); + expect(send.mock.calls[1]?.[0]).toEqual({ + requestId: 2, + command: { control: "angle", value: 60 }, + }); + queue.enqueue({ control: "angle", value: 100 }); + queue.enqueue({ control: "pose", value: "tent" }); + queue.receive({ requestId: 2, ok: true }); + expect(send.mock.calls[2]?.[0]).toEqual({ + requestId: 3, + command: { control: "pose", value: "tent" }, + }); + queue.receive({ requestId: 3, ok: true }); + expect(onChange).toHaveBeenLastCalledWith({ pending: false, requested: null, error: null }); + queue.clear(); +}); + +it("drops queued commands on failure, timeout and disconnect; late replies cannot acknowledge later work", () => { + vi.useFakeTimers(); + const send = vi.fn((_request: { requestId: number; command: DuoCommand }) => true); + const onChange = vi.fn(); + const queue = createDuoControl({ send, onChange, timeoutMs: 100 }); + queue.enqueue({ control: "angle", value: 90 }); + queue.enqueue({ control: "angle", value: 100 }); + vi.advanceTimersByTime(100); + expect(onChange.mock.lastCall?.[0].error).toContain("timed out"); + queue.enqueue({ control: "pose", value: "open" }); + queue.receive({ requestId: 1, ok: true }); + expect(onChange.mock.lastCall?.[0].pending).toBe(true); + queue.enqueue({ control: "angle", value: 130 }); + queue.receive({ requestId: 2, ok: false, error: "native refused" }); + expect(onChange.mock.lastCall?.[0]).toEqual({ + pending: false, + requested: null, + error: "native refused", + }); + queue.enqueue({ control: "pose", value: "book" }); + queue.enqueue({ control: "pose", value: "closed" }); + queue.clear(); + queue.receive({ requestId: 3, ok: true }); + expect(send).toHaveBeenCalledTimes(3); + queue.enqueue({ control: "angle", value: Infinity }); + expect(send).toHaveBeenCalledTimes(3); + queue.clear(); +}); + +it("pinches only a hit device, accumulates independently of native readback, clamps and cancels", () => { + let confirmed = 90; + const change = vi.fn(); + const pinch = createDuoPinch({ + angle: () => confirmed, + contains: (x, y) => x > 0.2 && y > 0.2, + change, + }); + expect(pinch.begin(0.1, 0.5)).toBe(false); + pinch.move(1); + expect(change).not.toHaveBeenCalled(); + expect(pinch.begin(0.5, 0.5)).toBe(true); + pinch.move(0.25); + expect(change).toHaveBeenLastCalledWith(120); + confirmed = 100; + pinch.move(0.25); + expect(change).toHaveBeenLastCalledWith(150); + pinch.move(2); + expect(change).toHaveBeenLastCalledWith(180); + pinch.move(-3); + expect(change).toHaveBeenLastCalledWith(0); + pinch.move(NaN); + pinch.end(); + expect(change).toHaveBeenLastCalledWith(null); + const count = change.mock.calls.length; + pinch.move(1); + pinch.end(); + expect(change).toHaveBeenCalledTimes(count); + expect(pinch.active).toBe(false); +}); diff --git a/packages/client-runtime/src/device/duoControl.ts b/packages/client-runtime/src/device/duoControl.ts new file mode 100644 index 000000000000..b9f61ec997d9 --- /dev/null +++ b/packages/client-runtime/src/device/duoControl.ts @@ -0,0 +1,113 @@ +// @effect-diagnostics globalTimers:off - The stream owns this browser control queue and its timeout. +export const DUO_POSES = [ + { id: "closed", label: "Closed", angle: 0 }, + { id: "book", label: "Book", angle: 90 }, + { id: "open", label: "Open", angle: 180 }, + { id: "laptop", label: "Laptop", angle: 90 }, + { id: "tent", label: "Tent", angle: 80 }, +] as const; +export type DuoPose = (typeof DUO_POSES)[number]["id"]; +export type DuoOrientation = + | "portrait" + | "landscape_left" + | "portrait_upside_down" + | "landscape_right"; +export type DuoCommand = + | { control: "angle"; value: number } + | { control: "pose"; value: DuoPose } + | { control: "table"; value: boolean } + | { control: "physical"; value: "faceup" | "facedown" } + | { control: "orientation"; value: DuoOrientation }; +export type DuoControlState = { + pending: boolean; + requested: DuoCommand | null; + error: string | null; +}; + +/** One in-flight native transaction. Hinge motion coalesces; presets replace queued motion. Nothing replays after reconnect. */ +export function createDuoControl(options: { + send: (request: { requestId: number; command: DuoCommand }) => boolean; + onChange: (state: DuoControlState) => void; + timeoutMs?: number; +}) { + let nextId = 1; + let active: { requestId: number; command: DuoCommand } | null = null; + let queued: DuoCommand | null = null; + let timer: ReturnType | null = null; + const publish = (error: string | null = null) => + options.onChange({ pending: !!active, requested: queued ?? active?.command ?? null, error }); + const clear = (error: string | null = null) => { + if (timer) clearTimeout(timer); + timer = null; + active = null; + queued = null; + publish(error); + }; + const drain = () => { + if (active || !queued) return; + active = { requestId: nextId++, command: queued }; + queued = null; + const id = active.requestId; + timer = setTimeout(() => { + if (active?.requestId === id) clear("Device control timed out. Its position is unknown."); + }, options.timeoutMs ?? 5_000); + publish(); + if (!options.send(active)) clear("Device is disconnected."); + }; + return { + enqueue(command: DuoCommand) { + if ( + command.control === "angle" && + (!Number.isFinite(command.value) || command.value < 0 || command.value > 180) + ) + return; + queued = command; + if (active) publish(); + else drain(); + }, + receive(reply: { requestId: number; ok: boolean; error?: string }) { + if (!active || reply.requestId !== active.requestId) return; + if (timer) clearTimeout(timer); + timer = null; + active = null; + if (!reply.ok) { + clear(reply.error ?? "Device control failed. Its position is unknown."); + return; + } + if (queued) drain(); + else publish(); + }, + clear, + }; +} + +/** A pinch keeps its own accumulator across asynchronous native acknowledgements. */ +export function createDuoPinch(options: { + angle: () => number; + contains: (x: number, y: number) => boolean; + change: (angle: number | null) => void; +}) { + let angle: number | null = null; + return { + begin(x: number, y: number) { + if (!options.contains(x, y)) return false; + angle = Math.max(0, Math.min(180, options.angle())); + return true; + }, + move(logScale: number) { + if (angle === null || !Number.isFinite(logScale)) return; + const next = Math.max(0, Math.min(180, angle + logScale * 120)); + if (next === angle) return; + angle = next; + options.change(next); + }, + end() { + if (angle === null) return; + angle = null; + options.change(null); + }, + get active() { + return angle !== null; + }, + }; +} diff --git a/packages/client-runtime/src/device/duoScene.test.ts b/packages/client-runtime/src/device/duoScene.test.ts new file mode 100644 index 000000000000..4837bb6b71c3 --- /dev/null +++ b/packages/client-runtime/src/device/duoScene.test.ts @@ -0,0 +1,108 @@ +import { + BoxGeometry, + Group, + Mesh, + MeshBasicMaterial, + PerspectiveCamera, + PlaneGeometry, + Texture, + Vector3, +} from "three"; +import { expect, it } from "vite-plus/test"; +import { createDuoScene, duoDisplayKey, duoRawPoint } from "./duoScene.ts"; + +function fixture() { + const asset = new Group(); + const left = new Group(); + left.name = "left-half"; + const right = new Group(); + right.name = "right-half"; + const screen = (name: string, x: number, z: number, rear = false) => { + const mesh = new Mesh(new PlaneGeometry(1, 2), new MeshBasicMaterial()); + if (rear) mesh.geometry.rotateY(Math.PI); + mesh.geometry.translate(x, 0, z); + mesh.name = name; + return mesh; + }; + const innerLeft = screen("inner-display-left", -0.5, 0.06); + const innerRight = screen("inner-display-right", 0.5, 0.06); + const cover = screen("cover-display", -0.5, -0.06, true); + left.add(innerLeft, cover); + right.add(innerRight); + for (const [group, x] of [ + [left, -0.5], + [right, 0.5], + ] as const) { + const body = new Mesh(new BoxGeometry(1, 2, 0.1), new MeshBasicMaterial()); + body.geometry.translate(x, 0, 0); + group.add(body); + } + asset.add(left, right); + const scene = createDuoScene(asset, { 1: new Texture(), 3: new Texture() }); + const camera = new PerspectiveCamera(36, 1, 0.1, 50); + camera.position.z = 6; + camera.updateMatrixWorld(); + return { scene, camera, left, right, innerLeft, innerRight }; +} + +it("keeps one continuous inner UV map across the hinge and folds opposite leaves", () => { + const { scene, left, right, innerLeft, innerRight } = fixture(); + scene.setAngle(90); + expect(left.rotation.y).toBeCloseTo(Math.PI / 4); + expect(right.rotation.y).toBeCloseTo(-Math.PI / 4); + const values = (mesh: Mesh) => + Array.from({ length: mesh.geometry.getAttribute("uv").count }, (_, i) => + mesh.geometry.getAttribute("uv").getX(i), + ); + expect(Math.max(...values(innerLeft))).toBe(0.5); + expect(Math.min(...values(innerRight))).toBe(0.5); + scene.dispose(); +}); + +it("derives resting views from the hinged display planes independently of the inspection orbit", () => { + const { scene } = fixture(); + scene.setAngle(90); + const frames = scene.restFrames(3); + expect(frames.map((frame) => frame.face)).toEqual(["inside", "left", "right"]); + expect(frames[1]!.normal.x).toBeGreaterThan(0.5); + expect(frames[2]!.normal.x).toBeLessThan(-0.5); + scene.root.rotation.set(0.5, 1.2, -0.7); + scene.root.position.set(3, -2, 1); + const rotated = scene.restFrames(3); + for (let index = 0; index < frames.length; index++) { + expect(rotated[index]!.normal.distanceTo(frames[index]!.normal)).toBeLessThan(1e-6); + expect(rotated[index]!.center.distanceTo(frames[index]!.center)).toBeLessThan(1e-6); + } + scene.setAngle(180); + expect(scene.restFrames(3).map((frame) => frame.face)).toEqual(["inside"]); + expect(scene.restFrames(1).map((frame) => frame.face)).toEqual(["cover"]); + scene.dispose(); +}); + +it("maps active display input through hardware mounting and blocks rear, inactive and stale surfaces", () => { + const { scene, camera, innerLeft } = fixture(); + scene.setAngle(180); + const screen = { width: 2007, height: 2853, orientation: "portrait" as const, screenId: 3 }; + const key = duoDisplayKey(screen); + scene.root.updateMatrixWorld(); + const world = innerLeft.localToWorld(new Vector3(-0.5, -0.5, 0.06)).project(camera); + const x = (world.x + 1) / 2, + y = (1 - world.y) / 2; + const hit = scene.screenPoint(x, y, camera, screen, key); + expect(hit?.x).toBeCloseTo(0.75); + expect(hit?.y).toBeCloseTo(0.75); + expect( + scene.screenPoint( + x, + y, + camera, + { ...screen, screenId: 1 }, + duoDisplayKey({ ...screen, screenId: 1 }), + ), + ).toBeNull(); + expect(scene.screenPoint(x, y, camera, screen, "old")).toBeNull(); + expect(scene.screenPoint(x, y, camera, screen, key)).not.toBeNull(); + expect(scene.screenPoint(x, 1.1, camera, screen, key, true)?.x).toBeGreaterThan(1); + expect(duoRawPoint(1, 0.2, 0.7)).toEqual({ x: 0.2, y: 0.7 }); + scene.dispose(); +}); diff --git a/packages/client-runtime/src/device/duoScene.ts b/packages/client-runtime/src/device/duoScene.ts new file mode 100644 index 000000000000..eada17e5a8fc --- /dev/null +++ b/packages/client-runtime/src/device/duoScene.ts @@ -0,0 +1,244 @@ +import { + Box3, + BufferAttribute, + Group, + Mesh, + MeshBasicMaterial, + Matrix4, + Quaternion, + Plane, + Raycaster, + Triangle, + Vector2, + Vector3, + type PerspectiveCamera, + type Texture, +} from "three"; +import type { DuoRestFrame } from "./duoSnap.ts"; +import type { DeviceScreenSize } from "./stream.ts"; + +export type DuoPanelId = 1 | 3; +export type DuoHingeLeaf = "left" | "right"; +export type DuoFrameLayout = { width: number; height: number }; + +/** Hardware mounting is independent of app orientation and the model's orbit. The inverse is shared by taps and drags. */ +export function duoRawPoint(panel: DuoPanelId, x: number, y: number) { + return panel === 1 ? { x, y } : { x: y, y: 1 - x }; +} +export function duoFrameMatches(frame: DuoFrameLayout, screen: DeviceScreenSize) { + return ( + frame.width > 0 && + frame.height > 0 && + Math.abs(frame.width / frame.height - screen.width / screen.height) <= + 1 / frame.height + 1 / screen.height + ); +} +export function duoDisplayKey(screen: DeviceScreenSize | null) { + return screen ? `${screen.screenId}:${screen.width}:${screen.height}:${screen.orientation}` : ""; +} + +/** Asset resources belong to its model slot; this scene owns only live-screen replacement materials. */ +export function createDuoScene(asset: Group, textures: Record) { + const left = asset.getObjectByName("left-half"); + const right = asset.getObjectByName("right-half"); + const cover = asset.getObjectByName("cover-display"); + const innerLeft = asset.getObjectByName("inner-display-left"); + const innerRight = asset.getObjectByName("inner-display-right"); + if ( + !left || + !right || + !(cover instanceof Mesh) || + !(innerLeft instanceof Mesh) || + !(innerRight instanceof Mesh) + ) + throw new Error("Duo model must have two hinge groups and all three display meshes"); + const surfaces = { 1: [cover], 3: [innerLeft, innerRight] }; + const boundsByPanel = ([1, 3] as const).map((id) => { + const bounds = new Box3(); + for (const mesh of surfaces[id]) { + mesh.geometry.computeBoundingBox(); + bounds.union(mesh.geometry.boundingBox!); + } + const size = bounds.getSize(new Vector3()); + if (![size.x, size.y].every((value) => Number.isFinite(value) && value > 0)) + throw new Error("Invalid Duo display bounds"); + return { bounds, size }; + }); + const root = new Group(); + const content = new Group(); + content.add(asset); + root.add(content); + let currentAngle = Number.NaN; + const localBounds = new Box3(); + const relative = new Matrix4(); + const inverse = new Matrix4(); + const materials = { + 1: new MeshBasicMaterial({ map: textures[1], toneMapped: false }), + 3: new MeshBasicMaterial({ map: textures[3], toneMapped: false }), + }; + const originals = new Map(); + // The two inner meshes sample halves of one framebuffer. Cover UVs face outward on the rear leaf. + for (const id of [1, 3] as const) { + const { bounds, size } = boundsByPanel[id === 1 ? 0 : 1]!; + for (const mesh of surfaces[id]) { + const position = mesh.geometry.getAttribute("position"); + const uv = new Float32Array(position.count * 2); + for (let i = 0; i < position.count; i++) { + const x = (position.getX(i) - bounds.min.x) / size.x; + uv[i * 2] = id === 1 ? 1 - x : x; + uv[i * 2 + 1] = (position.getY(i) - bounds.min.y) / size.y; + } + mesh.geometry.setAttribute("uv", new BufferAttribute(uv, 2)); + originals.set(mesh, mesh.material); + mesh.material = materials[id]; + } + } + const ray = new Raycaster(); + let captured: { + panel: DuoPanelId; + plane: Plane; + triangle: Triangle; + uv: [Vector2, Vector2, Vector2]; + key: string; + } | null = null; + return { + root, + setAngle(angle: number) { + if (angle === currentAngle) return; + currentAngle = angle; + const radians = ((180 - Math.min(180, Math.max(0, angle))) * Math.PI) / 360; + left.rotation.y = radians; + right.rotation.y = -radians; + // Orbit the folded body's center, while retaining the authored hinge pivots. + content.position.set(0, 0, 0); + root.updateWorldMatrix(true, true); + inverse.copy(root.matrixWorld).invert(); + localBounds.makeEmpty(); + asset.traverse((object) => { + if (!(object instanceof Mesh)) return; + if (!object.geometry.boundingBox) object.geometry.computeBoundingBox(); + if (!object.geometry.boundingBox) return; + relative.multiplyMatrices(inverse, object.matrixWorld); + localBounds.union(object.geometry.boundingBox.clone().applyMatrix4(relative)); + }); + content.position.copy(localBounds.getCenter(new Vector3())).negate(); + }, + restFrames(panel: DuoPanelId): DuoRestFrame[] { + root.updateWorldMatrix(true, true); + inverse.copy(root.matrixWorld).invert(); + const frame = (meshes: Mesh[], face: DuoRestFrame["face"]): DuoRestFrame => { + const bounds = new Box3(); + const normal = new Vector3(); + const up = new Vector3(); + for (const mesh of meshes) { + if (!mesh.geometry.boundingBox) mesh.geometry.computeBoundingBox(); + relative.multiplyMatrices(inverse, mesh.matrixWorld); + bounds.union(mesh.geometry.boundingBox!.clone().applyMatrix4(relative)); + normal.add( + new Vector3() + .fromBufferAttribute(mesh.geometry.getAttribute("normal"), 0) + .transformDirection(relative), + ); + up.add(new Vector3(0, 1, 0).transformDirection(relative)); + } + return { + face, + center: bounds.getCenter(new Vector3()), + normal: normal.normalize(), + up: up.normalize(), + }; + }; + if (panel === 1) return [frame([cover], "cover")]; + const inside = frame([innerLeft, innerRight], "inside"); + // Leaf views only make sense when both displays form a useful open fold. + return currentAngle > 20 && currentAngle < 165 + ? [inside, frame([innerLeft], "left"), frame([innerRight], "right")] + : [inside]; + }, + leafRotation(leaf: DuoHingeLeaf) { + root.updateWorldMatrix(true, true); + relative.multiplyMatrices( + root.matrixWorld.clone().invert(), + (leaf === "left" ? left : right).matrixWorld, + ); + return new Quaternion().setFromRotationMatrix(relative.extractRotation(relative)); + }, + hingeLeafAt(x: number, y: number, camera: PerspectiveCamera): DuoHingeLeaf | null { + if (!Number.isFinite(x) || !Number.isFinite(y)) return null; + root.updateMatrixWorld(true); + camera.updateMatrixWorld(true); + ray.setFromCamera(new Vector2(x * 2 - 1, 1 - y * 2), camera); + let object = ray.intersectObject(root, true)[0]?.object; + while (object) { + if (object === left) return "left"; + if (object === right) return "right"; + object = object.parent ?? undefined; + } + return null; + }, + cancelInput() { + captured = null; + }, + screenPoint( + x: number, + y: number, + camera: PerspectiveCamera, + screen: DeviceScreenSize | null, + readyKey: string, + extend = false, + ) { + const key = duoDisplayKey(screen); + if (!screen || !readyKey || key !== readyKey) { + captured = null; + return null; + } + root.updateMatrixWorld(true); + camera.updateMatrixWorld(true); + ray.setFromCamera(new Vector2(x * 2 - 1, 1 - y * 2), camera); + if (extend && captured?.key === key) { + const point = ray.ray.intersectPlane(captured.plane, new Vector3()); + const bary = point ? captured.triangle.getBarycoord(point, new Vector3()) : null; + if (!bary) return null; + const uv = captured.uv[0] + .clone() + .multiplyScalar(bary.x) + .addScaledVector(captured.uv[1], bary.y) + .addScaledVector(captured.uv[2], bary.z); + return duoRawPoint(captured.panel, uv.x, 1 - uv.y); + } + // The chassis occludes rear displays. Only the first visible hit can own a contact. + const hit = ray.intersectObject(root, true)[0]; + if (!hit?.uv || !hit.face || !(hit.object instanceof Mesh)) return null; + const panel: DuoPanelId | null = surfaces[1].includes(hit.object) + ? 1 + : surfaces[3].includes(hit.object) + ? 3 + : null; + if (!panel || panel !== screen.screenId) return null; + const indices = [hit.face.a, hit.face.b, hit.face.c] as const; + const positions = hit.object.geometry.getAttribute("position"); + const triangle = new Triangle( + ...(indices.map((index) => + new Vector3().fromBufferAttribute(positions, index).applyMatrix4(hit.object.matrixWorld), + ) as [Vector3, Vector3, Vector3]), + ); + const hitUvs = hit.object.geometry.getAttribute("uv"); + const uv = indices.map((index) => new Vector2().fromBufferAttribute(hitUvs, index)) as [ + Vector2, + Vector2, + Vector2, + ]; + captured = { panel, triangle, uv, plane: triangle.getPlane(new Plane()), key }; + return duoRawPoint(panel, hit.uv.x, 1 - hit.uv.y); + }, + dispose() { + for (const [mesh, material] of originals) mesh.material = material; + for (const material of Object.values(materials)) { + material.map = null; + material.dispose(); + } + content.remove(asset); + captured = null; + }, + }; +} diff --git a/packages/client-runtime/src/device/duoSnap.test.ts b/packages/client-runtime/src/device/duoSnap.test.ts new file mode 100644 index 000000000000..9b312a4fb45e --- /dev/null +++ b/packages/client-runtime/src/device/duoSnap.test.ts @@ -0,0 +1,54 @@ +import { expect, it } from "vite-plus/test"; +import { Quaternion, Vector3 } from "three"; +import { duoViewSnaps, nearestDuoView } from "./duoSnap.ts"; + +const frames = [ + { + face: "inside" as const, + normal: new Vector3(0, 0, 1), + up: new Vector3(0, 1, 0), + center: new Vector3(), + }, +]; +const rotation = (x: number, y: number, z = 0) => + new Quaternion().setFromAxisAngle(new Vector3(x, y, z).normalize(), Math.hypot(x, y, z)); + +it("chooses a leaf-focused seated view when it is closer than the middle of the fold", () => { + const leaf = { + face: "right" as const, + normal: new Vector3(0.6, 0, 0.8), + up: new Vector3(0, 1, 0), + center: new Vector3(1, 0, 0), + }; + const candidates = duoViewSnaps([...frames, leaf], 3); + const seat = candidates.find( + (candidate) => candidate.face === "right" && candidate.orientation === "portrait", + )!; + const released = seat.rotation.clone().premultiply(rotation(0.04, 0.05)); + const chosen = nearestDuoView(released, candidates)!; + expect(chosen.face).toBe("right"); + expect(chosen.orientation).toBe("portrait"); + expect(chosen.center.x).toBe(1); +}); + +it("a seated view exposes the base, and upright yaw keeps both inner displays facing the camera", () => { + const folded = [ + frames[0]!, + ...(["left", "right"] as const).map((face) => ({ + face, + normal: new Vector3(face === "left" ? Math.SQRT1_2 : -Math.SQRT1_2, 0, Math.SQRT1_2), + up: new Vector3(0, 1, 0), + center: new Vector3(face === "left" ? -1 : 1, 0, 0), + })), + ]; + const candidates = duoViewSnaps(folded, 3); + expect(candidates.filter((candidate) => candidate.face === "right")).toHaveLength(1); + for (const candidate of candidates) { + for (const frame of folded.slice(1)) + expect(frame.normal.clone().applyQuaternion(candidate.rotation).z).toBeGreaterThan(0.2); + const side = candidate.rotation.clone().premultiply(rotation(0, 1.5)); + const chosen = nearestDuoView(side, [candidate])!; + for (const frame of folded.slice(1)) + expect(frame.normal.clone().applyQuaternion(chosen.rotation).z).toBeGreaterThan(0.02); + } +}); diff --git a/packages/client-runtime/src/device/duoSnap.ts b/packages/client-runtime/src/device/duoSnap.ts new file mode 100644 index 000000000000..1e0e3f46fd0c --- /dev/null +++ b/packages/client-runtime/src/device/duoSnap.ts @@ -0,0 +1,86 @@ +import { Matrix4, Quaternion, Vector3 } from "three"; +import { nearestDeviceView } from "./deviceViewSnap.ts"; +import type { DeviceScreenSize } from "./stream.ts"; + +export type DuoRestFace = "cover" | "inside" | "left" | "right"; +export type DuoRestFrame = { face: DuoRestFace; normal: Vector3; up: Vector3; center: Vector3 }; +export type DuoViewSnap = { + rotation: Quaternion; + face: DuoRestFace; + orientation: DeviceScreenSize["orientation"]; + center: Vector3; + yawLimit: number; +}; +const orientations = [ + "portrait", + "landscape_left", + "portrait_upside_down", + "landscape_right", +] as const; +const z = new Vector3(0, 0, 1); +const y = new Vector3(0, 1, 0); + +/** Build views from the actual hinged display planes, rather than model-specific Euler offsets. */ +export function duoViewSnaps(frames: readonly DuoRestFrame[], panel: 1 | 3) { + const snaps: DuoViewSnap[] = []; + for (const frame of frames) { + const normal = frame.normal.clone().normalize(); + if (normal.lengthSq() < 0.5) continue; + const right = frame.up.clone().cross(normal).normalize(); + const up = normal.clone().cross(right).normalize(); + const faceRotation = new Quaternion() + .setFromRotationMatrix(new Matrix4().makeBasis(right, up, normal)) + .invert(); + for (let index = 0; index < orientations.length; index++) { + // Only these rolls put the partner below the focused lid. Other quarter + // turns belong to the upright, whole-display family. + if (frame.face === "right" && index !== 0) continue; + if (frame.face === "left" && index !== 2) continue; + const roll = (panel === 3 ? Math.PI / 2 : 0) - (index * Math.PI) / 2; + const rotation = faceRotation.clone().premultiply(new Quaternion().setFromAxisAngle(z, roll)); + // A leaf-focused view looks slightly down onto its partner, as in a seated laptop. + if (frame.face === "left" || frame.face === "right") + rotation.premultiply(new Quaternion().setFromAxisAngle(new Vector3(1, 0, 0), Math.PI / 12)); + if ( + panel === 3 && + frames.some( + (leaf) => + (leaf.face === "left" || leaf.face === "right") && + leaf.normal.clone().applyQuaternion(rotation).z < 0.04, + ) + ) + continue; + // Bound yaw using both real leaf normals. A narrow fold cannot tolerate + // the same side view as a fully open display. + let yawLimit = frame.face === "cover" ? Math.PI / 9 : Math.PI / 3; + if (panel === 3) { + for (const sign of [-1, 1]) { + for (let step = 1; step <= 60; step++) { + const turn = new Quaternion().setFromAxisAngle(y, (sign * step * Math.PI) / 180); + if ( + frames.some( + (leaf) => + (leaf.face === "left" || leaf.face === "right") && + leaf.normal.clone().applyQuaternion(rotation).applyQuaternion(turn).z < 0.04, + ) + ) { + yawLimit = Math.min(yawLimit, ((step - 1) * Math.PI) / 180); + break; + } + } + } + } + snaps.push({ + rotation, + face: frame.face, + orientation: orientations[index]!, + center: frame.center.clone(), + yawLimit, + }); + } + } + return snaps; +} + +/** Fold-specific candidates use the base viewer's nearest-family selection. */ +export const nearestDuoView = nearestDeviceView; diff --git a/packages/client-runtime/src/device/duoStream.test.ts b/packages/client-runtime/src/device/duoStream.test.ts new file mode 100644 index 000000000000..f290bef1712d --- /dev/null +++ b/packages/client-runtime/src/device/duoStream.test.ts @@ -0,0 +1,285 @@ +import { afterEach, expect, it, vi } from "vite-plus/test"; +import { createDeviceStreamClient, type DeviceScreenSize } from "./stream.ts"; +afterEach(() => vi.unstubAllGlobals()); + +it("switches to fixed authenticated feeds while retaining one HID socket, routes the active panel and rejects superseded decoded output", async () => { + const feeds: { + url: string; + signal: AbortSignal; + controller: ReadableStreamDefaultController; + }[] = []; + const outputs: VideoFrameOutputCallback[] = []; + const errors: VideoDecoderInit["error"][] = []; + let supported = true; + let panelHttpStatus = 200; + let expected = 0; + let decoderReady = () => {}; + const waitDecoders = (count: number) => { + expected = count; + return new Promise((resolve) => { + decoderReady = resolve; + }); + }; + class Decoder { + static isConfigSupported = async () => ({ supported }); + static instances: Decoder[] = []; + state = "unconfigured"; + constructor(options: VideoDecoderInit) { + outputs.push(options.output); + errors.push(options.error); + Decoder.instances.push(this); + if (outputs.length === expected) decoderReady(); + } + configure() { + this.state = "configured"; + } + close() { + this.state = "closed"; + } + } + let socketReady = () => {}; + const socketConstructed = new Promise((resolve) => { + socketReady = resolve; + }); + class Socket { + static OPEN = 1; + static instances: Socket[] = []; + readyState = 1; + binaryType = "arraybuffer"; + onopen?: () => void; + onmessage?: (event: { data: ArrayBuffer }) => void; + onclose?: (event: { code: number; reason: string }) => void; + send = vi.fn(); + close = vi.fn(); + constructor() { + Socket.instances.push(this); + socketReady(); + } + } + vi.stubGlobal("VideoDecoder", Decoder); + vi.stubGlobal("EncodedVideoChunk", vi.fn()); + vi.stubGlobal("WebSocket", Socket); + vi.stubGlobal( + "fetch", + vi.fn(async (url: string, options: { signal: AbortSignal }) => { + if (!url.includes("avcc")) return new Response("prime"); + if (url.includes("/panel/") && panelHttpStatus !== 200) + return new Response("unsupported panel", { status: panelHttpStatus }); + const stream = new ReadableStream({ + start(controller) { + feeds.push({ url, signal: options.signal, controller }); + options.signal.addEventListener("abort", () => + controller.error(new DOMException("Aborted", "AbortError")), + ); + }, + }); + return new Response(stream); + }), + ); + const present = vi.fn((_source: CanvasImageSource, _width: number, _height: number) => true), + cover = vi.fn((_source: CanvasImageSource, _width: number, _height: number) => true), + inner = vi.fn((_source: CanvasImageSource, _width: number, _height: number) => true); + let panelFailed = () => {}; + const panelFailure = new Promise((resolve) => { + panelFailed = resolve; + }); + const onStatus = vi.fn(); + const onDuoControl = vi.fn(); + const onDuoUnavailable = vi.fn((_detail?: string) => panelFailed()); + const client = createDeviceStreamClient( + { + platform: "ios", + deviceId: "duo", + access: { + httpBase: "https://t3.test/api/device-hub", + wsBase: "wss://t3.test/api/device-hub", + credentials: true, + query: { hostId: "remote", wsTicket: "ticket" }, + }, + }, + { present }, + { + onStatus, + onDuoControl, + onDuoUnavailable, + onScreen: vi.fn(), + onInputConnected: vi.fn(), + onUnauthorized: vi.fn(), + onMjpegFallback: vi.fn(), + }, + ); + client.start(); + await socketConstructed; + const ws = Socket.instances[0]!; + const config = ( + id: number, + orientation: DeviceScreenSize["orientation"] = "portrait", + physical = false, + ) => { + const json = new TextEncoder().encode( + JSON.stringify({ + width: id === 1 ? 1398 : 2007, + height: id === 1 ? 2034 : 2853, + orientation, + screenId: id, + supportsHingeAngle: true, + supportsPhysicalOrientation: physical, + hingePose: "open", + }), + ); + const packet = new Uint8Array(json.length + 1); + packet[0] = 0x82; + packet.set(json, 1); + ws.onmessage?.({ data: packet.buffer }); + }; + config(3); + const requestedOrientation = () => + JSON.parse(new TextDecoder().decode(ws.send.mock.lastCall?.[0].subarray(1))).orientation; + client.rotate(); + expect(requestedOrientation()).toBe("landscape_left"); + config(3); // An orientation-locked app keeps its framebuffer orientation after the sensor rotates. + expect(onDuoControl.mock.lastCall?.[0].error).toBeNull(); + client.rotate(); + expect(requestedOrientation()).toBe("portrait_upside_down"); + config(3, "portrait_upside_down"); + client.rotate(); + expect(requestedOrientation()).toBe("landscape_right"); + config(3); // An external native orientation change becomes authoritative again. + client.rotate(); + expect(requestedOrientation()).toBe("landscape_left"); + config(3, "landscape_left"); + expect(onDuoControl.mock.lastCall?.[0].pending).toBe(false); + client.controlDuo({ control: "angle", value: 40 }); + const angleRequest = JSON.parse(new TextDecoder().decode(ws.send.mock.lastCall?.[0].subarray(1))); + const before = ws.send.mock.calls.length; + client.controlDuo({ control: "orientation", value: "portrait" }); + expect(ws.send.mock.calls.length).toBe(before); + config(3, "landscape_left"); // The angle's config precedes its receipt; it cannot acknowledge a queued rotation. + const reply = new TextEncoder().encode( + JSON.stringify({ requestId: angleRequest.requestId, ok: true }), + ); + const receipt = new Uint8Array(reply.length + 1); + receipt[0] = 0x90; + receipt.set(reply, 1); + ws.onmessage?.({ data: receipt.buffer }); + expect(requestedOrientation()).toBe("portrait"); + expect(ws.send.mock.lastCall?.[0][0]).toBe(0x07); + client.controlDuo({ control: "angle", value: 55 }); + const orientationSends = ws.send.mock.calls.length; + expect(onDuoControl.mock.lastCall?.[0].pending).toBe(true); + config(3); + expect(ws.send.mock.calls.length).toBe(orientationSends + 1); + const after = JSON.parse(new TextDecoder().decode(ws.send.mock.lastCall?.[0].subarray(1))); + expect(after.command).toEqual({ control: "angle", value: 55 }); + const finalReply = new TextEncoder().encode( + JSON.stringify({ requestId: after.requestId, ok: true }), + ); + const finalReceipt = new Uint8Array(finalReply.length + 1); + finalReceipt[0] = 0x90; + finalReceipt.set(finalReply, 1); + ws.onmessage?.({ data: finalReceipt.buffer }); + expect(onDuoControl.mock.lastCall?.[0].pending).toBe(false); + const description = new Uint8Array([0, 0, 0, 5, 1, 1, 0x64, 0, 0x1f]); + let ready = waitDecoders(1); + feeds[0]!.controller.enqueue(description); + await ready; + client.setDuoPanels({ cover: { present: cover }, inner: { present: inner } }); + expect(feeds[0]!.signal.aborted).toBe(true); + expect(Socket.instances).toHaveLength(1); + for (const [index, id] of [ + [1, 1], + [2, 3], + ]) { + const url = new URL(feeds[index!]!.url); + expect(url.pathname.endsWith(`/panel/${id}/stream.avcc`)).toBe(true); + expect(url.searchParams.get("hostId")).toBe("remote"); + expect(url.searchParams.get("wsTicket")).toBe("ticket"); + } + ready = waitDecoders(3); + feeds[1]!.controller.enqueue(description); + feeds[2]!.controller.enqueue(description); + await ready; + const frame = { + displayWidth: 2007, + displayHeight: 2853, + close: vi.fn(), + } as unknown as VideoFrame; + outputs[0]!(frame); + expect(present).not.toHaveBeenCalled(); + outputs[1]!(frame); + expect(cover).not.toHaveBeenCalled(); + outputs[2]!(frame); + expect(inner).toHaveBeenCalledOnce(); + expect(present).toHaveBeenCalledOnce(); + config(1); + outputs[2]!(frame); + expect(inner).toHaveBeenCalledOnce(); + outputs[1]!(frame); + expect(cover).toHaveBeenCalledOnce(); + client.setDuoPanels(null); + expect(feeds[1]!.signal.aborted).toBe(true); + expect(feeds[2]!.signal.aborted).toBe(true); + expect(feeds[3]!.url).not.toContain("/panel/"); + outputs[1]!(frame); + expect(cover).toHaveBeenCalledOnce(); + ready = waitDecoders(4); + feeds[3]!.controller.enqueue(description); + await ready; + errors[0]!(new DOMException("Late decoder failure")); + expect(Decoder.instances[3]!.state).toBe("configured"); + config(1, "portrait", true); + client.setDuoPanels({ cover: { present: cover }, inner: { present: inner } }); + expect(feeds).toHaveLength(5); // One active feed replaces the two fixed feeds. + expect(feeds[4]!.url).not.toContain("/panel/"); + expect(new URL(feeds[4]!.url).searchParams.get("hostId")).toBe("remote"); + ready = waitDecoders(5); + feeds[4]!.controller.enqueue(description); + await ready; + const primary = { ...frame, displayWidth: 1398, displayHeight: 2034 } as VideoFrame; + const painted = present.mock.calls.length; + outputs[4]!(primary); + expect(present).toHaveBeenCalledTimes(painted + 1); + expect(present.mock.lastCall?.[0]).toBe(primary); + expect(cover).toHaveBeenCalledOnce(); // The main sink owns primary texture delivery. + config(3, "portrait", true); + expect(feeds[4]!.signal.aborted).toBe(true); + expect(feeds).toHaveLength(6); + ready = waitDecoders(6); + feeds[5]!.controller.enqueue(description); + await ready; + outputs[4]!(primary); // The old elected display cannot paint after handoff. + expect(present).toHaveBeenCalledTimes(painted + 1); + outputs[5]!(frame); + expect(present).toHaveBeenCalledTimes(painted + 2); + expect(present.mock.lastCall?.[0]).toBe(frame); + config(3, "portrait", true); + expect(feeds).toHaveLength(6); // Duplicate native readback does not reconnect. + supported = false; + client.setDuoPanels(null); + client.setDuoPanels({ cover: { present: cover }, inner: { present: inner } }); + expect(feeds[7]!.url).not.toContain("/panel/"); + feeds[7]!.controller.enqueue(description); + await panelFailure; + expect(onDuoUnavailable.mock.lastCall?.[0]).toContain("cannot decode"); + config(3); + const fixedPanelFailure = new Promise((resolve) => { + panelFailed = resolve; + }); + client.setDuoPanels({ cover: { present: cover }, inner: { present: inner } }); + feeds[8]!.controller.enqueue(description); + await fixedPanelFailure; + expect(onDuoUnavailable).toHaveBeenCalled(); + expect(onStatus.mock.calls.some(([status]) => status === "error")).toBe(false); + panelHttpStatus = 404; + const missingPanel = new Promise((resolve) => { + panelFailed = resolve; + }); + client.setDuoPanels({ cover: { present: cover }, inner: { present: inner } }); + await missingPanel; + expect(onDuoUnavailable.mock.lastCall?.[0]).toContain("does not provide fixed Duo"); + expect(Socket.instances).toHaveLength(1); + client.stop(); + expect(feeds[3]!.signal.aborted).toBe(true); + expect(ws.close).toHaveBeenCalledOnce(); + expect(frame.close).toHaveBeenCalledTimes(9); +}); diff --git a/packages/client-runtime/src/device/duoViewer.test.ts b/packages/client-runtime/src/device/duoViewer.test.ts new file mode 100644 index 000000000000..b85d306bcfd6 --- /dev/null +++ b/packages/client-runtime/src/device/duoViewer.test.ts @@ -0,0 +1,703 @@ +import { afterEach, expect, it, vi } from "vite-plus/test"; +import type { Scene, PerspectiveCamera, Box3, Quaternion } from "three"; + +const gpu = vi.hoisted(() => ({ + instances: [] as { + blank: boolean; + allocations: number; + frames: Scene[]; + views: { camera: PerspectiveCamera; bounds: Box3; quaternion: Quaternion }[]; + dispose: ReturnType; + forceContextLoss: ReturnType; + }[], + environmentDispose: vi.fn(), +})); +vi.mock("three", async () => { + const actual = await vi.importActual("three"); + return { + ...actual, + WebGLRenderer: class { + state = { + blank: true, + allocations: 0, + frames: [] as Scene[], + views: [] as { camera: PerspectiveCamera; bounds: Box3; quaternion: Quaternion }[], + dispose: vi.fn(), + forceContextLoss: vi.fn(), + }; + constructor() { + gpu.instances.push(this.state); + } + setDrawingBufferSize() { + this.state.blank = true; + this.state.allocations++; + } + render(scene: Scene, camera: PerspectiveCamera) { + this.state.blank = false; + this.state.frames.push(scene); + const root = scene.children.find((child) => child instanceof actual.Group)!; + root.updateMatrixWorld(true); + camera.updateMatrixWorld(true); + this.state.views.push({ + camera: camera.clone(), + bounds: new actual.Box3().setFromObject(root), + quaternion: root.quaternion.clone(), + }); + } + dispose() { + this.state.dispose(); + } + forceContextLoss() { + this.state.forceContextLoss(); + } + }, + PMREMGenerator: class { + fromScene() { + return { texture: new actual.Texture(), dispose: gpu.environmentDispose }; + } + dispose() {} + }, + }; +}); +const models = vi.hoisted(() => ({ + resolve: (_model: { asset: import("three").Group; dispose: () => void }) => {}, + signal: null as AbortSignal | null, +})); +vi.mock("./modelScene.ts", () => ({ + loadDeviceModel: (_source: unknown, signal: AbortSignal) => { + models.signal = signal; + return new Promise((resolve) => { + models.resolve = resolve; + }); + }, +})); +import { + Group, + Mesh, + MeshBasicMaterial, + PlaneGeometry, + Vector3, + Euler, + Quaternion as Rotation, +} from "three"; +import { createDuoViewer } from "./duoViewer.ts"; + +afterEach(() => { + vi.useRealTimers(); + vi.unstubAllGlobals(); + gpu.instances.length = 0; + gpu.environmentDispose.mockClear(); +}); + +function fixture( + reduced = true, + onOrientationRequested = vi.fn(), + onPanelRequested?: (panel: 1 | 3) => void, +) { + const pending = new Map(); + let id = 0; + vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => { + pending.set(++id, callback); + return id; + }); + vi.stubGlobal("cancelAnimationFrame", (id: number) => pending.delete(id)); + vi.stubGlobal("matchMedia", () => ({ matches: reduced })); + const drawImage = vi.fn(); + vi.stubGlobal("document", { + createElement: () => ({ + width: 0, + height: 0, + getContext: () => ({ + fillRect() {}, + save() {}, + restore() {}, + translate() {}, + rotate() {}, + drawImage, + getImageData: () => ({ data: new Uint8ClampedArray(8 * 8 * 4).fill(255) }), + }), + }), + }); + const canvas = new EventTarget() as HTMLCanvasElement; + const viewer = createDuoViewer({ + canvas, + sources: { + 1: { width: 1398, height: 2034 } as HTMLCanvasElement, + 3: { width: 2007, height: 2853 } as HTMLCanvasElement, + }, + model: { id: "iphone-duo", url: "/duo.glb" }, + onUnavailable: vi.fn(), + onOrientationRequested, + ...(onPanelRequested ? { onPanelRequested } : {}), + }); + const draw = () => { + const callbacks = [...pending.values()]; + pending.clear(); + callbacks.forEach((callback) => callback(0)); + }; + return { viewer, draw, pending, state: gpu.instances[0]!, onOrientationRequested, drawImage }; +} + +function asset() { + const group = new Group(); + for (const [name, names] of [ + ["left-half", ["cover-display", "inner-display-left"]], + ["right-half", ["inner-display-right"]], + ] as const) { + const leaf = new Group(); + leaf.name = name; + for (const name of names) { + const mesh = new Mesh(new PlaneGeometry(1, 2), new MeshBasicMaterial()); + if (name === "cover-display") mesh.geometry.rotateY(Math.PI); + mesh.geometry.translate( + name === "inner-display-right" ? 0.5 : -0.5, + 0, + name === "cover-display" ? -0.05 : 0.05, + ); + mesh.name = name; + leaf.add(mesh); + } + group.add(leaf); + } + return group; +} + +it("coalesces resize with redraw, retains the renderer and scene, and settles without an idle animation loop", async () => { + let now = 0; + vi.stubGlobal("performance", { now: () => now }); + const { viewer, draw, pending, state } = fixture(); + const dispose = vi.fn(); + models.resolve({ asset: asset(), dispose }); + await Promise.resolve(); + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "portrait", + screenId: 1, + hingeAngle: 0, + }); + viewer.resize(500, 700, 2); + draw(); + const scene = state.frames.at(-1); + viewer.resize(450, 700, 2); + viewer.resize(400, 700, 2); + viewer.orbit(0.1, 0.04); + expect(state.blank).toBe(false); + expect(state.allocations).toBe(1); + expect(pending.size).toBe(1); + draw(); + expect(state.blank).toBe(false); + expect(state.allocations).toBe(2); + expect(state.frames.at(-1)).toBe(scene); + expect(gpu.instances).toHaveLength(1); + now += 1000; + draw(); + draw(); + expect(pending.size).toBe(0); + viewer.dispose(); + viewer.dispose(); + expect(dispose).toHaveBeenCalledOnce(); + expect(state.dispose).toHaveBeenCalledOnce(); + expect(gpu.environmentDispose).toHaveBeenCalledOnce(); + expect(pending.size).toBe(0); +}); + +it("cancels loading and disposes a late model after unmount without installing or rendering it", async () => { + const { viewer, draw, state } = fixture(); + viewer.resize(400, 700, 2); + viewer.dispose(); + expect(models.signal?.aborted).toBe(true); + const dispose = vi.fn(); + models.resolve({ asset: asset(), dispose }); + await Promise.resolve(); + draw(); + expect(dispose).toHaveBeenCalledOnce(); + expect(state.frames).toHaveLength(0); +}); + +it("keeps the chosen release view when an app locks orientation, freezes it during contact, and stops drawing after release", async () => { + let now = 0; + vi.stubGlobal("performance", { now: () => now }); + const { viewer, draw, state, pending, onOrientationRequested } = fixture(); + models.resolve({ asset: asset(), dispose: vi.fn() }); + await Promise.resolve(); + const screen = { + width: 1398, + height: 2034, + orientation: "portrait" as const, + screenId: 1, + hingeAngle: 0, + }; + viewer.setScreen(screen); + viewer.resize(500, 700, 2); + draw(); + const initial = state.views.at(-1)!.quaternion.clone(); + viewer.setInteractionActive(true, "orbit"); + viewer.orbit(0, Math.PI / 3); + draw(); + const held = state.views.at(-1)!.quaternion.clone(); + now = 1000; + viewer.frameUpdated(1); + draw(); + expect(state.views.at(-1)!.quaternion.angleTo(held)).toBeLessThan(1e-6); + expect(onOrientationRequested).not.toHaveBeenCalled(); + viewer.setInteractionActive(false, "orbit"); + draw(); + expect(onOrientationRequested).toHaveBeenCalledOnce(); + const chosen = state.views.at(-1)!.quaternion.clone(); + expect(chosen.angleTo(initial)).toBeGreaterThan(1); + viewer.setScreen({ ...screen }); // Native config confirms the sensor command, even if the app stays portrait. + draw(); + expect(state.views.at(-1)!.quaternion.angleTo(chosen)).toBeLessThan(1e-6); + expect(pending.size).toBe(0); + viewer.resetPose(); + draw(); + expect(onOrientationRequested).toHaveBeenCalledTimes(2); + expect(onOrientationRequested.mock.lastCall?.[0]).toBe("portrait"); + viewer.setScreen({ ...screen }); + draw(); + expect(pending.size).toBe(0); + viewer.dispose(); +}); + +it("settles a hinge transition after a throttled frame instead of stretching time", async () => { + let now = 100; + vi.stubGlobal("performance", { now: () => now }); + const { viewer, draw, pending } = fixture(false); + models.resolve({ asset: asset(), dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(400, 700, 2); + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "portrait", + screenId: 1, + hingeAngle: 0, + }); + draw(); + viewer.setScreen({ + width: 2007, + height: 2853, + orientation: "portrait", + screenId: 3, + hingeAngle: 180, + }); + now += 1_000; + draw(); + draw(); + expect(pending.size).toBe(0); + viewer.dispose(); +}); + +it("faces a display handed off by native rotation without requesting another sensor rotation", async () => { + let now = 0; + vi.stubGlobal("performance", { now: () => now }); + const { viewer, draw, pending, onOrientationRequested } = fixture(); + const loaded = asset(); + models.resolve({ asset: loaded, dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(500, 700, 2); + viewer.setScreen({ + width: 2007, + height: 2853, + orientation: "portrait", + screenId: 3, + hingeAngle: 90, + }); + draw(); + viewer.setInteractionActive(true, "orbit"); + viewer.orbit(0, Math.PI / 6); + viewer.setInteractionActive(false, "orbit"); + now = 1000; + draw(); + const requests = onOrientationRequested.mock.calls.length; + expect(requests).toBe(1); + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "landscape_left", + screenId: 1, + hingeAngle: 90, + }); + draw(); + draw(); + const cover = loaded.getObjectByName("cover-display") as Mesh; + const normal = new Vector3() + .fromBufferAttribute(cover.geometry.getAttribute("normal"), 0) + .transformDirection(cover.matrixWorld); + expect(normal.z).toBeGreaterThan(0.45); + expect(onOrientationRequested).toHaveBeenCalledTimes(requests); + expect(pending.size).toBe(0); + viewer.dispose(); +}); + +it.each(["book", "laptop", "open"] as const)( + "faces native cover readback for %s even when configuration arrives before the model", + async (hingePose) => { + const { viewer, draw } = fixture(); + viewer.resize(500, 700, 2); + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "landscape_left", + screenId: 1, + hingeAngle: hingePose === "open" ? 180 : 90, + hingePose, + }); + const loaded = asset(); + models.resolve({ asset: loaded, dispose: vi.fn() }); + await Promise.resolve(); + draw(); + draw(); + const cover = loaded.getObjectByName("cover-display") as Mesh; + const normal = new Vector3() + .fromBufferAttribute(cover.geometry.getAttribute("normal"), 0) + .transformDirection(cover.matrixWorld); + expect(normal.z).toBeGreaterThan(0.45); + viewer.dispose(); + }, +); + +it("keeps default-zoom orbits framed across folds and fits the current assembly", async () => { + const { viewer, draw, state } = fixture(); + models.resolve({ asset: asset(), dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(380, 620, 2); + viewer.resetPose(); + viewer.setInteractionActive(true, "orbit"); + let minimumDistance = Infinity; + let maximumDistance = 0; + for (const hingeAngle of [0, 30, 90, 127, 180]) { + viewer.setScreen({ + width: 2007, + height: 2853, + orientation: "portrait", + screenId: 3, + hingeAngle, + }); + for (let turn = 0; turn < 16; turn++) { + viewer.orbit(0.1, turn % 2 ? -0.08 : 0.08); + draw(); + const { camera, bounds } = state.views.at(-1)!; + const center = bounds.getCenter(new Vector3()).project(camera); + expect(Math.abs(center.x)).toBeLessThan(0.18); + expect(Math.abs(center.y)).toBeLessThan(0.18); + state.frames.at(-1)!.traverse((object) => { + if (!(object instanceof Mesh)) return; + const positions = object.geometry.getAttribute("position"); + for (let index = 0; index < positions.count; index++) { + const point = new Vector3() + .fromBufferAttribute(positions, index) + .applyMatrix4(object.matrixWorld) + .project(camera); + expect(Math.abs(point.x)).toBeLessThan(1); + expect(Math.abs(point.y)).toBeLessThan(1); + } + }); + minimumDistance = Math.min(minimumDistance, camera.position.z); + maximumDistance = Math.max(maximumDistance, camera.position.z); + } + } + expect(maximumDistance).toBeGreaterThan(minimumDistance); + viewer.dispose(); +}); + +it("previews hinge articulation while preserving the laptop lid orientation and blocking unconfirmed input", async () => { + const { viewer, draw, pending } = fixture(); + const loaded = asset(); + models.resolve({ asset: loaded, dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(500, 700, 2); + viewer.setScreen({ + width: 2853, + height: 2007, + orientation: "landscape_left", + screenId: 3, + hingeAngle: 90, + hingePose: "laptop", + }); + draw(); + const lid = loaded.getObjectByName("right-half")!; + const presentation = lid.getWorldQuaternion(new Rotation()); + for (const value of [127, 50, 170]) { + viewer.setHingePreview(value); + draw(); + expect(loaded.getObjectByName("left-half")!.rotation.y).toBeCloseTo( + ((180 - value) * Math.PI) / 360, + ); + expect(lid.getWorldQuaternion(new Rotation()).angleTo(presentation)).toBeLessThan(0.00001); + expect(viewer.screenPoint(0.5, 0.5)).toBeNull(); + viewer.setScreen({ + width: 2007, + height: 2853, + orientation: "portrait", + screenId: 3, + hingeAngle: value, + hingePose: null, + }); + draw(); + expect(lid.getWorldQuaternion(new Rotation()).angleTo(presentation)).toBeLessThan(0.00001); + } + viewer.setHingePreview(null); + draw(); + expect(pending.size).toBe(0); + expect(lid.getWorldQuaternion(new Rotation()).angleTo(presentation)).toBeLessThan(0.00001); + viewer.dispose(); +}); + +it("does not restart an animated preset on duplicate native configurations", async () => { + let now = 100; + vi.stubGlobal("performance", { now: () => now }); + const { viewer, draw, pending } = fixture(false); + models.resolve({ asset: asset(), dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(400, 700, 2); + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "portrait", + screenId: 1, + hingeAngle: 0, + hingePose: "closed", + }); + draw(); + const next = { + width: 2007, + height: 2853, + orientation: "portrait" as const, + screenId: 3, + hingeAngle: 180, + hingePose: "open" as const, + }; + viewer.setScreen(next); + now += 1_000; + viewer.setScreen(next); + draw(); + now += 1000; + draw(); + draw(); + expect(pending.size).toBe(0); + viewer.dispose(); +}); + +it("lets standalone rotation leave Laptop and Tent and stand a closed device upright after hinge edits", async () => { + const { viewer, draw, state } = fixture(); + models.resolve({ asset: asset(), dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(500, 700, 2); + viewer.resetPose(); + for (const hingePose of ["laptop", "tent"] as const) { + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "landscape_left", + screenId: 1, + hingeAngle: 90, + hingePose, + }); + draw(); + viewer.setHingePreview(0); + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "landscape_left", + screenId: 1, + hingeAngle: 0, + hingePose: null, + }); + viewer.setHingePreview(null); + draw(); + viewer.cancelInput(); // Explicit toolbar rotation ends folding ownership. + viewer.setScreen({ + width: 1398, + height: 2034, + orientation: "portrait", + screenId: 1, + hingeAngle: 0, + hingePose: null, + }); + draw(); + const upright = new Rotation().setFromEuler(new Euler(0, Math.PI / 2, 0, "YXZ")); + expect(state.views.at(-1)!.quaternion.angleTo(upright)).toBeLessThan(0.00001); + const bounds = state.views.at(-1)!.bounds; + expect(bounds.max.y - bounds.min.y).toBeGreaterThan(bounds.max.x - bounds.min.x); + } + viewer.dispose(); +}); + +it("snaps onto the opposite screen, requests native handoff once and retains the chosen view on readback", async () => { + vi.useFakeTimers(); + let now = 0; + vi.stubGlobal("performance", { now: () => now }); + const onPanelRequested = vi.fn(); + const { viewer, draw, state, pending, onOrientationRequested } = fixture( + true, + vi.fn(), + onPanelRequested, + ); + models.resolve({ asset: asset(), dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(500, 700, 2); + const inner = { + width: 2007, + height: 2853, + orientation: "portrait" as const, + screenId: 3, + hingeAngle: 90, + supportsPhysicalOrientation: true, + }; + viewer.setScreen(inner); + draw(); + viewer.setInteractionActive(true, "orbit"); + viewer.orbit(Math.PI / 3, 0); + draw(); + viewer.setInteractionActive(false, "orbit"); + draw(); + expect(onPanelRequested).toHaveBeenCalledExactlyOnceWith(1); + expect(onOrientationRequested).not.toHaveBeenCalled(); + expect(viewer.screenPoint(0.5, 0.5)).toBeNull(); + const chosen = state.views.at(-1)!.quaternion.clone(); + viewer.setScreen({ ...inner, hingePose: null }); // Command acknowledgement precedes sensor readback. + draw(); + expect(state.views.at(-1)!.quaternion.angleTo(chosen)).toBeLessThan(1e-6); + viewer.setScreen({ ...inner, width: 1398, height: 2034, screenId: 1 }); + draw(); + expect(state.views.at(-1)!.quaternion.angleTo(chosen)).toBeLessThan(1e-6); + vi.advanceTimersByTime(5000); + expect(onPanelRequested).toHaveBeenCalledTimes(1); + expect(pending.size).toBe(0); + viewer.setInteractionActive(true, "orbit"); + viewer.orbit(-Math.PI / 3, 0); + draw(); + viewer.setInteractionActive(false, "orbit"); + draw(); + expect(onPanelRequested).toHaveBeenLastCalledWith(3); + const reverse = state.views.at(-1)!.quaternion.clone(); + viewer.setScreen(inner); + draw(); + expect(state.views.at(-1)!.quaternion.angleTo(reverse)).toBeLessThan(1e-6); + vi.advanceTimersByTime(5000); + draw(); + expect(pending.size).toBe(0); + expect(onPanelRequested).toHaveBeenCalledTimes(2); + viewer.dispose(); + vi.useRealTimers(); +}); + +it("rolls an unconfirmed opposite-screen snap back to the native active screen after timeout", async () => { + vi.useFakeTimers(); + const request = vi.fn(); + const { viewer, draw, state, pending } = fixture(true, vi.fn(), request); + models.resolve({ asset: asset(), dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(500, 700, 2); + viewer.setScreen({ + width: 2007, + height: 2853, + orientation: "portrait", + screenId: 3, + hingeAngle: 90, + supportsPhysicalOrientation: true, + }); + draw(); + viewer.setInteractionActive(true, "orbit"); + viewer.orbit(Math.PI / 3, 0); + draw(); + viewer.setInteractionActive(false, "orbit"); + draw(); + const unconfirmed = state.views.at(-1)!.quaternion.clone(); + expect(request).toHaveBeenCalledExactlyOnceWith(1); + vi.advanceTimersByTime(5000); + draw(); + expect(state.views.at(-1)!.quaternion.angleTo(unconfirmed)).toBeGreaterThan(0.5); + expect(request).toHaveBeenCalledOnce(); + draw(); + expect(pending.size).toBe(0); + viewer.dispose(); +}); + +it("uses the elected primary frame during handoff and never lets a stale fixed-panel feed overwrite it", async () => { + const { viewer, drawImage } = fixture(); + const cover = { + width: 1398, + height: 2034, + orientation: "portrait" as const, + screenId: 1, + hingeAngle: 90, + }; + viewer.setScreen(cover); + const primary = { width: 1398, height: 2034 } as HTMLCanvasElement; + viewer.frameUpdated(1, primary); + expect(drawImage.mock.calls.at(-1)?.[0]).toBe(primary); + const uploads = drawImage.mock.calls.length; + viewer.frameUpdated(1); + expect(drawImage).toHaveBeenCalledTimes(uploads); + viewer.frameUpdated(3, primary); + expect(drawImage).toHaveBeenCalledTimes(uploads); + viewer.setScreen({ ...cover, width: 2007, height: 2853, screenId: 3 }); + const inner = { width: 2007, height: 2853 } as HTMLCanvasElement; + viewer.frameUpdated(3, inner); + expect(drawImage.mock.calls.at(-1)?.[0]).toBe(inner); + viewer.setScreen(cover); + viewer.frameUpdated(1); + expect(drawImage.mock.calls.length).toBeGreaterThan(uploads + 1); + viewer.dispose(); +}); + +it.each(["left", "right"] as const)( + "keeps the primary surface oriented when pinching over the %s half through closure and reopening", + async (face) => { + const { viewer, draw, state, pending, onOrientationRequested } = fixture(); + const loaded = asset(); + models.resolve({ asset: loaded, dispose: vi.fn() }); + await Promise.resolve(); + viewer.resize(500, 700, 2); + const inner = { + width: 2007, + height: 2853, + orientation: "portrait" as const, + screenId: 3, + hingeAngle: 180, + hingePose: "open" as const, + }; + viewer.setScreen(inner); + draw(); + expect(viewer.beginHinge(-1, -1)).toBe(false); + const leaf = loaded.getObjectByName("right-half")!; + const mesh = loaded.getObjectByName(`inner-display-${face}`) as Mesh; + const center = mesh.geometry.boundingBox!.getCenter(new Vector3()); + const point = mesh.localToWorld(center).project(state.views.at(-1)!.camera); + expect(viewer.beginHinge((point.x + 1) / 2, (1 - point.y) / 2)).toBe(true); + const orientation = leaf.getWorldQuaternion(new Rotation()); + const partner = loaded.getObjectByName("left-half")!; + const partnerStart = partner.getWorldQuaternion(new Rotation()); + for (const value of [150, 90, 30, 0, 30, 90, 180]) { + viewer.setHingePreview(value); + draw(); + expect(leaf.getWorldQuaternion(new Rotation()).angleTo(orientation)).toBeLessThan(1e-6); + if (value === 90) + expect(partner.getWorldQuaternion(new Rotation()).angleTo(partnerStart)).toBeGreaterThan(1); + const next = + value === 0 + ? { ...inner, width: 1398, height: 2034, screenId: 1, hingeAngle: value, hingePose: null } + : { ...inner, hingeAngle: value, hingePose: null }; + viewer.setScreen(next); + viewer.setHingePreview(null); + draw(); + viewer.setScreen({ ...next }); // Late native readback cannot change the view. + draw(); + expect(leaf.getWorldQuaternion(new Rotation()).angleTo(orientation)).toBeLessThan(1e-6); + expect(viewer.screenPoint(0.5, 0.5)).toBeNull(); // No fresh native frame yet. + if (value === 0) { + const cover = loaded.getObjectByName("cover-display") as Mesh; + const center = cover.geometry.boundingBox!.getCenter(new Vector3()); + const hit = cover.localToWorld(center).project(state.views.at(-1)!.camera); + expect(viewer.beginHinge((hit.x + 1) / 2, (1 - hit.y) / 2)).toBe(true); + } + } + expect(onOrientationRequested).not.toHaveBeenCalled(); + draw(); + expect(pending.size).toBe(0); + viewer.dispose(); + }, +); diff --git a/packages/client-runtime/src/device/duoViewer.ts b/packages/client-runtime/src/device/duoViewer.ts new file mode 100644 index 000000000000..7feca7452e0d --- /dev/null +++ b/packages/client-runtime/src/device/duoViewer.ts @@ -0,0 +1,566 @@ +// @effect-diagnostics globalTimers:off - Native display handoff has a bounded acknowledgement window. +import { + AmbientLight, + Box3, + CanvasTexture, + DirectionalLight, + Euler, + LinearFilter, + PerspectiveCamera, + PMREMGenerator, + Quaternion, + Scene, + SRGBColorSpace, + Vector3, + WebGLRenderer, +} from "three"; +import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js"; +import { + createDuoScene, + duoDisplayKey, + duoFrameMatches, + type DuoPanelId, + type DuoHingeLeaf, +} from "./duoScene.ts"; +import { loadDeviceModel } from "./modelScene.ts"; +import { createDeviceModelSlot, type DeviceModelSource } from "./model.ts"; +import { createDeviceFraming } from "./deviceFraming.ts"; +import { createDeviceMotion } from "./deviceMotion.ts"; +import { duoViewSnaps, nearestDuoView, type DuoRestFace } from "./duoSnap.ts"; +import { createRenderScheduler } from "./renderScheduler.ts"; +import type { DeviceScreenSize } from "./stream.ts"; +import type { DuoPose } from "./duoControl.ts"; + +export interface DuoViewer { + readonly setScreen: (screen: DeviceScreenSize | null) => void; + readonly setHingePreview: (angle: number | null) => void; + readonly setInteractionActive: (active: boolean, mode?: "touch" | "orbit") => void; + readonly rejectOrientation: () => void; + readonly frameUpdated: (panel: DuoPanelId, primary?: HTMLCanvasElement) => void; + readonly resize: (width: number, height: number, pixelRatio: number) => void; + readonly screenPoint: ( + x: number, + y: number, + captured?: boolean, + ) => { x: number; y: number } | null; + readonly orbit: (x: number, y: number) => void; + readonly beginHinge: (x: number, y: number) => boolean; + readonly resetPose: () => void; + readonly cancelInput: () => void; + readonly dispose: () => void; +} + +/** On-demand renderer for the articulated body. One inner framebuffer spans both leaves; HID belongs to the stream. */ +export function createDuoViewer(options: { + canvas: HTMLCanvasElement; + sources: Record; + model: DeviceModelSource; + onUnavailable: () => void; + onModelError?: (cause: unknown) => void; + onPanelRequested?: (panel: DuoPanelId) => void; + onOrientationRequested?: (orientation: DeviceScreenSize["orientation"]) => void; +}): DuoViewer { + const surfaces = ([1, 3] as const) + .map((id) => { + const canvas = document.createElement("canvas"); + canvas.width = id === 1 ? 784 : 1600; + canvas.height = id === 1 ? 1140 : 1125; + const context = canvas.getContext("2d", { alpha: false }); + if (!context) throw new Error("Duo display canvas is unavailable"); + context.fillStyle = "#080a10"; + context.fillRect(0, 0, canvas.width, canvas.height); + return { id, canvas, context }; + }) + .map(({ id, canvas, context }) => { + const texture = new CanvasTexture(canvas); + texture.colorSpace = SRGBColorSpace; + texture.minFilter = texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + return { id, canvas, context, texture }; + }); + const renderer = new WebGLRenderer({ + canvas: options.canvas, + alpha: true, + antialias: true, + powerPreference: "low-power", + }); + renderer.outputColorSpace = SRGBColorSpace; + const scene = new Scene(); + const environment = (() => { + const generator = new PMREMGenerator(renderer); + const room = new RoomEnvironment(); + try { + return generator.fromScene(room, 0.04); + } catch (cause) { + for (const surface of surfaces) surface.texture.dispose(); + renderer.dispose(); + renderer.forceContextLoss(); + throw cause; + } finally { + room.dispose(); + generator.dispose(); + } + })(); + scene.environment = environment.texture; + const camera = new PerspectiveCamera(36, 1, 0.1, 150); + const key = new DirectionalLight(0xffffff, 4); + key.position.set(-6, 10, 14); + const fill = new DirectionalLight(0xc7dcff, 2); + fill.position.set(8, -3, -5); + scene.add(new AmbientLight(0xffffff, 2.4), key, fill); + let model: ReturnType | null = null; + let screen: DeviceScreenSize | null = null; + let readyKey = ""; + let primaryKey = ""; + let activationAt = 0; + let disposed = false; + let viewport = { width: 0, height: 0, ratio: 1 }; + let buffer = { width: 0, height: 0, ratio: 0 }; + let restFace: DuoRestFace = "inside"; + let requestedOrientation: DeviceScreenSize["orientation"] | null = null; + let viewOrientation: DeviceScreenSize["orientation"] | null = null; + const pivot = new Vector3(); + const targetPivot = new Vector3(); + let requestedPanel: DuoPanelId | null = null; + let handoffTimer: ReturnType | null = null; + const clearHandoff = () => { + if (handoffTimer) clearTimeout(handoffTimer); + handoffTimer = null; + requestedPanel = null; + }; + const activeSnaps = () => + model + ? duoViewSnaps( + model.restFrames(screen?.screenId === 1 ? 1 : 3), + screen?.screenId === 1 ? 1 : 3, + ) + : []; + const snaps = () => { + if (!model || !screen?.supportsPhysicalOrientation || !options.onPanelRequested) + return activeSnaps(); + const cover = duoViewSnaps(model.restFrames(1), 1); + // A shut inner display is occluded and cannot become a useful rest view. + return angle > 20 && angle < 180 + ? [...duoViewSnaps(model.restFrames(3), 3), ...cover] + : activeSnaps(); + }; + const orbit = createDeviceMotion({ + choose(rotation) { + const snap = nearestDuoView(rotation, snaps()); + if (!snap) return rotation; + restFace = snap.face; + const panel = snap.face === "cover" ? 1 : 3; + if (screen && panel !== (requestedPanel ?? screen.screenId) && options.onPanelRequested) { + clearHandoff(); + requestedPanel = panel; + model?.cancelInput(); + options.onPanelRequested(panel); + handoffTimer = setTimeout(() => { + clearHandoff(); + const confirmed = nearestDuoView(orbit.rotation, activeSnaps()); + if (confirmed) { + restFace = confirmed.face; + orbit.setPose(confirmed.rotation, performance.now()); + } + scheduler.invalidate(); + }, 5000); + } else if ( + screen && + requestedPanel === null && + snap.orientation !== viewOrientation && + options.onOrientationRequested + ) { + viewOrientation = snap.orientation; + requestedOrientation = snap.orientation; + options.onOrientationRequested(snap.orientation); + } + return snap.rotation; + }, + }); + let angle = 180; + let targetAngle = 180; + let previewAngle: number | null = null; + let hingeLeaf: DuoHingeLeaf | null = null; + let appliedAngle = Number.NaN; + let interactionActive = false; + const framing = createDeviceFraming(); + const framingBounds = new Box3(); + let firstPose = true; + let physicalPose: DuoPose = "open"; + let presentationAngle = 180; + let targetPresentation = new Quaternion(); + let lastTime = 0; + const reduced = + typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches; + const applyPose = () => { + if (!model) return; + const before = hingeLeaf && appliedAngle !== angle ? model.leafRotation(hingeLeaf) : null; + model.setAngle(angle); + appliedAngle = angle; + if (before && hingeLeaf) { + // The primary surface stays in camera space; its partner supplies the fold. + // Rebase both the displayed rotation and its rest target so releasing a + // pinch cannot resume the pre-fold body rotation. + const correction = before.multiply(model.leafRotation(hingeLeaf).invert()); + if (correction.angleTo(new Quaternion()) > 1e-8) + orbit.setPose(orbit.rotation.clone().multiply(correction), performance.now(), true); + } + model.root.quaternion.copy(orbit.rotation); + model.root.position.set(0, 0, 0); + const frames = model.restFrames(restFace === "cover" ? 1 : 3); + const frame = frames.find((frame) => frame.face === restFace) ?? frames[0]; + targetPivot.copy(frame?.center ?? new Vector3()); + model.root.position.copy(pivot).applyQuaternion(model.root.quaternion).negate(); + }; + const updateCamera = () => { + camera.position.set(framing.center.x, framing.center.y, framing.distance()); + camera.lookAt(framing.center.x, framing.center.y, 0); + camera.updateProjectionMatrix(); + }; + const fit = (immediate = false) => { + if (!model || !viewport.width || !viewport.height) return; + camera.aspect = viewport.width / viewport.height; + framing.setBounds( + framingBounds.setFromObject(model.root), + (camera.fov * Math.PI) / 360, + camera.aspect, + performance.now(), + immediate, + ); + updateCamera(); + }; + const moving = () => + Math.abs(angle - targetAngle) > 0.01 || pivot.distanceTo(targetPivot) > 0.001; + const scheduler = createRenderScheduler(() => { + if (disposed || !model || !viewport.width || !viewport.height) return; + try { + const now = performance.now(); + const elapsed = Math.max(0, (now - lastTime) / 1000); + const amount = reduced + ? 1 + : 1 - Math.exp(-14 * Math.max(0, (now - lastTime) / 1000 || 0.016)); + lastTime = now; + const inMotion = moving(); + const orbitChanged = orbit.advance(now, reduced); + if (inMotion) { + angle += (targetAngle - angle) * amount; + + if (Math.abs(angle - targetAngle) <= 0.01) angle = targetAngle; + applyPose(); + pivot.lerp(targetPivot, amount); + if (pivot.distanceTo(targetPivot) <= 0.001) pivot.copy(targetPivot); + applyPose(); + fit(reduced || elapsed > 0.5); + } + if (orbitChanged) { + applyPose(); + fit(reduced || elapsed > 0.5); + } + if (framing.advance(now, reduced)) updateCamera(); + if ( + buffer.width !== viewport.width || + buffer.height !== viewport.height || + buffer.ratio !== viewport.ratio + ) { + renderer.setDrawingBufferSize(viewport.width, viewport.height, viewport.ratio); + buffer = viewport; + } + renderer.render(scene, camera); + if (inMotion || moving() || orbit.needsFrame() || framing.needsFrame()) + scheduler.invalidate(); + } catch { + options.onUnavailable(); + } + }); + const slot = createDeviceModelSlot({ + load: loadDeviceModel, + onError(cause) { + options.onModelError?.(cause); + options.onUnavailable(); + }, + install(loaded) { + const next = loaded + ? createDuoScene(loaded.asset, { 1: surfaces[0]!.texture, 3: surfaces[1]!.texture }) + : null; + if (model) { + scene.remove(model.root); + model.dispose(); + } + model = next; + appliedAngle = Number.NaN; + if (!model || disposed) return; + scene.add(model.root); + applyPose(); + if (screen?.screenId === 1) { + const snap = nearestDuoView(orbit.rotation, activeSnaps()); + if (snap) { + restFace = snap.face; + orbit.setPose(snap.rotation, performance.now(), true); + applyPose(); + } + } + pivot.copy(targetPivot); + applyPose(); + fit(); + scheduler.invalidate(); + }, + }); + const lost = (event: Event) => { + event.preventDefault(); + options.onUnavailable(); + }; + options.canvas.addEventListener("webglcontextlost", lost); + slot.set(options.model); + return { + setScreen(next) { + if (disposed) return; + const wasMoving = moving() || orbit.needsFrame(); + if (duoDisplayKey(screen) !== duoDisplayKey(next)) { + readyKey = ""; + activationAt = performance.now(); + model?.cancelInput(); + } + const previous = screen; + const ownedHandoff = requestedPanel !== null; + if (!next || next.screenId === requestedPanel) clearHandoff(); + const ownedRotation = requestedOrientation !== null && next?.screenId === previous?.screenId; + const changedDisplay = next?.screenId !== previous?.screenId; + requestedOrientation = null; + screen = next; + const changedPose = next?.hingePose && next.hingePose !== previous?.hingePose; + const folding = hingeLeaf !== null && !changedPose; + const rotated = + next?.screenId === previous?.screenId && + next?.hingeAngle === previous?.hingeAngle && + next?.orientation !== previous?.orientation; + const leftPhysicalPose = + rotated && + !ownedRotation && + !ownedHandoff && + !folding && + !next?.hingePose && + previewAngle === null; + if (firstPose || changedPose || leftPhysicalPose) { + physicalPose = next?.hingePose ?? (next?.screenId === 1 ? "closed" : "open"); + presentationAngle = next?.hingeAngle ?? (next?.screenId === 1 ? 0 : 180); + } + // A command reply/config confirms hinge state. Display identity, never an angle heuristic, owns input. + targetAngle = previewAngle ?? next?.hingeAngle ?? (next?.screenId === 1 ? 0 : 180); + // Native angle commands clear hingePose. They change articulation only; + // preserve the viewing pose, including laptop/tent and user orbit. A + // separate rotation clears the physical preset and follows panel orientation. + if (firstPose || changedPose || (rotated && !ownedRotation && !ownedHandoff && !folding)) { + hingeLeaf = null; + viewOrientation = next?.orientation ?? null; + const poseAngle = presentationAngle; + const fold = ((180 - poseAngle) * Math.PI) / 360; + let roll = physicalPose === "closed" ? 0 : Math.PI / 2; + if (next && next.width < next.height) { + if (next.orientation === "landscape_left") roll -= Math.PI / 2; + if (next.orientation === "landscape_right") roll += Math.PI / 2; + } + if (next?.orientation === "portrait_upside_down") roll -= Math.PI; + const physical = + physicalPose === "laptop" + ? new Euler(-fold + Math.PI / 9, -Math.PI / 9, Math.PI / 2, "YXZ") + : physicalPose === "tent" + ? new Euler(Math.PI / 2 + Math.PI / 18, -Math.PI / 9, -Math.PI / 2, "YXZ") + : new Euler(0, (Math.PI / 2) * Math.pow(1 - poseAngle / 180, 3), 0, "YXZ"); + targetPresentation = new Quaternion().setFromEuler(physical); + if (physicalPose !== "laptop" && physicalPose !== "tent") + targetPresentation.premultiply( + new Quaternion().setFromAxisAngle(new Vector3(0, 0, 1), roll), + ); + if (next?.screenId === 1) { + // Native readback can retain the cover even for an open physical preset. + // A screen-facing rest must use that display's actual hinged normal. + const snap = nearestDuoView(targetPresentation, activeSnaps()); + if (snap) targetPresentation.copy(snap.rotation); + } + if (firstPose) angle = targetAngle; + restFace = next?.screenId === 1 ? "cover" : physicalPose === "laptop" ? "right" : "inside"; + orbit.setPose(targetPresentation, performance.now(), firstPose); + firstPose = false; + } else if (next && changedDisplay && !ownedHandoff && !folding) { + // A sensor rotation can hand ownership to the other native display. + // Face that display without sending another rotation and creating a feedback loop. + const snap = nearestDuoView(orbit.rotation, activeSnaps()); + if (snap) { + restFace = snap.face; + orbit.setPose(snap.rotation, performance.now()); + } + } + if (!wasMoving) lastTime = performance.now(); + applyPose(); + fit(); + scheduler.invalidate(); + }, + setHingePreview(next) { + if (disposed || (next !== null && (!Number.isFinite(next) || next < 0 || next > 180))) return; + if (!moving()) lastTime = performance.now(); + previewAngle = next; + if (next !== null && !hingeLeaf && model) { + clearHandoff(); + requestedOrientation = null; + hingeLeaf = screen?.screenId === 1 && angle > 20 ? "left" : "right"; + orbit.setPose(orbit.rotation.clone(), performance.now(), true); + } + orbit.hold(interactionActive || next !== null, performance.now()); + targetAngle = next ?? screen?.hingeAngle ?? (screen?.screenId === 1 ? 0 : 180); + if (next !== null) { + angle = next; + applyPose(); + fit(); + } + model?.cancelInput(); + scheduler.invalidate(); + }, + setInteractionActive(active, mode = "touch") { + if (disposed) return; + if (mode === "orbit") { + if (active) hingeLeaf = null; + orbit.dragActive(active, performance.now()); + } else { + interactionActive = active; + orbit.hold(active || previewAngle !== null, performance.now()); + framing.hold(active, performance.now()); + } + scheduler.invalidate(); + }, + rejectOrientation() { + if (disposed || (requestedOrientation === null && requestedPanel === null)) return; + clearHandoff(); + requestedOrientation = null; + viewOrientation = screen?.orientation ?? null; + const confirmed = activeSnaps().filter((snap) => snap.orientation === screen?.orientation); + const snap = nearestDuoView(orbit.rotation, confirmed); + if (snap) { + restFace = snap.face; + orbit.setPose(snap.rotation, performance.now()); + } + scheduler.invalidate(); + }, + frameUpdated(id, primary) { + if (disposed || screen?.screenId !== id) return; + const key = duoDisplayKey(screen); + // The elected native feed is authoritative during handoff. Fixed-panel + // encoders can retain an inactive blank until that surface changes again. + if (!primary && primaryKey === key) return; + const source = primary ?? options.sources[id]; + if (!duoFrameMatches(source, screen)) return; + const surface = surfaces[id === 1 ? 0 : 1]!; + const { context, canvas } = surface; + // Ignore native shutdown blanks only while waiting for an activation. Steady black application content remains valid. + if (!primary && !readyKey && performance.now() - activationAt < 1500) { + const probe = document.createElement("canvas"); + probe.width = probe.height = 8; + const probeContext = probe.getContext("2d", { willReadFrequently: true }); + if (probeContext) { + probeContext.drawImage(source, 0, 0, 8, 8); + if ( + !probeContext + .getImageData(0, 0, 8, 8) + .data.some((value, index) => index % 4 !== 3 && value > 3) + ) + return; + } + } + context.save(); + context.fillStyle = "#080a10"; + context.fillRect(0, 0, canvas.width, canvas.height); + context.translate(canvas.width / 2, canvas.height / 2); + // The inner panel is mounted a quarter turn from the native framebuffer. + const rotate = id === 3; + if (rotate) context.rotate(Math.PI / 2); + const scale = Math.min( + canvas.width / (rotate ? source.height : source.width), + canvas.height / (rotate ? source.width : source.height), + ); + context.drawImage( + source, + (-source.width * scale) / 2, + (-source.height * scale) / 2, + source.width * scale, + source.height * scale, + ); + context.restore(); + surface.texture.needsUpdate = true; + readyKey = duoDisplayKey(screen); + if (primary) primaryKey = readyKey; + scheduler.invalidate(); + }, + resize(width, height, ratio) { + if (disposed || ![width, height, ratio].every(Number.isFinite) || width <= 0 || height <= 0) + return; + viewport = { width, height, ratio: Math.min(2, Math.max(1, ratio)) }; + fit(true); + scheduler.invalidate(); + }, + screenPoint(x, y, captured = false) { + if ( + disposed || + moving() || + previewAngle !== null || + requestedOrientation !== null || + requestedPanel !== null + ) + return null; + applyPose(); + return model?.screenPoint(x, y, camera, screen, readyKey, captured) ?? null; + }, + cancelInput() { + hingeLeaf = null; + model?.cancelInput(); + }, + orbit(x, y) { + if (disposed) return; + hingeLeaf = null; + orbit.orbit(x * viewport.width, y * viewport.height, performance.now()); + scheduler.invalidate(); + }, + beginHinge(x, y) { + if (disposed || !model || !screen) return false; + applyPose(); + const leaf = model.hingeLeafAt(x, y, camera); + if (!leaf) return false; + clearHandoff(); + requestedOrientation = null; + // The right inner leaf and the shut cover share the front-facing plane. + // Holding that leaf lets the cover replace the inner image without a + // half turn, even when the pinch starts over the moving partner. + hingeLeaf = screen.screenId === 1 && angle > 20 ? "left" : "right"; + orbit.setPose(orbit.rotation.clone(), performance.now(), true); + model.cancelInput(); + return true; + }, + resetPose() { + if (disposed) return; + hingeLeaf = null; + requestedOrientation = null; + clearHandoff(); + const snap = activeSnaps().find((snap) => snap.orientation === screen?.orientation); + if (snap && snap.orientation !== viewOrientation && options.onOrientationRequested) { + viewOrientation = snap.orientation; + requestedOrientation = snap.orientation; + options.onOrientationRequested(snap.orientation); + } + orbit.reset(snap?.rotation ?? targetPresentation, performance.now()); + applyPose(); + fit(); + scheduler.invalidate(); + }, + dispose() { + clearHandoff(); + if (disposed) return; + disposed = true; + scheduler.dispose(); + slot.dispose(); + options.canvas.removeEventListener("webglcontextlost", lost); + for (const surface of surfaces) surface.texture.dispose(); + scene.environment = null; + environment.dispose(); + renderer.dispose(); + renderer.forceContextLoss(); + }, + }; +} diff --git a/packages/client-runtime/src/device/model.test.ts b/packages/client-runtime/src/device/model.test.ts index 94db7942176b..7bd1aa231376 100644 --- a/packages/client-runtime/src/device/model.test.ts +++ b/packages/client-runtime/src/device/model.test.ts @@ -24,6 +24,7 @@ function fixture() { it("matches only exact supported hardware, including iPad size and generation", () => { expect(resolveDeviceModelId("ios", "iPhone 18 Pro")).toBe(pro.id); expect(resolveDeviceModelId("ios", "iPhone 18 Pro Max")).toBe(max.id); + expect(resolveDeviceModelId("ios", "iPhone Duo")).toBe("iphone-duo"); expect(resolveDeviceModelId("ios", "iPad Pro 13-inch (M5)")).toBe("ipad-pro-13-m5"); for (const name of [ "iPhone 17 Pro", diff --git a/packages/client-runtime/src/device/model.ts b/packages/client-runtime/src/device/model.ts index 1f6524d4277c..c1a66f33365e 100644 --- a/packages/client-runtime/src/device/model.ts +++ b/packages/client-runtime/src/device/model.ts @@ -1,6 +1,6 @@ import type { DevicePlatform } from "@t3tools/contracts"; -export type DeviceModelId = "iphone-18-pro" | "iphone-18-pro-max" | "ipad-pro-13-m5"; +export type DeviceModelId = "iphone-18-pro" | "iphone-18-pro-max" | "ipad-pro-13-m5" | "iphone-duo"; export interface DeviceModelSource { readonly id: DeviceModelId; @@ -18,6 +18,7 @@ export type DeviceAssetSource = DeviceModelSource | DeviceAccessorySource; /** Match actual hardware, never stretch an available model to impersonate another device. */ export function resolveDeviceModelId(platform: DevicePlatform, name: string): DeviceModelId | null { if (platform !== "ios") return null; + if (/^iPhone Duo$/i.test(name)) return "iphone-duo"; if (/^iPhone 18 Pro Max$/i.test(name)) return "iphone-18-pro-max"; if (/^iPhone 18 Pro$/i.test(name)) return "iphone-18-pro"; if (/^iPad Pro 13-inch \(M5\)$/i.test(name)) return "ipad-pro-13-m5"; diff --git a/packages/client-runtime/src/device/phoneViewer.test.ts b/packages/client-runtime/src/device/phoneViewer.test.ts index ddb08bddf976..d40ddc72b39d 100644 --- a/packages/client-runtime/src/device/phoneViewer.test.ts +++ b/packages/client-runtime/src/device/phoneViewer.test.ts @@ -11,6 +11,7 @@ const gpu = vi.hoisted(() => ({ scene: Scene; phone: Object3D | undefined; rotation: Quaternion | undefined; + displayAngle: number | undefined; yaw: number | undefined; cameraZ: number; }[]; @@ -50,6 +51,7 @@ vi.mock("three", async () => { scene, phone, rotation: phone?.quaternion.clone(), + displayAngle: phone?.children[0]?.rotation.z, yaw: phone?.rotation.y, cameraZ: camera.position.z, }); @@ -78,10 +80,24 @@ vi.mock("./modelScene.ts", async () => { }; }); -import { BoxGeometry, Group, Mesh, MeshBasicMaterial, PlaneGeometry, Quaternion } from "three"; +import { + Box3, + BoxGeometry, + Group, + Mesh, + MeshBasicMaterial, + PlaneGeometry, + Quaternion, + Vector3, +} from "three"; import { disposeDeviceModel } from "./modelScene.ts"; import { createPhoneViewer } from "./phoneViewer.ts"; -import { IOS_TABLET_SHAPE } from "./shapeProfile.ts"; +import { + ANDROID_PHONE_SHAPE, + IOS_TABLET_SHAPE, + resolveDeviceShape, + type DeviceShapeProfile, +} from "./shapeProfile.ts"; afterEach(() => { vi.restoreAllMocks(); @@ -90,7 +106,7 @@ afterEach(() => { models.pending.length = 0; }); -function fixture() { +function fixture(profile?: DeviceShapeProfile) { const pending = new Map(); let id = 0; let now = 0; @@ -104,7 +120,13 @@ function fixture() { const source = { width: 1206, height: 2622 } as HTMLCanvasElement; const onUnavailable = vi.fn(); const onFramingAspect = vi.fn(); - const viewer = createPhoneViewer({ canvas, source, onUnavailable, onFramingAspect }); + const viewer = createPhoneViewer({ + canvas, + source, + onUnavailable, + onFramingAspect, + ...(profile ? { profile } : {}), + }); const draw = (time = now) => { now = time; const callbacks = [...pending.values()]; @@ -194,6 +216,164 @@ it("changes device shape without replacing the renderer, decoded source or pose" viewer.dispose(); }); +it("keeps the Android viewer while the resized framebuffer turns between fold postures", () => { + const openProfile = resolveDeviceShape({ platform: "android", portraitAspect: 0.96 }); + const { viewer, draw, source, state } = fixture(openProfile); + const scene = state.frames.at(-1)!.scene; + source.width = 2076; + source.height = 2152; + viewer.setScreen({ width: 2076, height: 2152, orientation: "landscape_left" }); + viewer.frameUpdated(); + draw(0); + expect(state.frames.at(-1)!.displayAngle).toBeCloseTo(0); + draw(225); + expect(state.frames.at(-1)!.displayAngle).toBeCloseTo(-Math.PI / 4); + draw(450); + expect(state.frames.at(-1)!.displayAngle).toBeCloseTo(-Math.PI / 2); + expect(state.frames.at(-1)!.scene).toBe(scene); + expect(gpu.instances).toHaveLength(1); + + source.width = 1080; + source.height = 2424; + viewer.setScreen({ width: 1080, height: 2424, orientation: "portrait" }, ANDROID_PHONE_SHAPE); + viewer.frameUpdated(); + draw(450); + expect(state.frames.at(-1)!.displayAngle).toBeCloseTo(-Math.PI / 2); + draw(675); + expect(state.frames.at(-1)!.displayAngle).toBeCloseTo(-Math.PI / 4); + draw(900); + expect(state.frames.at(-1)!.displayAngle).toBeCloseTo(0); + expect(state.frames.at(-1)!.scene).toBe(scene); + viewer.dispose(); +}); + +it("animates the Android hinge on the same scene through an encoder resize", () => { + const openProfile = resolveDeviceShape({ platform: "android", portraitAspect: 0.96 }); + const { viewer, draw, source, state } = fixture(openProfile); + viewer.setFoldAngle(180); + draw(0); + const shell = state.frames.at(-1)!.phone!; + const moving = shell.children[0]!.children[0]!; + viewer.setFoldAngle(0); + draw(425); + expect(moving.rotation.y).toBeCloseTo(Math.PI / 2); + source.width = 1080; + source.height = 2424; + viewer.setScreen({ width: 1080, height: 2424, orientation: "portrait" }, ANDROID_PHONE_SHAPE); + viewer.frameUpdated(); + draw(850); + expect(moving.rotation.y).toBeCloseTo(Math.PI); + expect(state.frames.at(-1)!.phone).toBe(shell); + expect(gpu.instances).toHaveLength(1); + viewer.dispose(); +}); + +it("resizes the fold body for a landscape inner display and keeps it through the cover frame", () => { + const openProfile = resolveDeviceShape({ platform: "android", portraitAspect: 0.83 }); + const { viewer, draw, source, state } = fixture(openProfile); + viewer.setFoldAngle(180); + draw(0); + const portraitWidth = new Box3() + .setFromObject(state.frames.at(-1)!.phone!) + .getSize(new Vector3()).x; + source.width = 2208; + source.height = 1840; + viewer.setScreen({ width: 2208, height: 1840, orientation: "portrait" }); + viewer.frameUpdated(); + draw(10); + const landscape = state.frames.at(-1)!.phone!; + const landscapeWidth = new Box3().setFromObject(landscape).getSize(new Vector3()).x; + expect(landscapeWidth / portraitWidth).toBeGreaterThan(1.15); + source.width = 1080; + source.height = 2092; + viewer.setScreen({ width: 1080, height: 2092, orientation: "portrait" }, ANDROID_PHONE_SHAPE); + viewer.frameUpdated(); + draw(20); + expect(state.frames.at(-1)!.phone).toBe(landscape); + expect(gpu.instances).toHaveLength(1); + viewer.dispose(); +}); + +it("keeps the fold body through a rotated cover frame and learns the inner shape before fold mode", () => { + const { viewer, draw, source, state } = fixture(ANDROID_PHONE_SHAPE); + source.width = 2208; + source.height = 1840; + viewer.setScreen({ width: 2208, height: 1840, orientation: "portrait" }); + viewer.frameUpdated(); + draw(0); + viewer.setFoldAngle(180); + draw(10); + const landscape = state.frames.at(-1)!.phone!; + const width = new Box3().setFromObject(landscape).getSize(new Vector3()).x; + expect(width / new Box3().setFromObject(landscape).getSize(new Vector3()).y).toBeGreaterThan(1.1); + source.width = 2092; + source.height = 1080; + viewer.setScreen({ width: 2092, height: 1080, orientation: "landscape_left" }); + viewer.frameUpdated(); + draw(20); + expect(state.frames.at(-1)!.phone).toBe(landscape); + viewer.dispose(); +}); + +it("retargets an unfinished hinge turn from its visible angle", () => { + const { viewer, draw, state } = fixture(ANDROID_PHONE_SHAPE); + viewer.setFoldAngle(180); + draw(0); + const moving = state.frames.at(-1)!.phone!.children[0]!.children[0]!; + viewer.setFoldAngle(0); + draw(200); + const visibleAngle = moving.rotation.y; + viewer.setFoldAngle(180); + draw(200); + expect(moving.rotation.y).toBeCloseTo(visibleAngle); + draw(1050); + expect(moving.rotation.y).toBeCloseTo(0); + viewer.dispose(); +}); + +it("stops a hinge turn when a loaded model replaces the fold scene", async () => { + const { viewer, draw, pending } = fixture(ANDROID_PHONE_SHAPE); + viewer.setFoldAngle(180); + draw(0); + viewer.setFoldAngle(0); + viewer.setModel({ id: "iphone-18-pro", url: "/fold.glb" }); + const asset = new Group(); + const body = new Mesh(new BoxGeometry(1, 2, 0.1), new MeshBasicMaterial()); + const display = new Mesh(new PlaneGeometry(0.9, 1.9), new MeshBasicMaterial()); + display.name = "device-screen"; + asset.add(body, display); + models.pending[0]!.resolve({ asset, dispose: () => disposeDeviceModel(asset) }); + await Promise.resolve(); + draw(200); + draw(1050); + expect(pending.size).toBe(0); + viewer.dispose(); +}); + +it("keeps a loaded model when the fold angle changes and releases it once", async () => { + const { viewer, draw, state } = fixture(ANDROID_PHONE_SHAPE); + viewer.setModel({ id: "iphone-18-pro", url: "/pro.glb" }); + const asset = new Group(); + const body = new Mesh(new BoxGeometry(1.15, 2.3, 0.1), new MeshBasicMaterial()); + body.position.z = -0.02; + const display = new Mesh(new PlaneGeometry(1, 2.2), new MeshBasicMaterial()); + display.geometry.translate(0, 0, 0.043); + display.name = "device-screen"; + asset.add(body, display); + const dispose = vi.fn(() => disposeDeviceModel(asset)); + models.pending[0]!.resolve({ asset, dispose }); + await Promise.resolve(); + draw(); + const loaded = state.frames.at(-1)!.phone; + expect(loaded?.getObjectByName("device-screen")).toBe(display); + viewer.setFoldAngle(180); + viewer.setFoldAngle(0); + draw(); + expect(state.frames.at(-1)!.phone).toBe(loaded); + viewer.dispose(); + expect(dispose).toHaveBeenCalledOnce(); +}); + it("retains the loaded model and pose through rotation and framebuffer resolution changes, then releases it once", async () => { const { viewer, draw, source, state } = fixture(); viewer.orbit(0.08, 0.04); diff --git a/packages/client-runtime/src/device/phoneViewer.ts b/packages/client-runtime/src/device/phoneViewer.ts index 0794490606d1..3dfc25048e02 100644 --- a/packages/client-runtime/src/device/phoneViewer.ts +++ b/packages/client-runtime/src/device/phoneViewer.ts @@ -19,6 +19,11 @@ import { } from "./model.ts"; import { createImportedPhoneScene, loadDeviceModel } from "./modelScene.ts"; import { createPhoneScene, phoneDisplayLayout } from "./phoneScene.ts"; +import { + createAndroidFoldScene, + DEFAULT_FOLD_INNER_ASPECT, + isFoldInnerAspect, +} from "./androidFoldScene.ts"; import { createRenderScheduler } from "./renderScheduler.ts"; import { createDeviceMotion } from "./deviceMotion.ts"; import { createDeviceFraming } from "./deviceFraming.ts"; @@ -31,6 +36,7 @@ export interface PhoneViewer { readonly setAccessory: (source: DeviceAccessorySource | null) => void; readonly frameUpdated: () => void; readonly setScreen: (screen: DeviceScreenSize | null, profile?: DeviceShapeProfile) => void; + readonly setFoldAngle: (angle: number | null) => void; readonly resize: (width: number, height: number, pixelRatio: number) => void; readonly screenPoint: ( x: number, @@ -43,6 +49,9 @@ export interface PhoneViewer { readonly dispose: () => void; } +const ANDROID_ORIENTATION_TURN_MS = 450; +const ANDROID_FOLD_TURN_MS = 850; + /** Owns only presentation resources. The caller retains the decoded canvas and the stream connection. */ export function createPhoneViewer(options: { readonly canvas: HTMLCanvasElement; @@ -53,6 +62,7 @@ export function createPhoneViewer(options: { readonly profile?: DeviceShapeProfile; readonly model?: DeviceModelSource | null; readonly accessory?: DeviceAccessorySource | null; + readonly foldAngle?: number | null; }): PhoneViewer { const renderer = new WebGLRenderer({ canvas: options.canvas, @@ -87,11 +97,31 @@ export function createPhoneViewer(options: { let screen: DeviceScreenSize | null = null; let layout = phoneDisplayLayout(screen, options.source.width, options.source.height); let profile = options.profile ?? IOS_PHONE_SHAPE; + let foldAngle = options.foldAngle ?? null; + let orientationAngle = + foldAngle !== null && profile.id.startsWith("android") ? 0 : layout.rotation; + let orientationTurn: { from: number; to: number; startedAt: number } | null = null; let imported: Awaited> | null = null; let modelSource = options.model ?? null; let accessory: Awaited> | null = null; let accessoryBounds: Box3 | null = null; - let phone = createPhoneScene(texture, layout, profile); + let foldTurn: { from: number; to: number; startedAt: number } | null = null; + // The inner display's raw width over height. Cover frames leave the last unfolded shape. + const rawAspect = () => options.source.width / options.source.height; + let foldAspect = isFoldInnerAspect(rawAspect()) ? rawAspect() : DEFAULT_FOLD_INNER_ASPECT; + const createFoldScene = (angle: number, displayLayout = layout) => + createAndroidFoldScene(texture, displayLayout, angle, foldAspect); + /** The hinge angle currently on screen, including an unfinished turn. */ + const visibleFoldAngle = (fallback: number) => { + if (!foldTurn) return fallback; + const progress = Math.min(1, (performance.now() - foldTurn.startedAt) / ANDROID_FOLD_TURN_MS); + const eased = progress * progress * (3 - 2 * progress); + return foldTurn.from + (foldTurn.to - foldTurn.from) * eased; + }; + let phone: ReturnType | ReturnType = + foldAngle !== null && profile.id.startsWith("android") + ? createFoldScene(foldAngle) + : createPhoneScene(texture, layout, profile); scene.add(phone.root); let disposed = false; const rest = new Quaternion(); @@ -116,7 +146,7 @@ export function createPhoneViewer(options: { new Vector3(phone.width / 2, phone.height / 2, 0), ); if (imported && accessoryBounds) bounds.union(accessoryBounds); - bounds.applyMatrix4(new Matrix4().makeRotationZ(layout.rotation)); + bounds.applyMatrix4(new Matrix4().makeRotationZ(orientationAngle)); const size = bounds.getSize(new Vector3()); const aspect = size.x / size.y; if (aspect !== framingAspect) { @@ -140,7 +170,7 @@ export function createPhoneViewer(options: { }; const applyPose = () => { phone.root.quaternion.copy(motion.rotation); - phone.orientation.rotation.z = layout.rotation; + phone.orientation.rotation.z = orientationAngle; }; const scheduler = createRenderScheduler(() => { if (disposed || !viewport.width || !viewport.height) return; @@ -160,10 +190,30 @@ export function createPhoneViewer(options: { applyPose(); fit(reducedMotion()); } + if (orientationTurn) { + const progress = Math.min( + 1, + (now - orientationTurn.startedAt) / ANDROID_ORIENTATION_TURN_MS, + ); + const eased = progress * progress * (3 - 2 * progress); + orientationAngle = + orientationTurn.from + (orientationTurn.to - orientationTurn.from) * eased; + if (progress === 1) orientationTurn = null; + applyPose(); + fit(reducedMotion()); + } + if (foldTurn && "setAngle" in phone) { + const progress = Math.min(1, (now - foldTurn.startedAt) / ANDROID_FOLD_TURN_MS); + const eased = progress * progress * (3 - 2 * progress); + phone.setAngle(foldTurn.from + (foldTurn.to - foldTurn.from) * eased); + if (progress === 1) foldTurn = null; + fit(reducedMotion()); + } framing.advance(now, reducedMotion()); applyCamera(); renderer.render(scene, camera); - if (motion.needsFrame() || framing.needsFrame()) scheduler.invalidate(); + if (motion.needsFrame() || framing.needsFrame() || orientationTurn || foldTurn) + scheduler.invalidate(); } catch { options.onUnavailable(); } @@ -188,7 +238,22 @@ export function createPhoneViewer(options: { phone.setDisplay(texture, next); previous.dispose(); } - if (!imported && (nextProfile !== profile || next.aspect !== layout.aspect)) { + // Learn the inner display shape from any unfolded frame, including before fold mode. + const frameAspect = rawAspect(); + const innerChanged = isFoldInnerAspect(frameAspect) && frameAspect !== foldAspect; + if (innerChanged) foldAspect = frameAspect; + if (!imported && "setAngle" in phone && innerChanged) { + // A new inner display shape resizes the body; the hinge keeps its visible angle. + const angle = visibleFoldAngle(foldAngle ?? 180); + scene.remove(phone.root); + phone.dispose(); + phone = createFoldScene(angle, next); + scene.add(phone.root); + } else if ( + !imported && + !("setAngle" in phone) && + (nextProfile !== profile || next.aspect !== layout.aspect) + ) { scene.remove(phone.root); phone.dispose(); phone = createPhoneScene(texture, next, nextProfile); @@ -196,10 +261,26 @@ export function createPhoneViewer(options: { } else { phone.setDisplay(texture, next); } + if (next.rotation !== layout.rotation) { + if (nextProfile.id.startsWith("android") && !("setAngle" in phone) && !reducedMotion()) { + const difference = Math.atan2( + Math.sin(next.rotation - orientationAngle), + Math.cos(next.rotation - orientationAngle), + ); + orientationTurn = { + from: orientationAngle, + to: orientationAngle + difference, + startedAt: performance.now(), + }; + } else { + orientationTurn = null; + orientationAngle = "setAngle" in phone ? 0 : next.rotation; + } + } layout = next; profile = nextProfile; applyPose(); - fit(true); + fit(!orientationTurn); } applyPose(); }; @@ -212,7 +293,10 @@ export function createPhoneViewer(options: { ? null : model ? createImportedPhoneScene(model.asset, texture, layout) - : createPhoneScene(texture, layout, profile); + : foldAngle !== null && profile.id.startsWith("android") + ? createFoldScene(foldAngle) + : createPhoneScene(texture, layout, profile); + foldTurn = null; scene.remove(phone.root); phone.dispose(); imported = model; @@ -267,6 +351,34 @@ export function createPhoneViewer(options: { modelSlot.set(source); }, setAccessory, + setFoldAngle(next) { + if (disposed || next === foldAngle) return; + const previous = foldAngle; + foldAngle = next; + // A loaded model owns the scene; install() reads foldAngle if it is removed. + if (imported) return; + if (next === null || !("setAngle" in phone)) { + scene.remove(phone.root); + phone.dispose(); + phone = next === null ? createPhoneScene(texture, layout, profile) : createFoldScene(next); + scene.add(phone.root); + orientationTurn = null; + orientationAngle = next === null ? layout.rotation : 0; + foldTurn = null; + applyPose(); + fit(true); + } else { + const from = visibleFoldAngle(previous ?? next); + if (reducedMotion()) { + foldTurn = null; + phone.setAngle(next); + fit(true); + } else { + foldTurn = { from, to: next, startedAt: performance.now() }; + } + } + scheduler.invalidate(); + }, frameUpdated() { if (disposed) return; updateLayout(); diff --git a/packages/client-runtime/src/device/stream.ts b/packages/client-runtime/src/device/stream.ts index cc849ea3e15e..6b1ff6158879 100644 --- a/packages/client-runtime/src/device/stream.ts +++ b/packages/client-runtime/src/device/stream.ts @@ -19,6 +19,14 @@ * The decoder only runs while frames arrive and the viewer is attached; a * hidden panel calls `stop()` so an idle device costs nothing on the GPU. */ +import { + createDuoControl, + type DuoCommand, + type DuoControlState, + type DuoPose, +} from "./duoControl.ts"; +import * as Schema from "effect/Schema"; +import * as Option from "effect/Option"; import { createCanvasFrameSink, type DeviceFrameSink } from "./frame.ts"; import { type DeviceHubAccess, withDeviceHubQuery } from "./hubAccess.ts"; import type { DevicePlatform } from "@t3tools/contracts"; @@ -29,9 +37,55 @@ export interface DeviceScreenSize { readonly width: number; readonly height: number; readonly orientation: "portrait" | "portrait_upside_down" | "landscape_left" | "landscape_right"; + readonly screenId?: number; + readonly supportsHingeAngle?: boolean; + readonly supportsPhysicalOrientation?: boolean; + readonly hingeAngle?: number; + readonly hingePose?: DuoPose | null; + readonly tableMode?: boolean; + readonly tableModeAvailable?: boolean; +} + +const screenConfigSchema = Schema.Struct({ + width: Schema.Finite.check(Schema.isGreaterThan(0)), + height: Schema.Finite.check(Schema.isGreaterThan(0)), + orientation: Schema.Literals([ + "portrait", + "portrait_upside_down", + "landscape_left", + "landscape_right", + ]), + screenId: Schema.optionalKey(Schema.Number), + supportsHingeAngle: Schema.optionalKey(Schema.Boolean), + supportsPhysicalOrientation: Schema.optionalKey(Schema.Boolean), + hingeAngle: Schema.optionalKey( + Schema.Finite.check(Schema.isBetween({ minimum: 0, maximum: 180 })), + ), + hingePose: Schema.optionalKey( + Schema.NullOr(Schema.Literals(["closed", "book", "open", "laptop", "tent"])), + ), + tableMode: Schema.optionalKey(Schema.Boolean), + tableModeAvailable: Schema.optionalKey(Schema.Boolean), +}); +const controlReplySchema = Schema.Struct({ + requestId: Schema.Int, + ok: Schema.Boolean, + error: Schema.optionalKey(Schema.String), +}); +const decodeScreenConfig = Schema.decodeUnknownOption(screenConfigSchema); +const decodeControlReply = Schema.decodeUnknownOption(controlReplySchema); + +export interface DuoPanelSinks { + readonly cover: DeviceFrameSink; + readonly inner: DeviceFrameSink; + /** Invalidate captured input synchronously, before React can commit the new layout. */ + readonly onScreen?: (screen: DeviceScreenSize) => void; } export interface DeviceStreamEvents { + readonly onDuoControl?: (state: DuoControlState) => void; + /** A fixed panel cannot be decoded; the owner should return to the active flat feed. */ + readonly onDuoUnavailable?: (detail?: string) => void; readonly onStatus: (status: DeviceStreamStatus, detail?: string) => void; readonly onScreen: (screen: DeviceScreenSize) => void; /** The proxy rejected the credential; the owner should refresh access and reconnect. */ @@ -52,6 +106,9 @@ export interface DeviceStreamTarget { readonly access: DeviceHubAccess; /** Native iOS WebViews can use MJPEG without cross-origin fetch or secure-context support. */ readonly preferMjpeg?: boolean; + /** Internal fixed-panel feeds share their parent's input session. */ + readonly panelId?: 1 | 3; + readonly videoOnly?: boolean; } export type DeviceHardwareButton = "home" | "back" | "recents" | "power" | "appSwitcher"; @@ -219,6 +276,11 @@ export interface DeviceStreamClient { readonly pressButton: (button: DeviceHardwareButton) => void; readonly rotate: () => void; readonly setOrientation: (orientation: DeviceScreenSize["orientation"]) => void; + readonly controlDuo: (command: DuoCommand) => void; + /** Switch between one active feed and two fixed-panel feeds without replacing HID. */ + readonly setDuoPanels: (panels: DuoPanelSinks | null) => void; + /** Model UVs already map to the hardware framebuffer. */ + readonly sendRawTouch: (phase: "begin" | "move" | "end", x: number, y: number) => void; } const HID_USAGE_BY_CODE: Readonly> = { @@ -314,6 +376,37 @@ export function createDeviceStreamClient( let videoReadTimer: ReturnType | null = null; let mjpegImage: HTMLImageElement | null = null; let releaseImage: (() => void) | null = null; + let videoGeneration = 0; + let panelClients: DeviceStreamClient[] = []; + let panelSinks: DuoPanelSinks | null = null; + let rotationCursor: DeviceScreenSize["orientation"] | null = null; + let pendingOrientation: { requestId: number } | null = null; + const duoControl = createDuoControl({ + send(request) { + if (socket?.readyState !== WebSocket.OPEN || !screen?.supportsHingeAngle) return false; + if (request.command.control === "physical" && !screen.supportsPhysicalOrientation) + return false; + try { + pendingOrientation = null; + if (request.command.control === "orientation") { + const value = request.command.value; + pendingOrientation = { requestId: request.requestId }; + rotationCursor = value; + // Upstream serializes orientation with hinge commands, then broadcasts config. + // An orientation-locked app can keep its framebuffer orientation after the sensor rotates. + socket.send(taggedJson(IOS_MSG_ORIENTATION, { orientation: value })); + } else socket.send(taggedJson(0x10, request)); + return true; + } catch { + return false; + } + }, + onChange(state) { + if (!state.pending) pendingOrientation = null; + events.onDuoControl?.(state); + }, + }); + const videoPath = `/helper/${device}${target.panelId ? `/panel/${target.panelId}` : ""}/stream.avcc`; const mjpegUrl = () => httpUrl(`/helper/${device}/stream.mjpeg`); @@ -437,16 +530,26 @@ export function createDeviceStreamClient( }; const makeDecoder = () => { + const feedGeneration = videoGeneration; const decoder = new VideoDecoder({ output: (frame) => { try { - if (videoDecoder === decoder) paint(frame, frame.displayWidth, frame.displayHeight); + if ( + videoDecoder === decoder && + (platform !== "ios" || feedGeneration === videoGeneration) + ) + paint(frame, frame.displayWidth, frame.displayHeight); } finally { frame.close(); } }, error: () => { - if (stopped || videoDecoder !== decoder) return; + if ( + stopped || + videoDecoder !== decoder || + (platform === "ios" && feedGeneration !== videoGeneration) + ) + return; recoverDecoder(); }, }); @@ -456,7 +559,7 @@ export function createDeviceStreamClient( /** iOS can fall back to MJPEG when the stream's H.264 profile is unsupported. */ const configureDecoder = async ( config: VideoDecoderConfig, - isCurrent: () => boolean, + isCurrent = () => !stopped, ): Promise => { const epoch = decoderEpoch; const full: VideoDecoderConfig = { ...config, optimizeForLatency: true }; @@ -525,19 +628,32 @@ export function createDeviceStreamClient( // iOS video: fetch the AVCC body and demux into the decoder. const readIosVideo = async () => { + const lifecycle = generation; + const feedGeneration = ++videoGeneration; const demuxer = new AvccDemuxer(); - const session = generation; const videoController = new AbortController(); controller = videoController; - const isCurrent = () => !stopped && generation === session && controller === videoController; + const isCurrent = () => + !stopped && + generation === lifecycle && + videoGeneration === feedGeneration && + controller === videoController; let retryDetail: string | undefined; try { - const response = await fetch(httpUrl(`/helper/${device}/stream.avcc`), { + const response = await fetch(httpUrl(videoPath), { signal: videoController.signal, credentials: access.credentials ? "include" : "same-origin", }); - if (!isCurrent()) return; + if (!isCurrent()) { + await response.body?.cancel(); + return; + } if (response.status === 401 || response.status === 403) return handleUnauthorized(); + if (target.panelId && [400, 404, 405, 410].includes(response.status)) { + await response.body?.cancel(); + setStatus("error", "This Device Hub does not provide fixed Duo display feeds."); + return; + } if (!response.ok || !response.body) throw new Error(`stream ${response.status}`); const reader = response.body.getReader(); for (;;) { @@ -579,7 +695,12 @@ export function createDeviceStreamClient( if (!configured) { await reader.cancel().catch(() => {}); if (!isCurrent()) return; - fallBackToMjpeg(); + if (target.videoOnly) + setStatus( + "error", + `This browser cannot decode the Duo panel's ${avcCodecString(chunk.payload)} stream.`, + ); + else fallBackToMjpeg(); return; } break; @@ -630,6 +751,42 @@ export function createDeviceStreamClient( } }; + const startDuoVideo = (panels: DuoPanelSinks) => { + for (const panel of panelClients) panel.stop(); + // Physical handoff elects a native surface. Fixed-panel encoders can keep + // an inactive shutdown frame after election, so this build uses one active + // feed instead of decoding a third stream alongside the two fixed feeds. + const ids = screen?.supportsPhysicalOrientation ? ([null] as const) : ([1, 3] as const); + panelClients = ids.map((id) => { + const output = id === 1 ? panels.cover : panels.inner; + return createDeviceStreamClient( + { ...target, ...(id === null ? {} : { panelId: id }), videoOnly: true }, + { + present(source, width, height) { + if (id === null) { + return sink.present(source, width, height); + } + // An inactive native LCD can emit its shutdown black frame. Retain its last useful image. + if (screen?.screenId !== id) return true; + const retained = output.present(source, width, height); + const primary = sink.present(source, width, height); + return retained && primary; + }, + }, + { + onStatus: (status, detail) => { + if (status === "error") events.onDuoUnavailable?.(detail); + }, + onScreen: () => {}, + onInputConnected: () => {}, + onMjpegFallback: () => {}, + onUnauthorized: handleUnauthorized, + }, + ); + }); + for (const panel of panelClients) panel.start(); + }; + // iOS input socket; also carries the screen config the helper pushes. const connectIosInput = async () => { if (stopped) return; @@ -648,12 +805,42 @@ export function createDeviceStreamClient( if (stopped || socket !== ws) return; if (!(event.data instanceof ArrayBuffer)) return; const bytes = new Uint8Array(event.data); - if (bytes.length < 1 || bytes[0] !== IOS_TAG_SCREEN_CONFIG) return; + if (socket !== ws || stopped || bytes.length < 1) return; try { - const config = JSON.parse(decoder.decode(bytes.subarray(1))) as DeviceScreenSize; - if (config.width > 0 && config.height > 0) { - screen = config; - events.onScreen(config); + const payload: unknown = JSON.parse(decoder.decode(bytes.subarray(1))); + if (bytes[0] === 0x90) { + const reply = decodeControlReply(payload); + if (Option.isSome(reply)) duoControl.receive(reply.value); + } else if (bytes[0] === IOS_TAG_SCREEN_CONFIG) { + const config = decodeScreenConfig(payload); + if (Option.isSome(config)) { + const previous = screen; + screen = config.value; + if (screen.hingePose && screen.hingePose !== previous?.hingePose) + rotationCursor = screen.hingePose === "laptop" ? "landscape_left" : "portrait"; + else if (screen.orientation !== previous?.orientation) + rotationCursor = screen.orientation; + panelSinks?.onScreen?.(screen); + events.onScreen(screen); + // A surface election can leave an existing decoder on the former + // encoder description. Reopen only video to acquire the elected + // surface's seed and codec configuration; HID and the viewer stay. + if ( + panelSinks && + screen.supportsPhysicalOrientation && + previous && + screen.screenId !== previous.screenId + ) + startDuoVideo(panelSinks); + if (pendingOrientation) { + const receipt = pendingOrientation; + pendingOrientation = null; + duoControl.receive({ + requestId: receipt.requestId, + ok: true, + }); + } + } } } catch { // Ignore malformed config frames. @@ -662,6 +849,8 @@ export function createDeviceStreamClient( ws.onclose = (event) => { if (socket !== ws) return; socket = null; + duoControl.clear(); + rotationCursor = null; if (stopped) return; events.onInputConnected( false, @@ -757,7 +946,7 @@ export function createDeviceStreamClient( configuring = false; connecting(); if (platform === "ios") { - void connectIosInput(); + if (!target.videoOnly) void connectIosInput(); if (useWebCodecs) void readIosVideo(); else fallBackToMjpeg(); } else if (useWebCodecs) { @@ -771,6 +960,12 @@ export function createDeviceStreamClient( if (stopped) return; stopped = true; generation++; + videoGeneration++; + duoControl.clear(); + rotationCursor = null; + for (const panel of panelClients) panel.stop(); + panelClients = []; + panelSinks = null; mjpeg = false; clearFrameTimer(); if (videoReadTimer !== null) clearTimeout(videoReadTimer); @@ -814,6 +1009,29 @@ export function createDeviceStreamClient( start, stop, setMjpegImage, + controlDuo: duoControl.enqueue, + sendRawTouch: (phase, x, y) => { + if (platform === "ios") send(taggedJson(IOS_MSG_TOUCH, { type: phase, x, y })); + }, + setDuoPanels(panels) { + if (platform !== "ios" || target.videoOnly || stopped || panelSinks === panels) return; + if (panels && !screen?.supportsHingeAngle) return; + panelSinks = panels; + videoGeneration++; + controller?.abort(); + controller = null; + closeDecoder(); + const retry = retryTimers.get("video"); + if (retry) clearTimeout(retry); + retryTimers.delete("video"); + for (const panel of panelClients) panel.stop(); + panelClients = []; + if (!panels) { + if (useWebCodecs) void readIosVideo(); + return; + } + startDuoVideo(panels); + }, sendTouch: (phase, x, y) => { if (platform === "ios") { send(taggedJson(IOS_MSG_TOUCH, { type: phase, ...rawPoint(x, y) })); @@ -856,10 +1074,15 @@ export function createDeviceStreamClient( }, rotate: () => { if (platform !== "ios") return; - const current = screen?.orientation ?? "portrait"; + const current = screen?.supportsHingeAngle + ? (rotationCursor ?? screen.orientation) + : (screen?.orientation ?? "portrait"); const next = IOS_ORIENTATIONS[(IOS_ORIENTATIONS.indexOf(current) + 1) % IOS_ORIENTATIONS.length]!; - send(taggedJson(IOS_MSG_ORIENTATION, { orientation: next })); + if (screen?.supportsHingeAngle) { + rotationCursor = next; + duoControl.enqueue({ control: "orientation", value: next }); + } else send(taggedJson(IOS_MSG_ORIENTATION, { orientation: next })); }, setOrientation: (orientation) => { if (platform === "ios") send(taggedJson(IOS_MSG_ORIENTATION, { orientation })); diff --git a/packages/client-runtime/src/operations/commands.ts b/packages/client-runtime/src/operations/commands.ts index 8f313c1632ff..a08adba22949 100644 --- a/packages/client-runtime/src/operations/commands.ts +++ b/packages/client-runtime/src/operations/commands.ts @@ -42,6 +42,7 @@ export type UnsnoozeThreadInput = CommandInput<"thread.unsnooze">; export type PinThreadInput = CommandInput<"thread.pin">; export type UnpinThreadInput = CommandInput<"thread.unpin">; export type ReorderPinnedThreadInput = CommandInput<"thread.pin.reorder">; +export type SetThreadAutoSettleInput = CommandInput<"thread.auto-settle.set">; export type ReorderActiveThreadInput = CommandInput<"thread.active.reorder">; export type UpdateThreadMetadataInput = CommandInput<"thread.meta.update">; export type LinkThreadPullRequestInput = CommandInput<"thread.pull-request.link">; @@ -226,6 +227,16 @@ export const unpinThread: (input: UnpinThreadInput) => CommandEffect = Effect.fn }); }); +export const setThreadAutoSettle: (input: SetThreadAutoSettleInput) => CommandEffect = Effect.fn( + "EnvironmentCommands.setThreadAutoSettle", +)(function* (input) { + return yield* dispatch({ + ...input, + type: "thread.auto-settle.set", + commandId: yield* commandId(input), + }); +}); + export const reorderPinnedThread: (input: ReorderPinnedThreadInput) => CommandEffect = Effect.fn( "EnvironmentCommands.reorderPinnedThread", )(function* (input) { diff --git a/packages/client-runtime/src/platform/storageDocument.test.ts b/packages/client-runtime/src/platform/storageDocument.test.ts index a0c7de3d093b..811ad170b6dd 100644 --- a/packages/client-runtime/src/platform/storageDocument.test.ts +++ b/packages/client-runtime/src/platform/storageDocument.test.ts @@ -207,7 +207,7 @@ describe("ConnectionCatalogDocument", () => { remoteDpopTokens: [token], }; const schema = Schema.fromJsonString(ConnectionCatalogDocument); - const restored = Schema.decodeUnknownSync(schema)(Schema.encodeSync(schema)(document)); + const restored = Schema.decodeSync(schema)(Schema.encodeSync(schema)(document)); expect(restored).toEqual(document); expect(restored.remoteDpopTokens[0]?.accountId).toBe(accountId); diff --git a/packages/client-runtime/src/relay/discovery.test.ts b/packages/client-runtime/src/relay/discovery.test.ts index fdbcceb7df86..8251b2dc36d9 100644 --- a/packages/client-runtime/src/relay/discovery.test.ts +++ b/packages/client-runtime/src/relay/discovery.test.ts @@ -134,15 +134,13 @@ const makeHarness = Effect.fn("RelayDiscoveryTest.makeHarness")(function* () { ), ), clerkToken: Ref.get(clerkToken).pipe( - Effect.flatMap((token) => - token === null - ? Effect.fail( - new ConnectionBlockedError({ - reason: "authentication", - detail: "Signed out.", - }), - ) - : Effect.succeed(token), + Effect.filterOrFail( + (token) => token !== null, + () => + new ConnectionBlockedError({ + reason: "authentication", + detail: "Signed out.", + }), ), ), }), @@ -289,7 +287,7 @@ describe("RelayEnvironmentDiscovery", () => { Layer.mergeAll( Layer.succeed(ManagedRelay.ManagedRelayClient, client), Layer.succeed(ClientCapabilities.CloudSession, { - identity: Effect.succeed(Option.some({ accountId: "account-1" })), + identity: Effect.succeedSome({ accountId: "account-1" }), clerkToken: Effect.succeed("clerk-token"), }), Layer.succeed(Connectivity.Connectivity, { diff --git a/packages/client-runtime/src/relay/discovery.ts b/packages/client-runtime/src/relay/discovery.ts index 8a8eae1d6cd9..8aee35347e2b 100644 --- a/packages/client-runtime/src/relay/discovery.ts +++ b/packages/client-runtime/src/relay/discovery.ts @@ -241,7 +241,7 @@ export const make = Effect.fn("RelayEnvironmentDiscovery.make")(function* () { })); return; } - return yield* Effect.fail(failure); + return yield* failure; } const clerkToken = tokenResult.success; if ((yield* Ref.get(accountGeneration)) !== generation) { diff --git a/packages/client-runtime/src/relay/managedRelay.ts b/packages/client-runtime/src/relay/managedRelay.ts index eadece417681..9c414ad9e605 100644 --- a/packages/client-runtime/src/relay/managedRelay.ts +++ b/packages/client-runtime/src/relay/managedRelay.ts @@ -666,11 +666,8 @@ export const make = Effect.fn("ManagedRelayClient.make")(function* ( authorize(input).pipe( Effect.flatMap((authorization) => request(authorization).pipe( - Effect.catch((error) => { - if (!isRejectedDpopAccessToken(error)) { - return Effect.fail(error); - } - return invalidateAccessToken(authorization.accessToken).pipe( + Effect.catchIf(isRejectedDpopAccessToken, (error) => + invalidateAccessToken(authorization.accessToken).pipe( Effect.tap((invalidated) => Effect.annotateCurrentSpan({ "relay.token_cache.invalidated": invalidated, @@ -686,8 +683,8 @@ export const make = Effect.fn("ManagedRelayClient.make")(function* ( : Effect.void, ), Effect.andThen(refreshRejectedToken ? attempt(false) : Effect.fail(error)), - ); - }), + ), + ), ), ), ); diff --git a/packages/client-runtime/src/relay/managedRelayState.ts b/packages/client-runtime/src/relay/managedRelayState.ts index cb6d5983d394..cd092689cdd4 100644 --- a/packages/client-runtime/src/relay/managedRelayState.ts +++ b/packages/client-runtime/src/relay/managedRelayState.ts @@ -180,14 +180,12 @@ function readSessionClerkToken( session: ManagedRelaySession, ): Effect.Effect { return session.readClerkToken().pipe( - Effect.flatMap((token) => - token - ? Effect.succeed(token) - : Effect.fail( - new ManagedRelaySessionError({ - message: "The T3 Connect session token is unavailable.", - }), - ), + Effect.filterOrFail( + (token): token is string => Boolean(token), + () => + new ManagedRelaySessionError({ + message: "The T3 Connect session token is unavailable.", + }), ), ); } diff --git a/packages/client-runtime/src/rpc/session.test.ts b/packages/client-runtime/src/rpc/session.test.ts index 65b537921f09..9d49b8e6240a 100644 --- a/packages/client-runtime/src/rpc/session.test.ts +++ b/packages/client-runtime/src/rpc/session.test.ts @@ -161,7 +161,7 @@ const RpcRequest = Schema.TaggedStruct("Request", { }); const decodeJson = Schema.decodeUnknownSync(Schema.fromJsonString(Schema.Unknown)); const isRpcRequest = Schema.is(RpcRequest); -const isPing = Schema.is(Schema.Struct({ _tag: Schema.Literal("Ping") })); +const isPing = Schema.is(Schema.TaggedStruct("Ping", {})); const encodeJson = Schema.encodeUnknownSync(Schema.fromJsonString(Schema.Unknown)); const encodeServerConfig = Schema.encodeSync(ServerConfig); const encodeServerConfigStreamEvent = Schema.encodeSync(ServerConfigStreamEvent); @@ -823,14 +823,14 @@ describe("RpcSessionFactory", () => { retryNow: Effect.void, } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/rpc/session.ts b/packages/client-runtime/src/rpc/session.ts index 252bf0242236..93bb2ff2f7d3 100644 --- a/packages/client-runtime/src/rpc/session.ts +++ b/packages/client-runtime/src/rpc/session.ts @@ -291,13 +291,13 @@ export const make = Effect.fn("RpcSessionFactory.make")(function* ( Effect.flatMap(() => Effect.fail(configSubscriptionEndedError)), ), ).pipe( - Effect.flatMap((config) => - config.environment.environmentId === connection.environmentId - ? Effect.succeed(config) - : environmentMismatchError({ - expected: connection.environmentId, - actual: config.environment.environmentId, - }), + Effect.filterOrElse( + (config) => config.environment.environmentId === connection.environmentId, + (config) => + environmentMismatchError({ + expected: connection.environmentId, + actual: config.environment.environmentId, + }), ), Effect.withSpan("environment.initialSync"), ); diff --git a/packages/client-runtime/src/state/pullRequestRouting.ts b/packages/client-runtime/src/state/pullRequestRouting.ts index af625bbad56e..999c6133f5da 100644 --- a/packages/client-runtime/src/state/pullRequestRouting.ts +++ b/packages/client-runtime/src/state/pullRequestRouting.ts @@ -339,15 +339,11 @@ export function createPullRequestRouter() { } const operation = run(id); return yield* (reads.has(tag) ? operation.pipe(readTimeout(id)) : operation).pipe( - Effect.catch((error) => { - if ( - (reads.has(tag) || rejectedBeforeDispatch(error)) && - index + 1 < candidates.length - ) { - return visit(index + 1); - } - return Effect.fail(error); - }), + Effect.catchIf( + (error) => + (reads.has(tag) || rejectedBeforeDispatch(error)) && index + 1 < candidates.length, + () => visit(index + 1), + ), Effect.map((result) => typeof result === "object" && result !== null && "projectId" in result ? { diff --git a/packages/client-runtime/src/state/pullRequests.test.ts b/packages/client-runtime/src/state/pullRequests.test.ts index 75e6b38f4a2e..b6d8c1733f1c 100644 --- a/packages/client-runtime/src/state/pullRequests.test.ts +++ b/packages/client-runtime/src/state/pullRequests.test.ts @@ -1171,7 +1171,7 @@ it.effect("updates cached labels after successful edits without rereading the ho if (failDetail) { yield* detailRefreshStarted.open; yield* releaseDetailRefresh.await; - return yield* Effect.fail(new MutationRefused()); + return yield* new MutationRefused(); } return { title: "keep this title", labels: [existing] }; }), diff --git a/packages/client-runtime/src/state/server.test.ts b/packages/client-runtime/src/state/server.test.ts index 878f8c902f91..7443850b1b5e 100644 --- a/packages/client-runtime/src/state/server.test.ts +++ b/packages/client-runtime/src/state/server.test.ts @@ -778,14 +778,14 @@ describe("server state projection", () => { } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const savedConfigs = yield* Queue.unbounded(); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.some(CONFIG)), + loadServerConfig: () => Effect.succeedSome(CONFIG), saveServerConfig: (_environmentId, config) => Queue.offer(savedConfigs, config), - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, @@ -839,14 +839,14 @@ describe("server state projection", () => { } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const savedConfigs = yield* Queue.unbounded(); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.some(CONFIG)), + loadServerConfig: () => Effect.succeedSome(CONFIG), saveServerConfig: (_environmentId, config) => Queue.offer(savedConfigs, config), - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/state/server.ts b/packages/client-runtime/src/state/server.ts index a7476ec17157..5919c0af263a 100644 --- a/packages/client-runtime/src/state/server.ts +++ b/packages/client-runtime/src/state/server.ts @@ -933,6 +933,14 @@ export function createServerEnvironmentAtoms( ); }).pipe(Atom.withLabel(`environment-data:server:usage-prices:${environmentId}`)), ); + const usageScanSettingsAtom = Atom.family((environmentId: EnvironmentId) => + Atom.make((get) => + JSON.stringify([ + get(usagePricesAtom(environmentId)), + get(settingsValueAtom(environmentId))?.cursorKeychainUsageEnabled ?? false, + ]), + ).pipe(Atom.withLabel(`environment-data:server:usage-scan-settings:${environmentId}`)), + ); const providersValueAtom = Atom.family((environmentId: EnvironmentId) => Atom.make((get) => get(configValueAtom(environmentId))?.providers ?? null).pipe( Atom.withLabel(`environment-data:server:providers:${environmentId}`), @@ -1050,7 +1058,7 @@ export function createServerEnvironmentAtoms( label: "environment-data:server:usage-summary", tag: WS_METHODS.serverGetUsageSummary, staleTimeMs: 60_000, - refreshTrigger: ({ environmentId }) => usagePricesAtom(environmentId), + refreshTrigger: ({ environmentId }) => usageScanSettingsAtom(environmentId), }), configProjection, welcome, diff --git a/packages/client-runtime/src/state/serverUsage.test.ts b/packages/client-runtime/src/state/serverUsage.test.ts index 9cf2667d8bc2..2df10f7cf860 100644 --- a/packages/client-runtime/src/state/serverUsage.test.ts +++ b/packages/client-runtime/src/state/serverUsage.test.ts @@ -125,14 +125,14 @@ const makeHarness = Effect.fn("ServerUsageTest.makeHarness")(function* ( Stream.provideService(stream, EnvironmentSupervisor, supervisor), } as EnvironmentRegistry["Service"]); const cache = EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/state/session.ts b/packages/client-runtime/src/state/session.ts index 9a0a9dfe9a5f..5787bd0e60b7 100644 --- a/packages/client-runtime/src/state/session.ts +++ b/packages/client-runtime/src/state/session.ts @@ -20,7 +20,7 @@ function initialConfigOption( initialConfig: Effect.Effect, ): Effect.Effect> { return initialConfig.pipe( - Effect.map(Option.some), + Effect.asSome, Effect.catch((error) => Effect.logWarning("Could not load the initial environment configuration.").pipe( Effect.annotateLogs({ ...safeErrorLogAttributes(error) }), diff --git a/packages/client-runtime/src/state/shell-sync.test.ts b/packages/client-runtime/src/state/shell-sync.test.ts index 0d933c39f8ba..9483d86de0ff 100644 --- a/packages/client-runtime/src/state/shell-sync.test.ts +++ b/packages/client-runtime/src/state/shell-sync.test.ts @@ -80,14 +80,14 @@ describe("environment shell synchronization", () => { retryNow: Effect.void, } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.never, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, @@ -96,7 +96,7 @@ describe("environment shell synchronization", () => { // Cold cache with no HTTP snapshot available → falls back to the // socket-embedded snapshot. const snapshotLoader = ShellSnapshotLoader.of({ - load: () => Effect.succeed(Option.none()), + load: () => Effect.succeedNone, }); const shellState = yield* makeEnvironmentShellState().pipe( Effect.provideService(EnvironmentSupervisor.EnvironmentSupervisor, supervisor), @@ -176,14 +176,14 @@ describe("environment shell synchronization", () => { retryNow: Effect.void, } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, @@ -194,7 +194,7 @@ describe("environment shell synchronization", () => { Effect.provideService(Persistence.EnvironmentCacheStore, cache), Effect.provideService( ShellSnapshotLoader, - ShellSnapshotLoader.of({ load: () => Effect.succeed(Option.none()) }), + ShellSnapshotLoader.of({ load: () => Effect.succeedNone }), ), ); yield* SubscriptionRef.set(supervisorState, { @@ -284,14 +284,14 @@ describe("environment shell synchronization", () => { retryNow: Effect.void, } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.some(cachedSnapshot)), + loadShell: () => Effect.succeedSome(cachedSnapshot), saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, @@ -364,14 +364,14 @@ describe("environment shell synchronization", () => { retryNow: Effect.void, } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.some(LIVE_SHELL_SNAPSHOT)), + loadShell: () => Effect.succeedSome(LIVE_SHELL_SNAPSHOT), saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/state/sourceControl.test.ts b/packages/client-runtime/src/state/sourceControl.test.ts index 33c566bf82b6..ff9824106a78 100644 --- a/packages/client-runtime/src/state/sourceControl.test.ts +++ b/packages/client-runtime/src/state/sourceControl.test.ts @@ -101,14 +101,14 @@ describe("source control environment atoms", () => { } as unknown as EnvironmentRegistry.EnvironmentRegistry["Service"]); const removed = new Array(); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: (environmentId, cwd) => Effect.sync(() => { diff --git a/packages/client-runtime/src/state/threadCommands.ts b/packages/client-runtime/src/state/threadCommands.ts index 93e22cfd0c70..bb70356aab6a 100644 --- a/packages/client-runtime/src/state/threadCommands.ts +++ b/packages/client-runtime/src/state/threadCommands.ts @@ -29,6 +29,7 @@ import { type PinThreadInput, type ReorderPinnedThreadInput, type ReorderActiveThreadInput, + type SetThreadAutoSettleInput, type SettleThreadInput, type SnoozeThreadInput, type StartThreadTurnInput, @@ -53,6 +54,7 @@ import { pinThread, reorderPinnedThread, reorderActiveThread, + setThreadAutoSettle, settleThread, snoozeThread, startThreadTurn, @@ -81,6 +83,7 @@ export type { PinThreadInput, ReorderPinnedThreadInput, ReorderActiveThreadInput, + SetThreadAutoSettleInput, SettleThreadInput, SnoozeThreadInput, StartThreadTurnInput, @@ -170,6 +173,12 @@ export function createThreadEnvironmentAtoms( scheduler, concurrency, }), + setAutoSettle: createEnvironmentCommand(runtime, { + label: "environment-data:commands:thread:set-auto-settle", + execute: (input: SetThreadAutoSettleInput) => setThreadAutoSettle(input), + scheduler, + concurrency, + }), reorderActive: createEnvironmentCommand(runtime, { label: "environment-data:commands:thread:reorder-active", execute: (input: ReorderActiveThreadInput) => reorderActiveThread(input), diff --git a/packages/client-runtime/src/state/threadDetail.ts b/packages/client-runtime/src/state/threadDetail.ts index 379985b71243..d32329a741b4 100644 --- a/packages/client-runtime/src/state/threadDetail.ts +++ b/packages/client-runtime/src/state/threadDetail.ts @@ -60,6 +60,7 @@ export function mergeEnvironmentThread( settledAt: shell.settledAt, unsettledAt: shell.unsettledAt, activeOrderKey: shell.activeOrderKey, + autoSettleDisabledAt: shell.autoSettleDisabledAt, snoozedUntil: shell.snoozedUntil, snoozedAt: shell.snoozedAt, pinnedAt: shell.pinnedAt, diff --git a/packages/client-runtime/src/state/threadReducer.test.ts b/packages/client-runtime/src/state/threadReducer.test.ts index dbee48d7808c..0330d3dd8bd9 100644 --- a/packages/client-runtime/src/state/threadReducer.test.ts +++ b/packages/client-runtime/src/state/threadReducer.test.ts @@ -290,6 +290,46 @@ describe("applyThreadDetailEvent", () => { }); }); + describe("thread.auto-settle-set", () => { + it("stores and clears autoSettleDisabledAt", () => { + const disabledAt = "2026-04-01T05:00:00.000Z"; + const off = applyThreadDetailEvent(baseThread, { + ...baseEventFields, + sequence: 7, + occurredAt: disabledAt, + aggregateKind: "thread", + aggregateId: ThreadId.make("thread-1"), + type: "thread.auto-settle-set", + payload: { + threadId: ThreadId.make("thread-1"), + autoSettleDisabledAt: disabledAt, + updatedAt: disabledAt, + }, + }); + expect(off.kind).toBe("updated"); + if (off.kind !== "updated") return; + expect(off.thread.autoSettleDisabledAt).toBe(disabledAt); + + const on = applyThreadDetailEvent(off.thread, { + ...baseEventFields, + sequence: 8, + occurredAt: "2026-04-01T06:00:00.000Z", + aggregateKind: "thread", + aggregateId: ThreadId.make("thread-1"), + type: "thread.auto-settle-set", + payload: { + threadId: ThreadId.make("thread-1"), + autoSettleDisabledAt: null, + updatedAt: "2026-04-01T06:00:00.000Z", + }, + }); + expect(on.kind).toBe("updated"); + if (on.kind === "updated") { + expect(on.thread.autoSettleDisabledAt).toBeNull(); + } + }); + }); + describe("thread.meta-updated", () => { it.each(["f", null] as const)( "updates the active key to %s without activity", diff --git a/packages/client-runtime/src/state/threadReducer.ts b/packages/client-runtime/src/state/threadReducer.ts index 101bb34fba91..66f78a0464ec 100644 --- a/packages/client-runtime/src/state/threadReducer.ts +++ b/packages/client-runtime/src/state/threadReducer.ts @@ -131,6 +131,7 @@ export function applyThreadDetailEvent( settledAt: null, unsettledAt: null, activeOrderKey: null, + autoSettleDisabledAt: null, snoozedUntil: null, snoozedAt: null, deletedAt: null, @@ -249,6 +250,16 @@ export function applyThreadDetailEvent( }, }; + case "thread.auto-settle-set": + return { + kind: "updated", + thread: { + ...thread, + autoSettleDisabledAt: event.payload.autoSettleDisabledAt, + updatedAt: event.payload.updatedAt, + }, + }; + // ── Thread metadata ───────────────────────────────────────────── case "thread.meta-updated": return { diff --git a/packages/client-runtime/src/state/threads-atoms.test.ts b/packages/client-runtime/src/state/threads-atoms.test.ts index 6fd61c8d1cfd..34e6857041d3 100644 --- a/packages/client-runtime/src/state/threads-atoms.test.ts +++ b/packages/client-runtime/src/state/threads-atoms.test.ts @@ -200,7 +200,7 @@ const makeHarness = Effect.fn("TestThreadAtoms.makeHarness")(function* (options? Layer.succeed( EnvironmentCacheStore, EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, loadThread: () => Effect.sync(() => { @@ -209,9 +209,9 @@ const makeHarness = Effect.fn("TestThreadAtoms.makeHarness")(function* (options? }), saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/state/threads-pagination.test.ts b/packages/client-runtime/src/state/threads-pagination.test.ts index a473deb1f97b..4dbc3329994e 100644 --- a/packages/client-runtime/src/state/threads-pagination.test.ts +++ b/packages/client-runtime/src/state/threads-pagination.test.ts @@ -197,16 +197,16 @@ const makeHarness = Effect.fn("TestThreadPagination.makeHarness")(function* (opt retryNow: Effect.void, } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, loadThread: () => Effect.succeed(options?.cached !== undefined ? Option.some(options.cached) : Option.none()), saveThread: (_environmentId, thread) => Ref.update(savedThreads, (current) => [...current, thread]), removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/state/threads-sync.test.ts b/packages/client-runtime/src/state/threads-sync.test.ts index c41651576dad..848357ec2bef 100644 --- a/packages/client-runtime/src/state/threads-sync.test.ts +++ b/packages/client-runtime/src/state/threads-sync.test.ts @@ -216,7 +216,7 @@ const makeHarness = Effect.fn("TestEnvironmentThreads.makeHarness")(function* (o retryNow: Ref.update(retryCount, (count) => count + 1), } satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]); const cache = Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, loadThread: (_environmentId, threadId) => options?.loadCached ?? @@ -234,9 +234,9 @@ const makeHarness = Effect.fn("TestEnvironmentThreads.makeHarness")(function* (o ), removeThread: (_environmentId, threadId) => Ref.update(removedThreads, (current) => [...current, threadId]), - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/state/threads.ts b/packages/client-runtime/src/state/threads.ts index b7074b9304bb..e0be27d35358 100644 --- a/packages/client-runtime/src/state/threads.ts +++ b/packages/client-runtime/src/state/threads.ts @@ -126,9 +126,16 @@ function formatThreadError(cause: Cause.Cause): string { : "Could not synchronize the thread."; } +/** + * A starting or running session is mid-turn. Its detail can change many times + * per second, so the disk cache waits for it to settle. + */ +export function isThreadSessionRunning(session: OrchestrationThread["session"]): boolean { + return session?.status === "starting" || session?.status === "running"; +} + function shouldPersistThread(thread: OrchestrationThread): boolean { - const status = thread.session?.status; - return status !== "starting" && status !== "running"; + return !isThreadSessionRunning(thread.session); } interface ThreadResumeSnapshot { diff --git a/packages/client-runtime/src/state/usage.test.ts b/packages/client-runtime/src/state/usage.test.ts index 98eb46e13ca8..75977d20de74 100644 --- a/packages/client-runtime/src/state/usage.test.ts +++ b/packages/client-runtime/src/state/usage.test.ts @@ -184,6 +184,19 @@ describe("manual usage refresh", () => { }); describe("limits refresh cooldown", () => { + it("runs a fresh check after an in-flight check when settings change", async () => { + const id = EnvironmentId.make("limits-after-enable"); + const oldCheck = Promise.withResolvers(); + const first = refreshUsageLimits(id, () => oldCheck.promise, true); + const newCheck = vi.fn(async () => "new limits"); + const afterEnable = refreshUsageLimits(id, newCheck, false, true); + expect(newCheck).not.toHaveBeenCalled(); + oldCheck.resolve("old limits"); + expect(await first).toBe("old limits"); + expect(await afterEnable).toBe("new limits"); + expect(newCheck).toHaveBeenCalledTimes(1); + }); + it("joins manual calls and gates automatic refreshes after success or failure", async () => { const clock = vi.spyOn(Date, "now").mockReturnValue(1_000); try { diff --git a/packages/client-runtime/src/state/usage.ts b/packages/client-runtime/src/state/usage.ts index 8a2b1a44a951..11cfd26fbe6c 100644 --- a/packages/client-runtime/src/state/usage.ts +++ b/packages/client-runtime/src/state/usage.ts @@ -16,9 +16,18 @@ export async function refreshUsageLimits( environmentId: EnvironmentId, refresh: () => Promise, automatic = false, + afterPending = false, ): Promise { const pending = limitsRefreshes.get(environmentId); if (pending !== undefined) { + if (afterPending) { + try { + await pending; + } catch { + // The new check still needs to run if the earlier one failed. + } + return refreshUsageLimits(environmentId, refresh, false, true); + } // Manual refresh waits for the current check; automatic refresh does not repeat it. return automatic ? undefined : ((await pending) as A); } diff --git a/packages/client-runtime/src/state/vcs.test.ts b/packages/client-runtime/src/state/vcs.test.ts index d7a4692fc317..7b11c48f3ca5 100644 --- a/packages/client-runtime/src/state/vcs.test.ts +++ b/packages/client-runtime/src/state/vcs.test.ts @@ -98,12 +98,12 @@ function cacheWithRefs( overrides: Partial = {}, ) { return Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, loadVcsRefs: () => Effect.succeed(refs), saveVcsRefs: () => Effect.void, diff --git a/packages/client-runtime/src/state/vcsAction.test.ts b/packages/client-runtime/src/state/vcsAction.test.ts index 24aa314b1cff..2d840ba71ff4 100644 --- a/packages/client-runtime/src/state/vcsAction.test.ts +++ b/packages/client-runtime/src/state/vcsAction.test.ts @@ -99,14 +99,14 @@ function progress(event: T): T { function cacheStore(onClearVcsRefs: (environmentId: EnvironmentId) => void) { return Persistence.EnvironmentCacheStore.of({ - loadShell: () => Effect.succeed(Option.none()), + loadShell: () => Effect.succeedNone, saveShell: () => Effect.void, - loadThread: () => Effect.succeed(Option.none()), + loadThread: () => Effect.succeedNone, saveThread: () => Effect.void, removeThread: () => Effect.void, - loadServerConfig: () => Effect.succeed(Option.none()), + loadServerConfig: () => Effect.succeedNone, saveServerConfig: () => Effect.void, - loadVcsRefs: () => Effect.succeed(Option.none()), + loadVcsRefs: () => Effect.succeedNone, saveVcsRefs: () => Effect.void, removeVcsRefs: () => Effect.void, clearVcsRefs: (environmentId) => Effect.sync(() => onClearVcsRefs(environmentId)), diff --git a/packages/contracts/src/desktopBootstrap.ts b/packages/contracts/src/desktopBootstrap.ts index bc3558aa2962..31c604ffd7fe 100644 --- a/packages/contracts/src/desktopBootstrap.ts +++ b/packages/contracts/src/desktopBootstrap.ts @@ -23,3 +23,9 @@ export const DesktopBackendBootstrap = Schema.Struct({ }); export type DesktopBackendBootstrap = typeof DesktopBackendBootstrap.Type; + +/** Written to `/runtime` just before the desktop app stops its + backend to install an update. The updated app starts a new backend right + away, so a backend that sees a fresh marker at shutdown keeps its managed + tunnel. */ +export const DESKTOP_UPDATE_RESTART_MARKER_FILE = "desktop-update-restart"; diff --git a/packages/contracts/src/environment.ts b/packages/contracts/src/environment.ts index b5a3290f6314..089133d878a4 100644 --- a/packages/contracts/src/environment.ts +++ b/packages/contracts/src/environment.ts @@ -141,6 +141,9 @@ export const ExecutionEnvironmentCapabilities = Schema.Struct({ threadPinReorder: Schema.optionalKey(Schema.Boolean), /** Server persists manual Active order through thread.active.reorder. */ threadActiveReorder: Schema.optionalKey(Schema.Boolean), + /** Server understands thread.auto-settle.set (per-thread auto-settle off). + Same version-skew contract as threadSettlement. */ + threadAutoSettleOptOut: Schema.optionalKey(Schema.Boolean), /** Server understands regenerateTitle on thread.meta.update. Absent on older servers, so clients hide the action instead of sending it. */ threadTitleRegeneration: Schema.optionalKey(Schema.Boolean), diff --git a/packages/contracts/src/ipc.ts b/packages/contracts/src/ipc.ts index 4e4235c56431..01e9514b1a3c 100644 --- a/packages/contracts/src/ipc.ts +++ b/packages/contracts/src/ipc.ts @@ -40,6 +40,8 @@ export interface ContextMenuItem { icon?: string; /** Inserts a visual section divider immediately before this item. */ separatorBefore?: boolean; + /** Shows a check mark. Used to mark the current option inside a submenu. */ + checked?: boolean; children?: readonly ContextMenuItem[]; } @@ -55,6 +57,7 @@ export interface ContextMenuItemSchemaType { readonly header?: boolean; readonly icon?: string; readonly separatorBefore?: boolean; + readonly checked?: boolean; readonly children?: readonly ContextMenuItemSchemaType[]; } @@ -66,6 +69,7 @@ export const ContextMenuItemSchema: Schema.Codec = Sc header: Schema.optionalKey(Schema.Boolean), icon: Schema.optionalKey(Schema.String), separatorBefore: Schema.optionalKey(Schema.Boolean), + checked: Schema.optionalKey(Schema.Boolean), children: Schema.optionalKey( Schema.Array( Schema.suspend((): Schema.Codec => ContextMenuItemSchema), diff --git a/packages/contracts/src/keybindings.test.ts b/packages/contracts/src/keybindings.test.ts index 14579c741198..dee46551c224 100644 --- a/packages/contracts/src/keybindings.test.ts +++ b/packages/contracts/src/keybindings.test.ts @@ -78,6 +78,12 @@ it.effect("parses keybinding rules", () => }); assert.strictEqual(parsedProjectSearch.command, "projectSearch.toggle"); + const parsedUsageOpen = yield* decode(KeybindingRule, { + key: "mod+u", + command: "usage.open", + }); + assert.strictEqual(parsedUsageOpen.command, "usage.open"); + const parsedThemeEditor = yield* decode(KeybindingRule, { key: "mod+alt+shift+t", command: "themeEditor.toggle", diff --git a/packages/contracts/src/keybindings.ts b/packages/contracts/src/keybindings.ts index 75dc89195969..cb5768420306 100644 --- a/packages/contracts/src/keybindings.ts +++ b/packages/contracts/src/keybindings.ts @@ -78,6 +78,7 @@ export const STATIC_KEYBINDING_COMMANDS = [ "commandPalette.toggle", "filePicker.toggle", "projectSearch.toggle", + "usage.open", "theme.select", "appearance.cycle", "themeEditor.toggle", diff --git a/packages/contracts/src/orchestration.ts b/packages/contracts/src/orchestration.ts index 82117aecd1d1..56cc3dcf7fd5 100644 --- a/packages/contracts/src/orchestration.ts +++ b/packages/contracts/src/orchestration.ts @@ -839,6 +839,10 @@ export const OrchestrationThread = Schema.Struct({ // Manual Active placement. Keyless threads retain their creation/re-entry // order above the arranged run. Settling clears this slot. activeOrderKey: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), + // Set while the user has turned automatic settlement off for this thread. + // Survives manual settle, un-settle, and activity: only the user clears it. + // Optional so payloads from older servers still decode. + autoSettleDisabledAt: Schema.optional(Schema.NullOr(IsoDateTime)), // Pending-only state. Optional so older servers remain compatible. titleRegeneration: Schema.optional(Schema.NullOr(ThreadTitleRegeneration)), titleState: Schema.optional(Schema.NullOr(ThreadTitleState)), @@ -910,6 +914,7 @@ export const OrchestrationThreadShell = Schema.Struct({ pinnedAt: Schema.optional(Schema.NullOr(IsoDateTime)), pinOrderKey: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), activeOrderKey: Schema.optional(Schema.NullOr(TrimmedNonEmptyString)), + autoSettleDisabledAt: Schema.optional(Schema.NullOr(IsoDateTime)), titleRegeneration: Schema.optional(Schema.NullOr(ThreadTitleRegeneration)), titleState: Schema.optional(Schema.NullOr(ThreadTitleState)), session: Schema.NullOr(OrchestrationSession), @@ -1221,6 +1226,14 @@ const ThreadPinReorderCommand = Schema.Struct({ orderKey: TrimmedNonEmptyString, }); +const ThreadAutoSettleSetCommand = Schema.Struct({ + type: Schema.Literal("thread.auto-settle.set"), + commandId: CommandId, + threadId: ThreadId, + // false turns automatic settlement off for this thread, true turns it back on. + enabled: Schema.Boolean, +}); + const ThreadActiveReorderCommand = Schema.Struct({ type: Schema.Literal("thread.active.reorder"), commandId: CommandId, @@ -1430,6 +1443,7 @@ const DispatchableClientOrchestrationCommand = Schema.Union([ ThreadPinCommand, ThreadUnpinCommand, ThreadPinReorderCommand, + ThreadAutoSettleSetCommand, ThreadActiveReorderCommand, ThreadMetaUpdateCommand, ThreadPullRequestLinkCommand, @@ -1463,6 +1477,7 @@ export const ClientOrchestrationCommand = Schema.Union([ ThreadPinCommand, ThreadUnpinCommand, ThreadPinReorderCommand, + ThreadAutoSettleSetCommand, ThreadActiveReorderCommand, ThreadMetaUpdateCommand, ThreadPullRequestLinkCommand, @@ -1691,6 +1706,7 @@ export const OrchestrationEventType = Schema.Literals([ "thread.pinned", "thread.unpinned", "thread.pin-reordered", + "thread.auto-settle-set", "thread.meta-updated", "thread.pull-request-linked", "thread.pull-request-unlinked", @@ -1830,6 +1846,13 @@ export const ThreadPinReorderedPayload = Schema.Struct({ updatedAt: IsoDateTime, }); +export const ThreadAutoSettleSetPayload = Schema.Struct({ + threadId: ThreadId, + // Null re-enables automatic settlement. + autoSettleDisabledAt: Schema.NullOr(IsoDateTime), + updatedAt: IsoDateTime, +}); + export const ThreadMetaUpdatedPayload = Schema.Struct({ threadId: ThreadId, // Order updates use this existing event so older clients can ignore the @@ -2094,6 +2117,11 @@ export const OrchestrationEvent = Schema.Union([ type: Schema.Literal("thread.pin-reordered"), payload: ThreadPinReorderedPayload, }), + Schema.Struct({ + ...EventBaseFields, + type: Schema.Literal("thread.auto-settle-set"), + payload: ThreadAutoSettleSetPayload, + }), Schema.Struct({ ...EventBaseFields, type: Schema.Literal("thread.meta-updated"), diff --git a/packages/contracts/src/previewAutomation.ts b/packages/contracts/src/previewAutomation.ts index 431e83189d22..f87739670f75 100644 --- a/packages/contracts/src/previewAutomation.ts +++ b/packages/contracts/src/previewAutomation.ts @@ -649,6 +649,7 @@ const McpCapabilityErrorFields = { providerInstanceId: ProviderInstanceId, }; +/** Agents read this message, so it names the next step and not only the failure. */ export class PreviewAutomationUnavailableError extends Schema.TaggedError()( "PreviewAutomationUnavailableError", { @@ -657,7 +658,7 @@ export class PreviewAutomationUnavailableError extends Schema.TaggedError { it("round-trips through the JSON codec the RPC serializes with", () => { const codec = Schema.toCodecJson(PullRequestListResult); - const decoded = Schema.decodeUnknownSync(codec)(Schema.encodeUnknownSync(codec)(LIST_RESULT)); + const decoded = Schema.decodeSync(codec)(Schema.encodeUnknownSync(codec)(LIST_RESULT)); expect(decoded).toStrictEqual(LIST_RESULT); }); diff --git a/packages/contracts/src/relay.ts b/packages/contracts/src/relay.ts index 8b6562497681..8b1e310b8b8d 100644 --- a/packages/contracts/src/relay.ts +++ b/packages/contracts/src/relay.ts @@ -186,6 +186,34 @@ export const RelayManagedEndpointRuntimeConfig = Schema.Struct({ }); export type RelayManagedEndpointRuntimeConfig = typeof RelayManagedEndpointRuntimeConfig.Type; +export const RelayManagedEndpointRecoveryRequest = Schema.Struct({ + cloudUserId: TrimmedNonEmptyString, + origin: RelayManagedEndpointOrigin, + proof: TrimmedNonEmptyString, +}); +export type RelayManagedEndpointRecoveryRequest = typeof RelayManagedEndpointRecoveryRequest.Type; + +export const RelayManagedEndpointRecoveryRegistrationRequest = Schema.Struct({ + cloudUserId: TrimmedNonEmptyString, + tunnelId: TrimmedNonEmptyString, + origin: RelayManagedEndpointOrigin, + proof: TrimmedNonEmptyString, +}); +export type RelayManagedEndpointRecoveryRegistrationRequest = + typeof RelayManagedEndpointRecoveryRegistrationRequest.Type; + +export const RelayManagedEndpointRecoveryRegistrationResponse = Schema.Struct({ + status: Schema.Literals(["ready", "recovery_required"]), +}); +export type RelayManagedEndpointRecoveryRegistrationResponse = + typeof RelayManagedEndpointRecoveryRegistrationResponse.Type; + +export const RelayManagedEndpointRecoveryResponse = Schema.Struct({ + endpoint: RelayManagedEndpoint, + endpointRuntime: RelayManagedEndpointRuntimeConfig, +}); +export type RelayManagedEndpointRecoveryResponse = typeof RelayManagedEndpointRecoveryResponse.Type; + export const RelayLinkProofRequest = Schema.Struct({ challenge: Schema.String, relayIssuer: Schema.String, @@ -213,6 +241,26 @@ const RelaySignedJwtRegisteredClaims = { exp: Schema.Int, } as const; +export const RelayManagedEndpointRecoveryProofPayload = Schema.Union([ + Schema.Struct({ + ...RelaySignedJwtRegisteredClaims, + action: Schema.Literal("register"), + environmentId: EnvironmentId, + cloudUserId: TrimmedNonEmptyString, + tunnelId: TrimmedNonEmptyString, + origin: RelayManagedEndpointOrigin, + }), + Schema.Struct({ + ...RelaySignedJwtRegisteredClaims, + action: Schema.Literal("recover"), + environmentId: EnvironmentId, + cloudUserId: TrimmedNonEmptyString, + origin: RelayManagedEndpointOrigin, + }), +]); +export type RelayManagedEndpointRecoveryProofPayload = + typeof RelayManagedEndpointRecoveryProofPayload.Type; + export const RelayAgentActivityPublishProofPayload = Schema.Struct({ ...RelaySignedJwtRegisteredClaims, environmentId: EnvironmentId, @@ -1090,6 +1138,26 @@ const RelayDpopClientGroup = HttpApiGroup.make("dpopClient") const RelayServerGroup = HttpApiGroup.make("server") .add( + HttpApiEndpoint.post( + "registerManagedEndpointRecovery", + "/v1/environments/:environmentId/tunnel/recovery", + { + params: Schema.Struct({ + environmentId: EnvironmentId, + }), + payload: RelayManagedEndpointRecoveryRegistrationRequest, + success: RelayManagedEndpointRecoveryRegistrationResponse, + error: RelayAuthAndInternalErrors, + }, + ).annotate(OpenApi.Summary, "Register managed tunnel recovery without provisioning"), + HttpApiEndpoint.post("recoverManagedEndpoint", "/v1/environments/:environmentId/tunnel", { + params: Schema.Struct({ + environmentId: EnvironmentId, + }), + payload: RelayManagedEndpointRecoveryRequest, + success: RelayManagedEndpointRecoveryResponse, + error: RelayAuthAndInternalErrors, + }).annotate(OpenApi.Summary, "Recover an environment's managed tunnel"), HttpApiEndpoint.post( "publishAgentActivity", "/v1/environments/:environmentId/threads/:threadId/agent-activity", diff --git a/packages/contracts/src/rpc.test.ts b/packages/contracts/src/rpc.test.ts index 6a9adebd85d6..bdc3448b8563 100644 --- a/packages/contracts/src/rpc.test.ts +++ b/packages/contracts/src/rpc.test.ts @@ -13,19 +13,19 @@ import { WsSubscribeServerConfigRpc } from "./rpc.ts"; describe("subscribeServerConfig payload compatibility", () => { it("is accepted by a server whose schema predates the field", () => { const oldServerPayload = Schema.Struct({}); - const decoded = Schema.decodeUnknownExit(oldServerPayload)({ environmentThemes: true }); + const decoded = Schema.decodeExit(oldServerPayload)({ environmentThemes: true }); expect(Exit.isSuccess(decoded)).toBe(true); }); it("is carried by a server that declares it", () => { - const decoded = Schema.decodeUnknownSync(WsSubscribeServerConfigRpc.payloadSchema)({ + const decoded = Schema.decodeSync(WsSubscribeServerConfigRpc.payloadSchema)({ environmentThemes: true, }); expect(decoded).toEqual({ environmentThemes: true }); }); it("stays optional, so a client that never sends it still subscribes", () => { - const decoded = Schema.decodeUnknownSync(WsSubscribeServerConfigRpc.payloadSchema)({}); + const decoded = Schema.decodeSync(WsSubscribeServerConfigRpc.payloadSchema)({}); expect(decoded).toEqual({}); }); }); diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index ef640f6d349b..73ae483e39de 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -1090,9 +1090,7 @@ export type StorageCleanupSettings = typeof StorageCleanupSettings.Type; export const ServerSettings = Schema.Struct({ worktreeCleanup: WorktreeCleanup.pipe(Schema.withDecodingDefault(Effect.succeed(null))), storageCleanup: StorageCleanupSettings.pipe( - Schema.withDecodingDefault( - Effect.succeed(Schema.decodeUnknownSync(StorageCleanupSettings)({})), - ), + Schema.withDecodingDefault(Effect.succeed(Schema.decodeSync(StorageCleanupSettings)({}))), ), // How assistant text reaches clients during a turn. Deliberately a fresh // key (was `enableLegacyTokenStreaming`, before that @@ -1287,6 +1285,10 @@ export const ServerSettings = Schema.Struct({ usageLimitSources: Schema.Record(UsageLimitSourceId, UsageLimitSourceConfig).pipe( Schema.withDecodingDefault(Effect.succeed({})), ), + /** Allows this server to read the Cursor CLI's macOS Keychain login for account usage. */ + cursorKeychainUsageEnabled: Schema.Boolean.pipe( + Schema.withDecodingDefault(Effect.succeed(false)), + ), /** Exact model IDs, applied to past and future usage on this environment. */ usagePriceOverrides: Schema.Record(TrimmedNonEmptyString, UsageModelPriceOverride).pipe( Schema.withDecodingDefault(Effect.succeed({})), @@ -1561,6 +1563,7 @@ export const ServerSettingsPatch = Schema.Struct({ usageLimitSources: Schema.optionalKey( Schema.Record(UsageLimitSourceId, Schema.NullOr(UsageLimitSourceConfig)), ), + cursorKeychainUsageEnabled: Schema.optionalKey(Schema.Boolean), /** Each entry replaces one model's rates; `null` restores automatic pricing. */ usagePriceOverrides: Schema.optionalKey( Schema.Record(TrimmedNonEmptyString, Schema.NullOr(UsageModelPriceOverride)), diff --git a/packages/contracts/src/usage.ts b/packages/contracts/src/usage.ts index 29fb75bf949c..a6431330558a 100644 --- a/packages/contracts/src/usage.ts +++ b/packages/contracts/src/usage.ts @@ -1,13 +1,10 @@ /** * Usage reporting contract. * - * Each environment scans the provider CLIs' own on-disk session transcripts - * (`~/.claude/projects/**\/*.jsonl`, `~/.codex/sessions/**\/*.jsonl`, - * `~/.grok/sessions/**\/updates.jsonl`) rather than relying on T3 Code's own - * orchestration projections, so usage stays complete even for turns that were - * never driven through T3 Code. This mirrors the approach `ccusage` takes. + * Each environment scans native session files and databases, including work + * driven outside T3 Code. Source status describes gaps in local coverage. * - * Environments return pre-aggregated `(day, hourStart?, provider, model)` + * Environments return pre-aggregated `(day, hourStart?, provider, model, sourcePath?)` * buckets. Raw transcript records never cross the wire. * * @module usage @@ -21,18 +18,24 @@ import { NonNegativeInt, TrimmedNonEmptyString } from "./baseSchemas.ts"; * client renders partial coverage when an environment reports an older version * rather than failing the whole page. */ -export const USAGE_CONTRACT_VERSION = 5 as const; +export const USAGE_CONTRACT_VERSION = 6 as const; /** * Oldest {@link UsageSummary} version a current client will still merge. * - * v5 only adds `grok` to {@link UsageProviderKind}; v4 Claude/Codex buckets - * remain valid, so mixed-version environments keep those totals instead of - * treating every older server as stale. + * v5/v6 add providers and optional source attribution; v4 Claude/Codex + * buckets remain valid in mixed-version environments. */ export const USAGE_MERGE_COMPATIBLE_SINCE = 4 as const; -export const UsageProviderKind = Schema.Literals(["claude", "codex", "grok"]); +export const UsageProviderKind = Schema.Literals([ + "claude", + "codex", + "grok", + "cursor", + "opencode", + "antigravity", +]); export type UsageProviderKind = typeof UsageProviderKind.Type; /** @@ -93,6 +96,8 @@ export const UsageBucket = Schema.Struct({ hourStart: Schema.optional(TrimmedNonEmptyString), provider: UsageProviderKind, model: TrimmedNonEmptyString, + /** Source directory, so overlapping multi-home environments merge once per source. */ + sourcePath: Schema.optional(TrimmedNonEmptyString), totals: UsageTokenTotals, costUsd: Schema.Number, /** @@ -151,6 +156,8 @@ export const UsageSource = Schema.Struct({ */ distinctSessions: NonNegativeInt, message: Schema.NullOr(TrimmedNonEmptyString), + /** An action the client can offer to make this source available. */ + action: Schema.optionalKey(Schema.Literal("enableCursorKeychain")), }); export type UsageSource = typeof UsageSource.Type; diff --git a/packages/effect-acp/src/client.ts b/packages/effect-acp/src/client.ts index 27203c20c627..c3f4e7644fc9 100644 --- a/packages/effect-acp/src/client.ts +++ b/packages/effect-acp/src/client.ts @@ -346,11 +346,9 @@ export const make = Effect.fn("effect-acp/AcpClient.make")(function* ( registration: BufferedNotificationHandler, notification: A, ) => - Effect.forEach( - registration.handlers, - (handler) => handler(notification).pipe(Effect.catch(() => Effect.void)), - { discard: true }, - ); + Effect.forEach(registration.handlers, (handler) => handler(notification).pipe(Effect.ignore), { + discard: true, + }); const flushBufferedNotifications = (registration: BufferedNotificationHandler) => Effect.suspend(() => { diff --git a/packages/effect-acp/src/protocol.ts b/packages/effect-acp/src/protocol.ts index f74a8ea29e37..a84c2f5a57b4 100644 --- a/packages/effect-acp/src/protocol.ts +++ b/packages/effect-acp/src/protocol.ts @@ -216,7 +216,7 @@ export const makeAcpPatchedProtocol = Effect.fn("makeAcpPatchedProtocol")(functi Queue.offer(notificationQueue, notification).pipe( Effect.andThen( options.onNotification - ? options.onNotification(notification).pipe(Effect.catch(() => Effect.void)) + ? options.onNotification(notification).pipe(Effect.ignore) : Effect.void, ), Effect.asVoid, diff --git a/packages/effect-codex-app-server/scripts/generate.ts b/packages/effect-codex-app-server/scripts/generate.ts index 48509db8cc21..b2696097ef8c 100644 --- a/packages/effect-codex-app-server/scripts/generate.ts +++ b/packages/effect-codex-app-server/scripts/generate.ts @@ -17,7 +17,7 @@ import { } from "effect/unstable/http"; import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process"; -const UPSTREAM_REF = "678157acaa819d5510adfe359abb5d0392cfe461"; +const UPSTREAM_REF = "fe74a774532af67b5a4a3dec03ce9469e17f89af"; 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"; @@ -145,112 +145,6 @@ const ManualSchemas: Record = { }, }; -// Codex 0.150 added these multi-agent values before our next full protocol -// refresh. Keep every generated response namespace compatible with them. -const Codex0150DefinitionSchemas: Record = { - CollabAgentTool: { - type: "string", - enum: [ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ], - }, - CollabAgentToolCallStatus: { - type: "string", - enum: ["inProgress", "completed", "failed", "interrupted"], - }, - PlanType: { - type: "string", - enum: [ - "free", - "go", - "plus", - "pro", - "prolite", - "team", - "self_serve_business_prolite", - "self_serve_business_usage_based", - "business", - "ent26", - "enterprise_cbp_automation", - "enterprise_cbp_usage_based", - "enterprise", - "edu", - "edu_plus", - "edu_pro", - "unknown", - ], - }, - SubAgentActivityKind: { - type: "string", - enum: ["started", "interacted", "interrupted", "completed"], - }, -}; - -// Pinned protocol JSON omits later CodexErrorInfo variants. Keep historical -// thread payloads decodable; do not fold unknown values into "other". -const CodexErrorInfoCompatibilityValues = [ - "rateLimitExceeded", - "misalignmentPolicyViolation", -] as const; - -const CodexErrorInfoCompatibilityExports = new Set([ - "V2ThreadReadResponse", - "V2ThreadResumeResponse", - "V2ThreadRollbackResponse", - "V2ThreadForkResponse", - "V2TurnCompletedNotification", -]); - -function applyCodex0151DefinitionCompatibility( - exportName: string, - definitionName: string, - definitionSchema: Schema.Json, -): Schema.Json { - if ( - !CodexErrorInfoCompatibilityExports.has(exportName) || - definitionName !== "CodexErrorInfo" || - typeof definitionSchema !== "object" - ) { - return definitionSchema; - } - - const schema = definitionSchema as { - readonly oneOf?: ReadonlyArray<{ readonly enum?: ReadonlyArray }>; - }; - const [firstVariant, ...remainingVariants] = schema.oneOf ?? []; - const currentEnum = firstVariant?.enum; - if (!currentEnum) { - return definitionSchema; - } - - const missingValues = CodexErrorInfoCompatibilityValues.filter( - (value) => !currentEnum.includes(value), - ); - if (missingValues.length === 0) { - return definitionSchema; - } - - const enumValues = [...currentEnum]; - const otherIndex = enumValues.indexOf("other"); - const nextEnum = - otherIndex === -1 - ? [...enumValues, ...missingValues] - : [...enumValues.slice(0, otherIndex), ...missingValues, ...enumValues.slice(otherIndex)]; - - return { - ...definitionSchema, - oneOf: [{ ...firstVariant, enum: nextEnum }, ...remainingVariants], - }; -} - const getGeneratedPaths = Effect.fn("getGeneratedPaths")(function* () { const path = yield* Path.Path; const generatedDir = path.join(import.meta.dirname, "..", "src", "_generated"); @@ -269,8 +163,13 @@ const ensureGeneratedDir = Effect.fn("ensureGeneratedDir")(function* () { }); const fetchText = Effect.fn("fetchText")(function* (url: string) { + // Unauthenticated GitHub API calls are capped at 60/hour; set GITHUB_TOKEN to lift that. + const token = process.env.GITHUB_TOKEN; return yield* HttpClientRequest.get(url).pipe( HttpClientRequest.setHeader("user-agent", USER_AGENT), + token && url.startsWith("https://api.github.com/") + ? HttpClientRequest.bearerToken(token) + : (request) => request, HttpClient.execute, Effect.flatMap(HttpClientResponse.filterStatusOk), Effect.flatMap((okResponse) => okResponse.text), @@ -387,59 +286,82 @@ function stripNullDefaults(value: Schema.Json): Schema.Json { ) as Schema.Json; } -// Codex 0.153 adds async questions to agent messages. Keep older protocol -// fields until the next full refresh, including every thread history namespace. -function addAsyncQuestionFields(value: Schema.Json): Schema.Json { - if (Array.isArray(value)) { - return value.map(addAsyncQuestionFields); - } - if (value === null || typeof value !== "object") { +type JsonSchemaNode = { readonly [key: string]: Schema.Json }; + +function isJsonSchemaNode(value: Schema.Json | undefined): value is JsonSchemaNode { + return value !== null && typeof value === "object" && !Array.isArray(value); +} + +// Adapts Codex's JSON Schema to Effect's importer, visiting only schema +// positions so a field literally named "properties" is left alone: +// - Effect imports an object without additionalProperties as an open record. +// Codex omits it for plain structs, so close objects that list properties. +// - Effect cannot intersect shared object fields with object alternatives, +// which Codex uses for "one of these keys plus shared fields" +// (image_url | file_id). Fold the shared fields into each alternative. +function adaptSchemaForEffect(value: Schema.Json): Schema.Json { + if (!isJsonSchemaNode(value)) { return value; } - const properties = "properties" in value ? value.properties : undefined; - const itemType = - properties && typeof properties === "object" && "type" in properties - ? properties.type - : undefined; + const node: Record = { ...value }; + for (const key of ["items", "additionalProperties"]) { + const child = node[key]; + if (isJsonSchemaNode(child)) node[key] = adaptSchemaForEffect(child); + } + for (const key of ["anyOf", "oneOf", "allOf"]) { + const child = node[key]; + if (Array.isArray(child)) node[key] = child.map(adaptSchemaForEffect); + } + for (const key of ["properties", "definitions"]) { + const child = node[key]; + if (isJsonSchemaNode(child)) { + node[key] = Object.fromEntries( + Object.entries(child).map(([name, schema]) => [name, adaptSchemaForEffect(schema)]), + ); + } + } + + const { properties } = node; if ( - properties && - typeof properties === "object" && - itemType && - typeof itemType === "object" && - "enum" in itemType && - Array.isArray(itemType.enum) && - itemType.enum.includes("agentMessage") + isJsonSchemaNode(properties) && + Object.keys(properties).length > 0 && + !("additionalProperties" in node) ) { - return { - ...value, - properties: { - ...Object.fromEntries(Object.entries(properties).filter(([key]) => key !== "type")), - delivery: { anyOf: [{ type: "string", enum: ["async"] }, { type: "null" }] }, - questions: { - anyOf: [ - { - type: "array", - items: { - type: "object", - properties: { - title: { type: "string" }, - options: { - anyOf: [{ type: "array", items: { type: "string" } }, { type: "null" }], - }, - }, - required: ["title"], - }, - }, - { type: "null" }, - ], - }, - type: itemType, - }, - }; + node.additionalProperties = false; } - return Object.fromEntries( - Object.entries(value).map(([key, child]) => [key, addAsyncQuestionFields(child)]), - ); + + const alternativesKey = "anyOf" in node ? "anyOf" : "oneOf"; + const alternatives = node[alternativesKey]; + if ( + !isJsonSchemaNode(properties) || + !Array.isArray(alternatives) || + !alternatives.every( + (alternative) => isJsonSchemaNode(alternative) && alternative.type === "object", + ) + ) { + return node; + } + const { + properties: _shared, + required, + type: _type, + additionalProperties: _closed, + ...rest + } = node; + const sharedRequired = Array.isArray(required) ? required : []; + return { + ...rest, + [alternativesKey]: alternatives.map((alternative) => { + const branch = alternative as JsonSchemaNode; + const branchProperties = isJsonSchemaNode(branch.properties) ? branch.properties : {}; + const branchRequired = Array.isArray(branch.required) ? branch.required : []; + return { + ...branch, + properties: { ...properties, ...branchProperties }, + required: [...new Set([...sharedRequired, ...branchRequired])], + }; + }), + }; } function toPascalCaseMethod(method: string) { @@ -453,13 +375,14 @@ function toPascalCaseMethod(method: string) { } function parseRequestEntries(fileContents: string): ReadonlyArray { - const entryPattern = /\{\s*"method":\s*"([^"]+)",\s*id:\s*RequestId,\s*params:\s*([^,}]+)/g; + // Optional params render as `params?: Foo | undefined`; their JSON schema is `NullableFoo`. + const entryPattern = /\{\s*"method":\s*"([^"]+)",\s*id:\s*RequestId,\s*params(\??):\s*([^,}|]+)/g; const entries: Array = []; let match: RegExpExecArray | null; while ((match = entryPattern.exec(fileContents)) !== null) { entries.push({ method: match[1]!, - paramsType: match[2]!.trim(), + paramsType: `${match[2] ? "Nullable" : ""}${match[3]!.trim()}`, }); } return entries; @@ -649,7 +572,7 @@ function rewriteExternalRefs( const definitionName = child.slice("#/definitions/".length); const localRewrite = localDefinitionNames.get(definitionName); if (localRewrite) { - return [key, `#/definitions/${localRewrite}`]; + return [key, `#/components/schemas/${localRewrite}`]; } const candidates = [ @@ -670,7 +593,7 @@ function rewriteExternalRefs( throw new Error(`Missing rewritten definition for ref: ${child}`); } - return [key, `#/definitions/${rewritten}`]; + return [key, `#/components/schemas/${rewritten}`]; } return [ @@ -717,13 +640,10 @@ const generateFiles = Effect.fn("generateFiles")(function* () { ); for (const [definitionName, definitionSchema] of Object.entries(parsed.definitions ?? {})) { - const compatibleDefinitionSchema = - Codex0150DefinitionSchemas[definitionName] ?? - applyCodex0151DefinitionCompatibility(file.exportName, definitionName, definitionSchema); aggregateSchemas[localDefinitionNames.get(definitionName)!] = stripNullDefaults( normalizeNullableTypes( rewriteExternalRefs( - compatibleDefinitionSchema, + definitionSchema, localDefinitionNames, file.namespace, exportNameByQualifiedName, @@ -761,7 +681,7 @@ const generateFiles = Effect.fn("generateFiles")(function* () { for (const [name, schema] of Object.entries(aggregateSchemas).toSorted(([left], [right]) => left.localeCompare(right), )) { - aggregateSchemas[name] = addAsyncQuestionFields(schema); + aggregateSchemas[name] = adaptSchemaForEffect(schema); generator.addSchema(name, aggregateSchemas[name] as never); } 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 24452e881f66..88ffb842ac9b 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: 678157acaa819d5510adfe359abb5d0392cfe461 +// Upstream protocol ref: fe74a774532af67b5a4a3dec03ce9469e17f89af import * as CodexSchema from "./schema.gen.ts"; @@ -16,14 +16,24 @@ export const CLIENT_REQUEST_METHODS = { "thread/goal/get": "thread/goal/get", "thread/goal/clear": "thread/goal/clear", "thread/metadata/update": "thread/metadata/update", + "thread/attachment/add": "thread/attachment/add", + "thread/attachment/list": "thread/attachment/list", + "thread/attachment/remove": "thread/attachment/remove", + "thread/section/move": "thread/section/move", "thread/unarchive": "thread/unarchive", "thread/compact/start": "thread/compact/start", "thread/shellCommand": "thread/shellCommand", "thread/approveGuardianDeniedAction": "thread/approveGuardianDeniedAction", - "thread/rollback": "thread/rollback", + "thread/revert": "thread/revert", "thread/list": "thread/list", + "threadSection/list": "threadSection/list", + "threadSection/create": "threadSection/create", + "threadSection/update": "threadSection/update", + "threadSection/delete": "threadSection/delete", "thread/loaded/list": "thread/loaded/list", "thread/read": "thread/read", + "thread/turns/list": "thread/turns/list", + "thread/items/list": "thread/items/list", "thread/inject_items": "thread/inject_items", "skills/list": "skills/list", "skills/extraRoots/set": "skills/extraRoots/set", @@ -33,6 +43,7 @@ export const CLIENT_REQUEST_METHODS = { "marketplace/upgrade": "marketplace/upgrade", "plugin/list": "plugin/list", "plugin/installed": "plugin/installed", + "plugin/reconcile": "plugin/reconcile", "plugin/read": "plugin/read", "plugin/skill/read": "plugin/skill/read", "plugin/share/save": "plugin/share/save", @@ -87,6 +98,7 @@ export const CLIENT_REQUEST_METHODS = { "config/read": "config/read", "externalAgentConfig/detect": "externalAgentConfig/detect", "externalAgentConfig/import": "externalAgentConfig/import", + "externalAgentConfig/import/recordHistory": "externalAgentConfig/import/recordHistory", "externalAgentConfig/import/readHistories": "externalAgentConfig/import/readHistories", "config/value/write": "config/value/write", "config/batchWrite": "config/batchWrite", @@ -123,10 +135,15 @@ export const SERVER_NOTIFICATION_METHODS = { "thread/deleted": "thread/deleted", "thread/unarchived": "thread/unarchived", "thread/closed": "thread/closed", + "thread/reverted": "thread/reverted", "skills/changed": "skills/changed", "thread/name/updated": "thread/name/updated", + "thread/attachment/updated": "thread/attachment/updated", "thread/goal/updated": "thread/goal/updated", "thread/goal/cleared": "thread/goal/cleared", + "thread/queue/changed": "thread/queue/changed", + "project/changed": "project/changed", + "thread/project/updated": "thread/project/updated", "thread/environment/connected": "thread/environment/connected", "thread/environment/disconnected": "thread/environment/disconnected", "thread/settings/updated": "thread/settings/updated", @@ -140,6 +157,7 @@ export const SERVER_NOTIFICATION_METHODS = { "item/started": "item/started", "item/autoApprovalReview/started": "item/autoApprovalReview/started", "item/autoApprovalReview/completed": "item/autoApprovalReview/completed", + "autoApprovalReview/strictReviewRequired": "autoApprovalReview/strictReviewRequired", "item/completed": "item/completed", "rawResponseItem/completed": "rawResponseItem/completed", "rawResponse/completed": "rawResponse/completed", @@ -156,6 +174,7 @@ export const SERVER_NOTIFICATION_METHODS = { "item/mcpToolCall/progress": "item/mcpToolCall/progress", "mcpServer/oauthLogin/completed": "mcpServer/oauthLogin/completed", "mcpServer/startupStatus/updated": "mcpServer/startupStatus/updated", + "mcpServer/event/stream/notification": "mcpServer/event/stream/notification", "account/updated": "account/updated", "account/rateLimits/updated": "account/rateLimits/updated", "app/list/updated": "app/list/updated", @@ -169,6 +188,8 @@ export const SERVER_NOTIFICATION_METHODS = { "thread/compacted": "thread/compacted", "model/rerouted": "model/rerouted", "model/verification": "model/verification", + "modelProvider/authRecoveryStarted": "modelProvider/authRecoveryStarted", + "modelProvider/authRecoveryCompleted": "modelProvider/authRecoveryCompleted", "turn/moderationMetadata": "turn/moderationMetadata", "model/safetyBuffering/updated": "model/safetyBuffering/updated", warning: "warning", @@ -179,6 +200,9 @@ export const SERVER_NOTIFICATION_METHODS = { "fuzzyFileSearch/sessionCompleted": "fuzzyFileSearch/sessionCompleted", "thread/realtime/started": "thread/realtime/started", "thread/realtime/itemAdded": "thread/realtime/itemAdded", + "thread/realtime/item/started": "thread/realtime/item/started", + "thread/realtime/item/transcript/delta": "thread/realtime/item/transcript/delta", + "thread/realtime/item/completed": "thread/realtime/item/completed", "thread/realtime/transcript/delta": "thread/realtime/transcript/delta", "thread/realtime/transcript/done": "thread/realtime/transcript/done", "thread/realtime/outputAudio/delta": "thread/realtime/outputAudio/delta", @@ -208,14 +232,24 @@ export interface ClientRequestParamsByMethod { readonly "thread/goal/get": CodexSchema.V2ThreadGoalGetParams; readonly "thread/goal/clear": CodexSchema.V2ThreadGoalClearParams; readonly "thread/metadata/update": CodexSchema.V2ThreadMetadataUpdateParams; + readonly "thread/attachment/add": CodexSchema.V2ThreadAttachmentAddParams; + readonly "thread/attachment/list": CodexSchema.V2ThreadAttachmentListParams; + readonly "thread/attachment/remove": CodexSchema.V2ThreadAttachmentRemoveParams; + readonly "thread/section/move": CodexSchema.V2ThreadSectionMoveParams; readonly "thread/unarchive": CodexSchema.V2ThreadUnarchiveParams; readonly "thread/compact/start": CodexSchema.V2ThreadCompactStartParams; readonly "thread/shellCommand": CodexSchema.V2ThreadShellCommandParams; readonly "thread/approveGuardianDeniedAction": CodexSchema.V2ThreadApproveGuardianDeniedActionParams; - readonly "thread/rollback": CodexSchema.V2ThreadRollbackParams; + readonly "thread/revert": CodexSchema.V2ThreadRevertParams; readonly "thread/list": CodexSchema.V2ThreadListParams; + readonly "threadSection/list": CodexSchema.V2ThreadSectionListParams; + readonly "threadSection/create": CodexSchema.V2ThreadSectionCreateParams; + readonly "threadSection/update": CodexSchema.V2ThreadSectionUpdateParams; + readonly "threadSection/delete": CodexSchema.V2ThreadSectionDeleteParams; readonly "thread/loaded/list": CodexSchema.V2ThreadLoadedListParams; readonly "thread/read": CodexSchema.V2ThreadReadParams; + readonly "thread/turns/list": CodexSchema.V2ThreadTurnsListParams; + readonly "thread/items/list": CodexSchema.V2ThreadItemsListParams; readonly "thread/inject_items": CodexSchema.V2ThreadInjectItemsParams; readonly "skills/list": CodexSchema.V2SkillsListParams; readonly "skills/extraRoots/set": CodexSchema.V2SkillsExtraRootsSetParams; @@ -225,6 +259,7 @@ export interface ClientRequestParamsByMethod { readonly "marketplace/upgrade": CodexSchema.V2MarketplaceUpgradeParams; readonly "plugin/list": CodexSchema.V2PluginListParams; readonly "plugin/installed": CodexSchema.V2PluginInstalledParams; + readonly "plugin/reconcile": CodexSchema.V2PluginReconcileParams; readonly "plugin/read": CodexSchema.V2PluginReadParams; readonly "plugin/skill/read": CodexSchema.V2PluginSkillReadParams; readonly "plugin/share/save": CodexSchema.V2PluginShareSaveParams; @@ -266,9 +301,9 @@ export interface ClientRequestParamsByMethod { readonly "account/login/start": CodexSchema.V2LoginAccountParams; readonly "account/login/cancel": CodexSchema.V2CancelLoginAccountParams; readonly "account/logout": undefined; - readonly "account/rateLimits/read": undefined; + readonly "account/rateLimits/read": CodexSchema.V2NullableGetAccountRateLimitsParams; readonly "account/rateLimitResetCredit/consume": CodexSchema.V2ConsumeAccountRateLimitResetCreditParams; - readonly "account/usage/read": undefined; + readonly "account/usage/read": CodexSchema.V2NullableGetAccountTokenUsageParams; readonly "account/workspaceMessages/read": undefined; readonly "account/sendAddCreditsNudgeEmail": CodexSchema.V2SendAddCreditsNudgeEmailParams; readonly "feedback/upload": CodexSchema.V2FeedbackUploadParams; @@ -279,6 +314,7 @@ export interface ClientRequestParamsByMethod { readonly "config/read": CodexSchema.V2ConfigReadParams; readonly "externalAgentConfig/detect": CodexSchema.V2ExternalAgentConfigDetectParams; readonly "externalAgentConfig/import": CodexSchema.V2ExternalAgentConfigImportParams; + readonly "externalAgentConfig/import/recordHistory": CodexSchema.V2ExternalAgentConfigImportHistoryRecordParams; readonly "externalAgentConfig/import/readHistories": undefined; readonly "config/value/write": CodexSchema.V2ConfigValueWriteParams; readonly "config/batchWrite": CodexSchema.V2ConfigBatchWriteParams; @@ -303,14 +339,24 @@ export interface ClientRequestResponsesByMethod { readonly "thread/goal/get": CodexSchema.V2ThreadGoalGetResponse; readonly "thread/goal/clear": CodexSchema.V2ThreadGoalClearResponse; readonly "thread/metadata/update": CodexSchema.V2ThreadMetadataUpdateResponse; + readonly "thread/attachment/add": CodexSchema.V2ThreadAttachmentAddResponse; + readonly "thread/attachment/list": CodexSchema.V2ThreadAttachmentListResponse; + readonly "thread/attachment/remove": CodexSchema.V2ThreadAttachmentRemoveResponse; + readonly "thread/section/move": CodexSchema.V2ThreadSectionMoveResponse; readonly "thread/unarchive": CodexSchema.V2ThreadUnarchiveResponse; readonly "thread/compact/start": CodexSchema.V2ThreadCompactStartResponse; readonly "thread/shellCommand": CodexSchema.V2ThreadShellCommandResponse; readonly "thread/approveGuardianDeniedAction": CodexSchema.V2ThreadApproveGuardianDeniedActionResponse; - readonly "thread/rollback": CodexSchema.V2ThreadRollbackResponse; + readonly "thread/revert": CodexSchema.V2ThreadRevertResponse; readonly "thread/list": CodexSchema.V2ThreadListResponse; + readonly "threadSection/list": CodexSchema.V2ThreadSectionListResponse; + readonly "threadSection/create": CodexSchema.V2ThreadSectionCreateResponse; + readonly "threadSection/update": CodexSchema.V2ThreadSectionUpdateResponse; + readonly "threadSection/delete": CodexSchema.V2ThreadSectionDeleteResponse; readonly "thread/loaded/list": CodexSchema.V2ThreadLoadedListResponse; readonly "thread/read": CodexSchema.V2ThreadReadResponse; + readonly "thread/turns/list": CodexSchema.V2ThreadTurnsListResponse; + readonly "thread/items/list": CodexSchema.V2ThreadItemsListResponse; readonly "thread/inject_items": CodexSchema.V2ThreadInjectItemsResponse; readonly "skills/list": CodexSchema.V2SkillsListResponse; readonly "skills/extraRoots/set": CodexSchema.V2SkillsExtraRootsSetResponse; @@ -320,6 +366,7 @@ export interface ClientRequestResponsesByMethod { readonly "marketplace/upgrade": CodexSchema.V2MarketplaceUpgradeResponse; readonly "plugin/list": CodexSchema.V2PluginListResponse; readonly "plugin/installed": CodexSchema.V2PluginInstalledResponse; + readonly "plugin/reconcile": CodexSchema.V2PluginReconcileResponse; readonly "plugin/read": CodexSchema.V2PluginReadResponse; readonly "plugin/skill/read": CodexSchema.V2PluginSkillReadResponse; readonly "plugin/share/save": CodexSchema.V2PluginShareSaveResponse; @@ -374,6 +421,7 @@ export interface ClientRequestResponsesByMethod { readonly "config/read": CodexSchema.V2ConfigReadResponse; readonly "externalAgentConfig/detect": CodexSchema.V2ExternalAgentConfigDetectResponse; readonly "externalAgentConfig/import": CodexSchema.V2ExternalAgentConfigImportResponse; + readonly "externalAgentConfig/import/recordHistory": CodexSchema.V2ExternalAgentConfigImportHistoryRecordResponse; readonly "externalAgentConfig/import/readHistories": CodexSchema.V2ExternalAgentConfigImportHistoriesReadResponse; readonly "config/value/write": CodexSchema.V2ConfigWriteResponse; readonly "config/batchWrite": CodexSchema.V2ConfigWriteResponse; @@ -423,10 +471,15 @@ export interface ServerNotificationParamsByMethod { readonly "thread/deleted": CodexSchema.V2ThreadDeletedNotification; readonly "thread/unarchived": CodexSchema.V2ThreadUnarchivedNotification; readonly "thread/closed": CodexSchema.V2ThreadClosedNotification; + readonly "thread/reverted": CodexSchema.V2ThreadRevertedNotification; readonly "skills/changed": CodexSchema.V2SkillsChangedNotification; readonly "thread/name/updated": CodexSchema.V2ThreadNameUpdatedNotification; + readonly "thread/attachment/updated": CodexSchema.V2ThreadAttachmentUpdatedNotification; readonly "thread/goal/updated": CodexSchema.V2ThreadGoalUpdatedNotification; readonly "thread/goal/cleared": CodexSchema.V2ThreadGoalClearedNotification; + readonly "thread/queue/changed": CodexSchema.V2ThreadQueueChangedNotification; + readonly "project/changed": CodexSchema.V2ProjectChangedNotification; + readonly "thread/project/updated": CodexSchema.V2ThreadProjectUpdatedNotification; readonly "thread/environment/connected": CodexSchema.V2EnvironmentConnectionNotification; readonly "thread/environment/disconnected": CodexSchema.V2EnvironmentConnectionNotification; readonly "thread/settings/updated": CodexSchema.V2ThreadSettingsUpdatedNotification; @@ -440,6 +493,7 @@ export interface ServerNotificationParamsByMethod { readonly "item/started": CodexSchema.V2ItemStartedNotification; readonly "item/autoApprovalReview/started": CodexSchema.V2ItemGuardianApprovalReviewStartedNotification; readonly "item/autoApprovalReview/completed": CodexSchema.V2ItemGuardianApprovalReviewCompletedNotification; + readonly "autoApprovalReview/strictReviewRequired": CodexSchema.V2StrictReviewRequiredNotification; readonly "item/completed": CodexSchema.V2ItemCompletedNotification; readonly "rawResponseItem/completed": CodexSchema.V2RawResponseItemCompletedNotification; readonly "rawResponse/completed": CodexSchema.V2RawResponseCompletedNotification; @@ -456,6 +510,7 @@ export interface ServerNotificationParamsByMethod { readonly "item/mcpToolCall/progress": CodexSchema.V2McpToolCallProgressNotification; readonly "mcpServer/oauthLogin/completed": CodexSchema.V2McpServerOauthLoginCompletedNotification; readonly "mcpServer/startupStatus/updated": CodexSchema.V2McpServerStatusUpdatedNotification; + readonly "mcpServer/event/stream/notification": CodexSchema.V2McpServerEventStreamNotification; readonly "account/updated": CodexSchema.V2AccountUpdatedNotification; readonly "account/rateLimits/updated": CodexSchema.V2AccountRateLimitsUpdatedNotification; readonly "app/list/updated": CodexSchema.V2AppListUpdatedNotification; @@ -469,6 +524,8 @@ export interface ServerNotificationParamsByMethod { readonly "thread/compacted": CodexSchema.V2ContextCompactedNotification; readonly "model/rerouted": CodexSchema.V2ModelReroutedNotification; readonly "model/verification": CodexSchema.V2ModelVerificationNotification; + readonly "modelProvider/authRecoveryStarted": CodexSchema.V2AuthRecoveryNotification; + readonly "modelProvider/authRecoveryCompleted": CodexSchema.V2AuthRecoveryNotification; readonly "turn/moderationMetadata": CodexSchema.V2TurnModerationMetadataNotification; readonly "model/safetyBuffering/updated": CodexSchema.V2ModelSafetyBufferingUpdatedNotification; readonly warning: CodexSchema.V2WarningNotification; @@ -479,6 +536,9 @@ export interface ServerNotificationParamsByMethod { readonly "fuzzyFileSearch/sessionCompleted": CodexSchema.FuzzyFileSearchSessionCompletedNotification; readonly "thread/realtime/started": CodexSchema.V2ThreadRealtimeStartedNotification; readonly "thread/realtime/itemAdded": CodexSchema.V2ThreadRealtimeItemAddedNotification; + readonly "thread/realtime/item/started": CodexSchema.V2ThreadRealtimeItemStartedNotification; + readonly "thread/realtime/item/transcript/delta": CodexSchema.V2ThreadRealtimeItemTranscriptDeltaNotification; + readonly "thread/realtime/item/completed": CodexSchema.V2ThreadRealtimeItemCompletedNotification; readonly "thread/realtime/transcript/delta": CodexSchema.V2ThreadRealtimeTranscriptDeltaNotification; readonly "thread/realtime/transcript/done": CodexSchema.V2ThreadRealtimeTranscriptDoneNotification; readonly "thread/realtime/outputAudio/delta": CodexSchema.V2ThreadRealtimeOutputAudioDeltaNotification; @@ -503,14 +563,24 @@ export const CLIENT_REQUEST_PARAMS = { "thread/goal/get": CodexSchema.V2ThreadGoalGetParams, "thread/goal/clear": CodexSchema.V2ThreadGoalClearParams, "thread/metadata/update": CodexSchema.V2ThreadMetadataUpdateParams, + "thread/attachment/add": CodexSchema.V2ThreadAttachmentAddParams, + "thread/attachment/list": CodexSchema.V2ThreadAttachmentListParams, + "thread/attachment/remove": CodexSchema.V2ThreadAttachmentRemoveParams, + "thread/section/move": CodexSchema.V2ThreadSectionMoveParams, "thread/unarchive": CodexSchema.V2ThreadUnarchiveParams, "thread/compact/start": CodexSchema.V2ThreadCompactStartParams, "thread/shellCommand": CodexSchema.V2ThreadShellCommandParams, "thread/approveGuardianDeniedAction": CodexSchema.V2ThreadApproveGuardianDeniedActionParams, - "thread/rollback": CodexSchema.V2ThreadRollbackParams, + "thread/revert": CodexSchema.V2ThreadRevertParams, "thread/list": CodexSchema.V2ThreadListParams, + "threadSection/list": CodexSchema.V2ThreadSectionListParams, + "threadSection/create": CodexSchema.V2ThreadSectionCreateParams, + "threadSection/update": CodexSchema.V2ThreadSectionUpdateParams, + "threadSection/delete": CodexSchema.V2ThreadSectionDeleteParams, "thread/loaded/list": CodexSchema.V2ThreadLoadedListParams, "thread/read": CodexSchema.V2ThreadReadParams, + "thread/turns/list": CodexSchema.V2ThreadTurnsListParams, + "thread/items/list": CodexSchema.V2ThreadItemsListParams, "thread/inject_items": CodexSchema.V2ThreadInjectItemsParams, "skills/list": CodexSchema.V2SkillsListParams, "skills/extraRoots/set": CodexSchema.V2SkillsExtraRootsSetParams, @@ -520,6 +590,7 @@ export const CLIENT_REQUEST_PARAMS = { "marketplace/upgrade": CodexSchema.V2MarketplaceUpgradeParams, "plugin/list": CodexSchema.V2PluginListParams, "plugin/installed": CodexSchema.V2PluginInstalledParams, + "plugin/reconcile": CodexSchema.V2PluginReconcileParams, "plugin/read": CodexSchema.V2PluginReadParams, "plugin/skill/read": CodexSchema.V2PluginSkillReadParams, "plugin/share/save": CodexSchema.V2PluginShareSaveParams, @@ -561,9 +632,9 @@ export const CLIENT_REQUEST_PARAMS = { "account/login/start": CodexSchema.V2LoginAccountParams, "account/login/cancel": CodexSchema.V2CancelLoginAccountParams, "account/logout": undefined, - "account/rateLimits/read": undefined, + "account/rateLimits/read": CodexSchema.V2NullableGetAccountRateLimitsParams, "account/rateLimitResetCredit/consume": CodexSchema.V2ConsumeAccountRateLimitResetCreditParams, - "account/usage/read": undefined, + "account/usage/read": CodexSchema.V2NullableGetAccountTokenUsageParams, "account/workspaceMessages/read": undefined, "account/sendAddCreditsNudgeEmail": CodexSchema.V2SendAddCreditsNudgeEmailParams, "feedback/upload": CodexSchema.V2FeedbackUploadParams, @@ -574,6 +645,8 @@ export const CLIENT_REQUEST_PARAMS = { "config/read": CodexSchema.V2ConfigReadParams, "externalAgentConfig/detect": CodexSchema.V2ExternalAgentConfigDetectParams, "externalAgentConfig/import": CodexSchema.V2ExternalAgentConfigImportParams, + "externalAgentConfig/import/recordHistory": + CodexSchema.V2ExternalAgentConfigImportHistoryRecordParams, "externalAgentConfig/import/readHistories": undefined, "config/value/write": CodexSchema.V2ConfigValueWriteParams, "config/batchWrite": CodexSchema.V2ConfigBatchWriteParams, @@ -598,14 +671,24 @@ export const CLIENT_REQUEST_RESPONSES = { "thread/goal/get": CodexSchema.V2ThreadGoalGetResponse, "thread/goal/clear": CodexSchema.V2ThreadGoalClearResponse, "thread/metadata/update": CodexSchema.V2ThreadMetadataUpdateResponse, + "thread/attachment/add": CodexSchema.V2ThreadAttachmentAddResponse, + "thread/attachment/list": CodexSchema.V2ThreadAttachmentListResponse, + "thread/attachment/remove": CodexSchema.V2ThreadAttachmentRemoveResponse, + "thread/section/move": CodexSchema.V2ThreadSectionMoveResponse, "thread/unarchive": CodexSchema.V2ThreadUnarchiveResponse, "thread/compact/start": CodexSchema.V2ThreadCompactStartResponse, "thread/shellCommand": CodexSchema.V2ThreadShellCommandResponse, "thread/approveGuardianDeniedAction": CodexSchema.V2ThreadApproveGuardianDeniedActionResponse, - "thread/rollback": CodexSchema.V2ThreadRollbackResponse, + "thread/revert": CodexSchema.V2ThreadRevertResponse, "thread/list": CodexSchema.V2ThreadListResponse, + "threadSection/list": CodexSchema.V2ThreadSectionListResponse, + "threadSection/create": CodexSchema.V2ThreadSectionCreateResponse, + "threadSection/update": CodexSchema.V2ThreadSectionUpdateResponse, + "threadSection/delete": CodexSchema.V2ThreadSectionDeleteResponse, "thread/loaded/list": CodexSchema.V2ThreadLoadedListResponse, "thread/read": CodexSchema.V2ThreadReadResponse, + "thread/turns/list": CodexSchema.V2ThreadTurnsListResponse, + "thread/items/list": CodexSchema.V2ThreadItemsListResponse, "thread/inject_items": CodexSchema.V2ThreadInjectItemsResponse, "skills/list": CodexSchema.V2SkillsListResponse, "skills/extraRoots/set": CodexSchema.V2SkillsExtraRootsSetResponse, @@ -615,6 +698,7 @@ export const CLIENT_REQUEST_RESPONSES = { "marketplace/upgrade": CodexSchema.V2MarketplaceUpgradeResponse, "plugin/list": CodexSchema.V2PluginListResponse, "plugin/installed": CodexSchema.V2PluginInstalledResponse, + "plugin/reconcile": CodexSchema.V2PluginReconcileResponse, "plugin/read": CodexSchema.V2PluginReadResponse, "plugin/skill/read": CodexSchema.V2PluginSkillReadResponse, "plugin/share/save": CodexSchema.V2PluginShareSaveResponse, @@ -669,6 +753,8 @@ export const CLIENT_REQUEST_RESPONSES = { "config/read": CodexSchema.V2ConfigReadResponse, "externalAgentConfig/detect": CodexSchema.V2ExternalAgentConfigDetectResponse, "externalAgentConfig/import": CodexSchema.V2ExternalAgentConfigImportResponse, + "externalAgentConfig/import/recordHistory": + CodexSchema.V2ExternalAgentConfigImportHistoryRecordResponse, "externalAgentConfig/import/readHistories": CodexSchema.V2ExternalAgentConfigImportHistoriesReadResponse, "config/value/write": CodexSchema.V2ConfigWriteResponse, @@ -719,10 +805,15 @@ export const SERVER_NOTIFICATION_PARAMS = { "thread/deleted": CodexSchema.V2ThreadDeletedNotification, "thread/unarchived": CodexSchema.V2ThreadUnarchivedNotification, "thread/closed": CodexSchema.V2ThreadClosedNotification, + "thread/reverted": CodexSchema.V2ThreadRevertedNotification, "skills/changed": CodexSchema.V2SkillsChangedNotification, "thread/name/updated": CodexSchema.V2ThreadNameUpdatedNotification, + "thread/attachment/updated": CodexSchema.V2ThreadAttachmentUpdatedNotification, "thread/goal/updated": CodexSchema.V2ThreadGoalUpdatedNotification, "thread/goal/cleared": CodexSchema.V2ThreadGoalClearedNotification, + "thread/queue/changed": CodexSchema.V2ThreadQueueChangedNotification, + "project/changed": CodexSchema.V2ProjectChangedNotification, + "thread/project/updated": CodexSchema.V2ThreadProjectUpdatedNotification, "thread/environment/connected": CodexSchema.V2EnvironmentConnectionNotification, "thread/environment/disconnected": CodexSchema.V2EnvironmentConnectionNotification, "thread/settings/updated": CodexSchema.V2ThreadSettingsUpdatedNotification, @@ -737,6 +828,7 @@ export const SERVER_NOTIFICATION_PARAMS = { "item/autoApprovalReview/started": CodexSchema.V2ItemGuardianApprovalReviewStartedNotification, "item/autoApprovalReview/completed": CodexSchema.V2ItemGuardianApprovalReviewCompletedNotification, + "autoApprovalReview/strictReviewRequired": CodexSchema.V2StrictReviewRequiredNotification, "item/completed": CodexSchema.V2ItemCompletedNotification, "rawResponseItem/completed": CodexSchema.V2RawResponseItemCompletedNotification, "rawResponse/completed": CodexSchema.V2RawResponseCompletedNotification, @@ -753,6 +845,7 @@ export const SERVER_NOTIFICATION_PARAMS = { "item/mcpToolCall/progress": CodexSchema.V2McpToolCallProgressNotification, "mcpServer/oauthLogin/completed": CodexSchema.V2McpServerOauthLoginCompletedNotification, "mcpServer/startupStatus/updated": CodexSchema.V2McpServerStatusUpdatedNotification, + "mcpServer/event/stream/notification": CodexSchema.V2McpServerEventStreamNotification, "account/updated": CodexSchema.V2AccountUpdatedNotification, "account/rateLimits/updated": CodexSchema.V2AccountRateLimitsUpdatedNotification, "app/list/updated": CodexSchema.V2AppListUpdatedNotification, @@ -768,6 +861,8 @@ export const SERVER_NOTIFICATION_PARAMS = { "thread/compacted": CodexSchema.V2ContextCompactedNotification, "model/rerouted": CodexSchema.V2ModelReroutedNotification, "model/verification": CodexSchema.V2ModelVerificationNotification, + "modelProvider/authRecoveryStarted": CodexSchema.V2AuthRecoveryNotification, + "modelProvider/authRecoveryCompleted": CodexSchema.V2AuthRecoveryNotification, "turn/moderationMetadata": CodexSchema.V2TurnModerationMetadataNotification, "model/safetyBuffering/updated": CodexSchema.V2ModelSafetyBufferingUpdatedNotification, warning: CodexSchema.V2WarningNotification, @@ -778,6 +873,10 @@ export const SERVER_NOTIFICATION_PARAMS = { "fuzzyFileSearch/sessionCompleted": CodexSchema.FuzzyFileSearchSessionCompletedNotification, "thread/realtime/started": CodexSchema.V2ThreadRealtimeStartedNotification, "thread/realtime/itemAdded": CodexSchema.V2ThreadRealtimeItemAddedNotification, + "thread/realtime/item/started": CodexSchema.V2ThreadRealtimeItemStartedNotification, + "thread/realtime/item/transcript/delta": + CodexSchema.V2ThreadRealtimeItemTranscriptDeltaNotification, + "thread/realtime/item/completed": CodexSchema.V2ThreadRealtimeItemCompletedNotification, "thread/realtime/transcript/delta": CodexSchema.V2ThreadRealtimeTranscriptDeltaNotification, "thread/realtime/transcript/done": CodexSchema.V2ThreadRealtimeTranscriptDoneNotification, "thread/realtime/outputAudio/delta": CodexSchema.V2ThreadRealtimeOutputAudioDeltaNotification, 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 8665ad6f436d..3428ce049ed7 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: 678157acaa819d5510adfe359abb5d0392cfe461 +// Upstream protocol ref: fe74a774532af67b5a4a3dec03ce9469e17f89af import * as CodexSchema from "./schema.gen.ts"; @@ -20,6 +20,7 @@ export const v2 = { AppsListResponse: CodexSchema.V2AppsListResponse, AppsReadParams: CodexSchema.V2AppsReadParams, AppsReadResponse: CodexSchema.V2AppsReadResponse, + AuthRecoveryNotification: CodexSchema.V2AuthRecoveryNotification, CancelLoginAccountParams: CodexSchema.V2CancelLoginAccountParams, CancelLoginAccountResponse: CodexSchema.V2CancelLoginAccountResponse, CommandExecOutputDeltaNotification: CodexSchema.V2CommandExecOutputDeltaNotification, @@ -56,6 +57,10 @@ export const v2 = { CodexSchema.V2ExternalAgentConfigImportCompletedNotification, ExternalAgentConfigImportHistoriesReadResponse: CodexSchema.V2ExternalAgentConfigImportHistoriesReadResponse, + ExternalAgentConfigImportHistoryRecordParams: + CodexSchema.V2ExternalAgentConfigImportHistoryRecordParams, + ExternalAgentConfigImportHistoryRecordResponse: + CodexSchema.V2ExternalAgentConfigImportHistoryRecordResponse, ExternalAgentConfigImportParams: CodexSchema.V2ExternalAgentConfigImportParams, ExternalAgentConfigImportProgressNotification: CodexSchema.V2ExternalAgentConfigImportProgressNotification, @@ -112,6 +117,7 @@ export const v2 = { MarketplaceUpgradeResponse: CodexSchema.V2MarketplaceUpgradeResponse, McpResourceReadParams: CodexSchema.V2McpResourceReadParams, McpResourceReadResponse: CodexSchema.V2McpResourceReadResponse, + McpServerEventStreamNotification: CodexSchema.V2McpServerEventStreamNotification, McpServerOauthLoginCompletedNotification: CodexSchema.V2McpServerOauthLoginCompletedNotification, McpServerOauthLoginParams: CodexSchema.V2McpServerOauthLoginParams, McpServerOauthLoginResponse: CodexSchema.V2McpServerOauthLoginResponse, @@ -127,6 +133,8 @@ export const v2 = { ModelReroutedNotification: CodexSchema.V2ModelReroutedNotification, ModelSafetyBufferingUpdatedNotification: CodexSchema.V2ModelSafetyBufferingUpdatedNotification, ModelVerificationNotification: CodexSchema.V2ModelVerificationNotification, + NullableGetAccountRateLimitsParams: CodexSchema.V2NullableGetAccountRateLimitsParams, + NullableGetAccountTokenUsageParams: CodexSchema.V2NullableGetAccountTokenUsageParams, PermissionProfileListParams: CodexSchema.V2PermissionProfileListParams, PermissionProfileListResponse: CodexSchema.V2PermissionProfileListResponse, PlanDeltaNotification: CodexSchema.V2PlanDeltaNotification, @@ -138,6 +146,8 @@ export const v2 = { PluginListResponse: CodexSchema.V2PluginListResponse, PluginReadParams: CodexSchema.V2PluginReadParams, PluginReadResponse: CodexSchema.V2PluginReadResponse, + PluginReconcileParams: CodexSchema.V2PluginReconcileParams, + PluginReconcileResponse: CodexSchema.V2PluginReconcileResponse, PluginShareCheckoutParams: CodexSchema.V2PluginShareCheckoutParams, PluginShareCheckoutResponse: CodexSchema.V2PluginShareCheckoutResponse, PluginShareDeleteParams: CodexSchema.V2PluginShareDeleteParams, @@ -154,6 +164,7 @@ export const v2 = { PluginUninstallResponse: CodexSchema.V2PluginUninstallResponse, ProcessExitedNotification: CodexSchema.V2ProcessExitedNotification, ProcessOutputDeltaNotification: CodexSchema.V2ProcessOutputDeltaNotification, + ProjectChangedNotification: CodexSchema.V2ProjectChangedNotification, RawResponseCompletedNotification: CodexSchema.V2RawResponseCompletedNotification, RawResponseItemCompletedNotification: CodexSchema.V2RawResponseItemCompletedNotification, ReasoningSummaryPartAddedNotification: CodexSchema.V2ReasoningSummaryPartAddedNotification, @@ -172,6 +183,7 @@ export const v2 = { SkillsExtraRootsSetResponse: CodexSchema.V2SkillsExtraRootsSetResponse, SkillsListParams: CodexSchema.V2SkillsListParams, SkillsListResponse: CodexSchema.V2SkillsListResponse, + StrictReviewRequiredNotification: CodexSchema.V2StrictReviewRequiredNotification, TerminalInteractionNotification: CodexSchema.V2TerminalInteractionNotification, ThreadApproveGuardianDeniedActionParams: CodexSchema.V2ThreadApproveGuardianDeniedActionParams, ThreadApproveGuardianDeniedActionResponse: @@ -179,6 +191,13 @@ export const v2 = { ThreadArchivedNotification: CodexSchema.V2ThreadArchivedNotification, ThreadArchiveParams: CodexSchema.V2ThreadArchiveParams, ThreadArchiveResponse: CodexSchema.V2ThreadArchiveResponse, + ThreadAttachmentAddParams: CodexSchema.V2ThreadAttachmentAddParams, + ThreadAttachmentAddResponse: CodexSchema.V2ThreadAttachmentAddResponse, + ThreadAttachmentListParams: CodexSchema.V2ThreadAttachmentListParams, + ThreadAttachmentListResponse: CodexSchema.V2ThreadAttachmentListResponse, + ThreadAttachmentRemoveParams: CodexSchema.V2ThreadAttachmentRemoveParams, + ThreadAttachmentRemoveResponse: CodexSchema.V2ThreadAttachmentRemoveResponse, + ThreadAttachmentUpdatedNotification: CodexSchema.V2ThreadAttachmentUpdatedNotification, ThreadClosedNotification: CodexSchema.V2ThreadClosedNotification, ThreadCompactStartParams: CodexSchema.V2ThreadCompactStartParams, ThreadCompactStartResponse: CodexSchema.V2ThreadCompactStartResponse, @@ -197,6 +216,8 @@ export const v2 = { ThreadGoalUpdatedNotification: CodexSchema.V2ThreadGoalUpdatedNotification, ThreadInjectItemsParams: CodexSchema.V2ThreadInjectItemsParams, ThreadInjectItemsResponse: CodexSchema.V2ThreadInjectItemsResponse, + ThreadItemsListParams: CodexSchema.V2ThreadItemsListParams, + ThreadItemsListResponse: CodexSchema.V2ThreadItemsListResponse, ThreadListParams: CodexSchema.V2ThreadListParams, ThreadListResponse: CodexSchema.V2ThreadListResponse, ThreadLoadedListParams: CodexSchema.V2ThreadLoadedListParams, @@ -204,11 +225,17 @@ export const v2 = { ThreadMetadataUpdateParams: CodexSchema.V2ThreadMetadataUpdateParams, ThreadMetadataUpdateResponse: CodexSchema.V2ThreadMetadataUpdateResponse, ThreadNameUpdatedNotification: CodexSchema.V2ThreadNameUpdatedNotification, + ThreadProjectUpdatedNotification: CodexSchema.V2ThreadProjectUpdatedNotification, + ThreadQueueChangedNotification: CodexSchema.V2ThreadQueueChangedNotification, ThreadReadParams: CodexSchema.V2ThreadReadParams, ThreadReadResponse: CodexSchema.V2ThreadReadResponse, ThreadRealtimeClosedNotification: CodexSchema.V2ThreadRealtimeClosedNotification, ThreadRealtimeErrorNotification: CodexSchema.V2ThreadRealtimeErrorNotification, ThreadRealtimeItemAddedNotification: CodexSchema.V2ThreadRealtimeItemAddedNotification, + ThreadRealtimeItemCompletedNotification: CodexSchema.V2ThreadRealtimeItemCompletedNotification, + ThreadRealtimeItemStartedNotification: CodexSchema.V2ThreadRealtimeItemStartedNotification, + ThreadRealtimeItemTranscriptDeltaNotification: + CodexSchema.V2ThreadRealtimeItemTranscriptDeltaNotification, ThreadRealtimeOutputAudioDeltaNotification: CodexSchema.V2ThreadRealtimeOutputAudioDeltaNotification, ThreadRealtimeSdpNotification: CodexSchema.V2ThreadRealtimeSdpNotification, @@ -218,8 +245,19 @@ export const v2 = { ThreadRealtimeTranscriptDoneNotification: CodexSchema.V2ThreadRealtimeTranscriptDoneNotification, ThreadResumeParams: CodexSchema.V2ThreadResumeParams, ThreadResumeResponse: CodexSchema.V2ThreadResumeResponse, - ThreadRollbackParams: CodexSchema.V2ThreadRollbackParams, - ThreadRollbackResponse: CodexSchema.V2ThreadRollbackResponse, + ThreadRevertedNotification: CodexSchema.V2ThreadRevertedNotification, + ThreadRevertParams: CodexSchema.V2ThreadRevertParams, + ThreadRevertResponse: CodexSchema.V2ThreadRevertResponse, + ThreadSectionCreateParams: CodexSchema.V2ThreadSectionCreateParams, + ThreadSectionCreateResponse: CodexSchema.V2ThreadSectionCreateResponse, + ThreadSectionDeleteParams: CodexSchema.V2ThreadSectionDeleteParams, + ThreadSectionDeleteResponse: CodexSchema.V2ThreadSectionDeleteResponse, + ThreadSectionListParams: CodexSchema.V2ThreadSectionListParams, + ThreadSectionListResponse: CodexSchema.V2ThreadSectionListResponse, + ThreadSectionMoveParams: CodexSchema.V2ThreadSectionMoveParams, + ThreadSectionMoveResponse: CodexSchema.V2ThreadSectionMoveResponse, + ThreadSectionUpdateParams: CodexSchema.V2ThreadSectionUpdateParams, + ThreadSectionUpdateResponse: CodexSchema.V2ThreadSectionUpdateResponse, ThreadSetNameParams: CodexSchema.V2ThreadSetNameParams, ThreadSetNameResponse: CodexSchema.V2ThreadSetNameResponse, ThreadSettingsUpdatedNotification: CodexSchema.V2ThreadSettingsUpdatedNotification, @@ -230,6 +268,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, 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 f4b186147705..9bc7b1ec1e9e 100644 --- a/packages/effect-codex-app-server/src/_generated/schema.gen.ts +++ b/packages/effect-codex-app-server/src/_generated/schema.gen.ts @@ -1,8 +1,13 @@ // This file is generated by the effect-codex-app-server package. Do not edit manually. -// Upstream protocol ref: 678157acaa819d5510adfe359abb5d0392cfe461 +// Upstream protocol ref: fe74a774532af67b5a4a3dec03ce9469e17f89af import * as Schema from "effect/Schema"; +export type ApplyPatchApprovalParams__ThreadId = string; +export const ApplyPatchApprovalParams__ThreadId = Schema.String.annotate({ + identifier: "ApplyPatchApprovalParams__ThreadId", +}); + export type ApplyPatchApprovalParams__FileChange = | { readonly content: string; readonly type: "add" } | { readonly content: string; readonly type: "delete" } @@ -24,143 +29,90 @@ export const ApplyPatchApprovalParams__FileChange = Schema.Union( }).annotate({ title: "UpdateFileChange" }), ], { mode: "oneOf" }, -); - -export type ApplyPatchApprovalParams__ThreadId = string; -export const ApplyPatchApprovalParams__ThreadId = Schema.String; +).annotate({ identifier: "ApplyPatchApprovalParams__FileChange" }); export type ApplyPatchApprovalResponse__NetworkPolicyRuleAction = "allow" | "deny"; export const ApplyPatchApprovalResponse__NetworkPolicyRuleAction = Schema.Literals([ "allow", "deny", -]); +]).annotate({ identifier: "ApplyPatchApprovalResponse__NetworkPolicyRuleAction" }); export type ChatgptAuthTokensRefreshParams__ChatgptAuthTokensRefreshReason = "unauthorized"; -export const ChatgptAuthTokensRefreshParams__ChatgptAuthTokensRefreshReason = - Schema.Literal("unauthorized"); - -export type ClientRequest__AbsolutePathBuf = string; -export const ClientRequest__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 ClientRequest__AddCreditsNudgeCreditType = "credits" | "usage_limit"; -export const ClientRequest__AddCreditsNudgeCreditType = Schema.Literals(["credits", "usage_limit"]); - -export type ClientRequest__AdditionalContextKind = "untrusted" | "application"; -export const ClientRequest__AdditionalContextKind = Schema.Literals(["untrusted", "application"]); - -export type ClientRequest__AgentMessageInputContent = - | { readonly text: string; readonly type: "input_text" } - | { readonly encrypted_content: string; readonly type: "encrypted_content" }; -export const ClientRequest__AgentMessageInputContent = Schema.Union( +export const ChatgptAuthTokensRefreshParams__ChatgptAuthTokensRefreshReason = Schema.Union( [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("input_text").annotate({ - title: "InputTextAgentMessageInputContentType", - }), - }).annotate({ title: "InputTextAgentMessageInputContent" }), - Schema.Struct({ - encrypted_content: Schema.String, - type: Schema.Literal("encrypted_content").annotate({ - title: "EncryptedContentAgentMessageInputContentType", - }), - }).annotate({ title: "EncryptedContentAgentMessageInputContent" }), + Schema.Literal("unauthorized").annotate({ + description: "Codex attempted a backend request and received `401 Unauthorized`.", + }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ChatgptAuthTokensRefreshParams__ChatgptAuthTokensRefreshReason" }); -export type ClientRequest__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const ClientRequest__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); +export type ClientRequest__RequestId = string | number; +export const ClientRequest__RequestId = Schema.Union([ + Schema.String, + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "ClientRequest__RequestId" }); -export type ClientRequest__AppsInstalledParams = { - readonly forceRefresh?: boolean; - readonly threadId?: string | null; +export type ClientRequest__InitializeCapabilities = { + readonly experimentalApi?: boolean; + readonly extensions?: { readonly [x: string]: Schema.Json } | null; + readonly mcpServerOpenaiFormElicitation?: boolean; + readonly optOutNotificationMethods?: ReadonlyArray | null; + readonly requestAttestation?: boolean; }; -export const ClientRequest__AppsInstalledParams = Schema.Struct({ - forceRefresh: Schema.optionalKey( +export const ClientRequest__InitializeCapabilities = Schema.Struct({ + experimentalApi: Schema.optionalKey( Schema.Boolean.annotate({ - description: - "When true and Apps are permitted, refresh and publish the hosted connector runtime tool snapshot first.", + description: "Opt into receiving experimental API methods and fields.", + default: false, }), ), - threadId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Optional loaded thread id used to evaluate effective app configuration.", - }), - Schema.Null, - ]), - ), -}).annotate({ description: "Read the committed installed connector runtime snapshot." }); - -export type ClientRequest__AppsListParams = { - readonly cursor?: string | null; - readonly forceRefetch?: boolean; - readonly limit?: number | null; - readonly threadId?: string | null; -}; -export const ClientRequest__AppsListParams = Schema.Struct({ - cursor: Schema.optionalKey( + extensions: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: "Opaque pagination cursor returned by a previous call.", + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })).annotate({ + description: "MCP extension settings declared by the app-server client.", }), Schema.Null, ]), ), - forceRefetch: Schema.optionalKey( + mcpServerOpenaiFormElicitation: Schema.optionalKey( Schema.Boolean.annotate({ - description: "When true, bypass app caches and fetch the latest data from sources.", + description: + "Legacy opt-in for the `openai/form` MCP extension.\n\nNew clients should declare `openai/form` in [`Self::extensions`].", }), ), - limit: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ - description: "Optional page size; defaults to a reasonable server-side value.", - format: "uint32", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - threadId: Schema.optionalKey( + optOutNotificationMethods: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ + Schema.Array(Schema.String).annotate({ description: - "Optional thread id used to evaluate app feature gating from that thread's config.", + "Exact notification method names that should be suppressed for this connection (for example `thread/started`).", }), Schema.Null, ]), ), -}).annotate({ description: "EXPERIMENTAL - list available apps/connectors." }); - -export type ClientRequest__AppsReadParams = { - readonly appIds: ReadonlyArray; - readonly includeTools?: boolean; -}; -export const ClientRequest__AppsReadParams = Schema.Struct({ - appIds: Schema.Array(Schema.String).annotate({ - description: - "App ids to read. The server accepts at most 100 ids and deduplicates repeated ids while preserving their first-request order.", - }), - includeTools: Schema.optionalKey( + requestAttestation: Schema.optionalKey( Schema.Boolean.annotate({ - description: - "When true, include display-only public tool summaries in the returned metadata.", + description: "Opt into `attestation/generate` requests for upstream `x-oai-attestation`.", + default: false, }), ), -}).annotate({ description: "EXPERIMENTAL - read metadata for specific apps/connectors." }); +}).annotate({ + description: "Client-declared capabilities negotiated during initialize.", + identifier: "ClientRequest__InitializeCapabilities", +}); + +export type ClientRequest__ClientInfo = { + readonly name: string; + readonly title?: string | null; + readonly version: string; +}; +export const ClientRequest__ClientInfo = Schema.Struct({ + name: Schema.String, + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + version: Schema.String, +}).annotate({ identifier: "ClientRequest__ClientInfo" }); export type ClientRequest__AskForApproval = | "untrusted" @@ -189,182 +141,326 @@ export const ClientRequest__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ClientRequest__AskForApproval" }); -export type ClientRequest__CancelLoginAccountParams = { readonly loginId: string }; -export const ClientRequest__CancelLoginAccountParams = Schema.Struct({ loginId: Schema.String }); +export type ClientRequest__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const ClientRequest__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "ClientRequest__ApprovalsReviewer", +}); -export type ClientRequest__ClientInfo = { +export type ClientRequest__Personality = "none" | "friendly" | "pragmatic"; +export const ClientRequest__Personality = Schema.Literals([ + "none", + "friendly", + "pragmatic", +]).annotate({ + description: "Deprecated: `friendly` and `pragmatic` no longer select a style.", + identifier: "ClientRequest__Personality", +}); + +export type ClientRequest__SandboxMode = "read-only" | "workspace-write" | "danger-full-access"; +export const ClientRequest__SandboxMode = Schema.Literals([ + "read-only", + "workspace-write", + "danger-full-access", +]).annotate({ identifier: "ClientRequest__SandboxMode" }); + +export type ClientRequest__ThreadStartSource = "startup" | "clear"; +export const ClientRequest__ThreadStartSource = Schema.Literals(["startup", "clear"]).annotate({ + identifier: "ClientRequest__ThreadStartSource", +}); + +export type ClientRequest__ThreadSource = string; +export const ClientRequest__ThreadSource = Schema.String.annotate({ + identifier: "ClientRequest__ThreadSource", +}); + +export type ClientRequest__ThreadArchiveParams = { readonly threadId: string }; +export const ClientRequest__ThreadArchiveParams = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadArchiveParams" }); + +export type ClientRequest__ThreadDeleteParams = { readonly threadId: string }; +export const ClientRequest__ThreadDeleteParams = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadDeleteParams" }); + +export type ClientRequest__ThreadUnsubscribeParams = { readonly threadId: string }; +export const ClientRequest__ThreadUnsubscribeParams = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadUnsubscribeParams" }); + +export type ClientRequest__ThreadSetNameParams = { readonly name: string; - readonly title?: string | null; - readonly version: string; + readonly threadId: string; }; -export const ClientRequest__ClientInfo = Schema.Struct({ +export const ClientRequest__ThreadSetNameParams = Schema.Struct({ name: Schema.String, - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - version: Schema.String, -}); + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadSetNameParams" }); -export type ClientRequest__CommandExecResizeParams = { - readonly processId: string; - readonly size: { readonly cols: number; readonly rows: number }; -}; -export const ClientRequest__CommandExecResizeParams = Schema.Struct({ - processId: Schema.String.annotate({ - description: - "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", - }), - size: Schema.Struct({ - cols: Schema.Number.annotate({ - description: "Terminal width in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - rows: Schema.Number.annotate({ - description: "Terminal height in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - }).annotate({ description: "PTY size in character cells for `command/exec` PTY sessions." }), -}).annotate({ description: "Resize a running PTY-backed `command/exec` session." }); +export type ClientRequest__ThreadGoalStatus = + | "active" + | "paused" + | "blocked" + | "usageLimited" + | "budgetLimited" + | "complete"; +export const ClientRequest__ThreadGoalStatus = Schema.Literals([ + "active", + "paused", + "blocked", + "usageLimited", + "budgetLimited", + "complete", +]).annotate({ identifier: "ClientRequest__ThreadGoalStatus" }); -export type ClientRequest__CommandExecTerminalSize = { - readonly cols: number; - readonly rows: number; -}; -export const ClientRequest__CommandExecTerminalSize = Schema.Struct({ - cols: Schema.Number.annotate({ - description: "Terminal width in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - rows: Schema.Number.annotate({ - description: "Terminal height in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}).annotate({ description: "PTY size in character cells for `command/exec` PTY sessions." }); +export type ClientRequest__ThreadGoalGetParams = { readonly threadId: string }; +export const ClientRequest__ThreadGoalGetParams = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadGoalGetParams" }); -export type ClientRequest__CommandExecTerminateParams = { readonly processId: string }; -export const ClientRequest__CommandExecTerminateParams = Schema.Struct({ - processId: Schema.String.annotate({ - description: - "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", - }), -}).annotate({ description: "Terminate a running `command/exec` session." }); +export type ClientRequest__ThreadGoalClearParams = { readonly threadId: string }; +export const ClientRequest__ThreadGoalClearParams = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadGoalClearParams" }); -export type ClientRequest__CommandExecWriteParams = { - readonly closeStdin?: boolean; - readonly deltaBase64?: string | null; - readonly processId: string; +export type ClientRequest__ThreadMetadataGitInfoUpdateParams = { + readonly branch?: string | null; + readonly originUrl?: string | null; + readonly sha?: string | null; }; -export const ClientRequest__CommandExecWriteParams = Schema.Struct({ - closeStdin: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "Close stdin after writing `deltaBase64`, if present.", - }), +export const ClientRequest__ThreadMetadataGitInfoUpdateParams = Schema.Struct({ + branch: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Omit to leave the stored branch unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", + }), + Schema.Null, + ]), ), - deltaBase64: Schema.optionalKey( + originUrl: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Optional base64-encoded stdin bytes to write." }), + Schema.String.annotate({ + description: + "Omit to leave the stored origin URL unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", + }), Schema.Null, ]), ), - processId: Schema.String.annotate({ - description: - "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", - }), + sha: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Omit to leave the stored commit unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__ThreadMetadataGitInfoUpdateParams" }); + +export type ClientRequest__ThreadAttachmentAddParams = { + readonly attachmentType: string; + readonly identityKey: string; + readonly payload: Schema.Json; + readonly threadId: string; +}; +export const ClientRequest__ThreadAttachmentAddParams = Schema.Struct({ + attachmentType: Schema.String, + identityKey: Schema.String, + payload: Schema.Json.annotate({ expected: "JSON value" }), + threadId: Schema.String, }).annotate({ - description: "Write stdin bytes to a running `command/exec` session, close stdin, or both.", + description: "Parameters for creating or locating an attachment on its owning thread.", + identifier: "ClientRequest__ThreadAttachmentAddParams", }); -export type ClientRequest__CommandMigration = { readonly name: string }; -export const ClientRequest__CommandMigration = Schema.Struct({ name: Schema.String }); - -export type ClientRequest__ConfigReadParams = { - readonly cwd?: string | null; - readonly includeLayers?: boolean; +export type ClientRequest__ThreadAttachmentListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; + readonly threadId: string; }; -export const ClientRequest__ConfigReadParams = Schema.Struct({ - cwd: Schema.optionalKey( +export const ClientRequest__ThreadAttachmentListParams = Schema.Struct({ + cursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + limit: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: - "Optional working directory to resolve project config layers. If specified, return the effective config as seen from that directory (i.e., including any project layers between `cwd` and the project/repo root).", - }), + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), - includeLayers: Schema.optionalKey(Schema.Boolean), + threadId: Schema.String, +}).annotate({ + description: "Parameters for listing attachments from one thread.", + identifier: "ClientRequest__ThreadAttachmentListParams", }); -export type ClientRequest__ConsumeAccountRateLimitResetCreditParams = { - readonly creditId?: string | null; - readonly idempotencyKey: string; +export type ClientRequest__ThreadAttachmentRemoveParams = { + readonly attachmentType: string; + readonly identityKey: string; + readonly threadId: string; }; -export const ClientRequest__ConsumeAccountRateLimitResetCreditParams = Schema.Struct({ - creditId: Schema.optionalKey( +export const ClientRequest__ThreadAttachmentRemoveParams = Schema.Struct({ + attachmentType: Schema.String, + identityKey: Schema.String, + threadId: Schema.String, +}).annotate({ + description: "Parameters for deleting an attachment by its stable thread-local identity.", + identifier: "ClientRequest__ThreadAttachmentRemoveParams", +}); + +export type ClientRequest__ThreadSectionMoveParams = { + readonly beforeThreadId?: string | null; + readonly sectionId: string | null; + readonly threadId: string; +}; +export const ClientRequest__ThreadSectionMoveParams = Schema.Struct({ + beforeThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: - "Opaque reset-credit identifier to redeem. When omitted, the backend selects the next available credit.", + description: "Existing thread to insert before; omission or null appends to the section.", }), Schema.Null, ]), ), - idempotencyKey: Schema.String.annotate({ - description: - "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.", + sectionId: Schema.Union([ + Schema.String.annotate({ + description: "Destination section, or `null` to remove the thread from its section.", + }), + Schema.Null, + ]), + threadId: Schema.String.annotate({ + description: "Thread to move into, within, or out of a section.", }), +}).annotate({ + description: "Parameters for moving a thread within a server-owned section ordering.", + identifier: "ClientRequest__ThreadSectionMoveParams", }); -export type ClientRequest__ConversationTextRole = "user" | "developer" | "assistant"; -export const ClientRequest__ConversationTextRole = Schema.Literals([ - "user", - "developer", - "assistant", -]); +export type ClientRequest__ThreadUnarchiveParams = { readonly threadId: string }; +export const ClientRequest__ThreadUnarchiveParams = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadUnarchiveParams" }); -export type ClientRequest__DynamicToolNamespaceTool = { - readonly deferLoading?: boolean; - readonly description: string; - readonly inputSchema: unknown; - readonly name: string; - readonly type: "function"; -}; -export const ClientRequest__DynamicToolNamespaceTool = Schema.Union( - [ - Schema.Struct({ - deferLoading: Schema.optionalKey(Schema.Boolean), - description: Schema.String, - inputSchema: Schema.Unknown, - name: Schema.String, - type: Schema.Literal("function").annotate({ title: "FunctionDynamicToolNamespaceToolType" }), - }).annotate({ title: "FunctionDynamicToolNamespaceTool" }), - ], - { mode: "oneOf" }, -); +export type ClientRequest__ThreadCompactStartParams = { readonly threadId: string }; +export const ClientRequest__ThreadCompactStartParams = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadCompactStartParams" }); -export type ClientRequest__ExperimentalFeatureEnablementSetParams = { - readonly enablement: { readonly [x: string]: boolean }; +export type ClientRequest__ThreadShellCommandParams = { + readonly command: string; + readonly threadId: string; + readonly timeoutMs?: number | null; }; -export const ClientRequest__ExperimentalFeatureEnablementSetParams = Schema.Struct({ - enablement: Schema.Record(Schema.String, Schema.Boolean).annotate({ +export const ClientRequest__ThreadShellCommandParams = Schema.Struct({ + command: Schema.String.annotate({ description: - "Process-wide runtime feature enablement keyed by canonical feature name.\n\nOnly named features are updated. Omitted features are left unchanged. Send an empty map for a no-op.", + "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.", }), + threadId: Schema.String, + timeoutMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Maximum execution time in milliseconds. Defaults to one hour when omitted or null. Must be non-negative; zero requests an immediate timeout, not unlimited execution. Does not affect the immediate RPC acknowledgement.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__ThreadShellCommandParams" }); + +export type ClientRequest__ThreadApproveGuardianDeniedActionParams = { + readonly event: Schema.Json; + readonly threadId: string; +}; +export const ClientRequest__ThreadApproveGuardianDeniedActionParams = Schema.Struct({ + event: Schema.Json.annotate({ + expected: "JSON value", + description: "Serialized `codex_protocol::protocol::GuardianAssessmentEvent`.", + }), + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadApproveGuardianDeniedActionParams" }); + +export type ClientRequest__ThreadRevertParams = { + readonly beforeTurnId: string; + readonly threadId: string; +}; +export const ClientRequest__ThreadRevertParams = Schema.Struct({ + beforeTurnId: Schema.String.annotate({ + description: "Turn excluded from the replacement history, together with every later turn.", + }), + threadId: Schema.String, +}).annotate({ + description: + "Replace a paginated thread's durable history with the prefix before one turn.\n\nThis only changes persisted conversation history. It does not revert local file changes.", + identifier: "ClientRequest__ThreadRevertParams", }); -export type ClientRequest__ExperimentalFeatureListParams = { +export type ClientRequest__ThreadListCwdFilter = string | ReadonlyArray; +export const ClientRequest__ThreadListCwdFilter = Schema.Union([ + Schema.String, + Schema.Array(Schema.String), +]).annotate({ identifier: "ClientRequest__ThreadListCwdFilter" }); + +export type ClientRequest__SortDirection = "asc" | "desc"; +export const ClientRequest__SortDirection = Schema.Literals(["asc", "desc"]).annotate({ + identifier: "ClientRequest__SortDirection", +}); + +export type ClientRequest__ThreadSortKey = + | "created_at" + | "updated_at" + | "recency_at" + | "section_position"; +export const ClientRequest__ThreadSortKey = Schema.Literals([ + "created_at", + "updated_at", + "recency_at", + "section_position", +]).annotate({ identifier: "ClientRequest__ThreadSortKey" }); + +export type ClientRequest__ThreadSourceKind = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "subAgent" + | "subAgentReview" + | "subAgentCompact" + | "subAgentThreadSpawn" + | "subAgentOther" + | "unknown"; +export const ClientRequest__ThreadSourceKind = Schema.Literals([ + "cli", + "vscode", + "exec", + "appServer", + "subAgent", + "subAgentReview", + "subAgentCompact", + "subAgentThreadSpawn", + "subAgentOther", + "unknown", +]).annotate({ identifier: "ClientRequest__ThreadSourceKind" }); + +export type ClientRequest__ThreadSectionListParams = { readonly cursor?: string | null; readonly limit?: number | null; - readonly threadId?: string | null; }; -export const ClientRequest__ExperimentalFeatureListParams = Schema.Struct({ +export const ClientRequest__ThreadSectionListParams = Schema.Struct({ cursor: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -376,439 +472,479 @@ export const ClientRequest__ExperimentalFeatureListParams = Schema.Struct({ limit: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ - description: "Optional page size; defaults to a reasonable server-side value.", + description: "Maximum number of sections to return.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - threadId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.", - }), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), +}).annotate({ + description: "Parameters for listing independently persisted thread sections.", + identifier: "ClientRequest__ThreadSectionListParams", }); -export type ClientRequest__ExternalAgentConfigDetectParams = { - readonly cwds?: ReadonlyArray | null; - readonly includeHome?: boolean; - readonly migrationSource?: string | null; - readonly source?: string | null; +export type ClientRequest__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const ClientRequest__ExternalAgentConfigDetectParams = Schema.Struct({ - cwds: Schema.optionalKey( - Schema.Union([ - Schema.Array(Schema.String).annotate({ - description: "Zero or more working directories to include for repo-scoped detection.", - }), - Schema.Null, - ]), - ), - includeHome: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "If true, include detection under the user's home directory.", - }), - ), - migrationSource: Schema.optionalKey( +export const ClientRequest__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "ClientRequest__ThreadSectionAppearance", +}); + +export type ClientRequest__ThreadSectionDeleteParams = { readonly sectionId: string }; +export const ClientRequest__ThreadSectionDeleteParams = Schema.Struct({ + sectionId: Schema.String.annotate({ + description: "The stable, server-generated identity of the section to delete.", + }), +}).annotate({ + description: "Parameters for deleting an independently persisted thread section.", + identifier: "ClientRequest__ThreadSectionDeleteParams", +}); + +export type ClientRequest__ThreadLoadedListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; +}; +export const ClientRequest__ThreadLoadedListParams = Schema.Struct({ + cursor: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: - "Optional migration-source selector. Missing or unrecognized values use the default source.", + description: "Opaque pagination cursor returned by a previous call.", }), Schema.Null, ]), ), - source: Schema.optionalKey( + limit: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: - "Deprecated field retained for compatibility. This field is ignored; use `migrationSource` to select the migration source.", - }), + Schema.Number.annotate({ + description: "Optional page size; defaults to no limit.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), -}); - -export type ClientRequest__ExternalAgentConfigMigrationItemType = - | "AGENTS_MD" - | "CONFIG" - | "SKILLS" - | "PLUGINS" - | "MCP_SERVER_CONFIG" - | "SUBAGENTS" - | "HOOKS" - | "COMMANDS" - | "MEMORY" - | "SESSIONS"; -export const ClientRequest__ExternalAgentConfigMigrationItemType = Schema.Literals([ - "AGENTS_MD", - "CONFIG", - "SKILLS", - "PLUGINS", - "MCP_SERVER_CONFIG", - "SUBAGENTS", - "HOOKS", - "COMMANDS", - "MEMORY", - "SESSIONS", -]); +}).annotate({ identifier: "ClientRequest__ThreadLoadedListParams" }); -export type ClientRequest__FeedbackUploadParams = { - readonly classification: string; - readonly extraLogFiles?: ReadonlyArray | null; - readonly includeLogs?: boolean; - readonly reason?: string | null; - readonly tags?: { readonly [x: string]: string } | null; - readonly threadId?: string | null; +export type ClientRequest__ThreadReadParams = { + readonly includeTurns?: boolean; + readonly threadId: string; }; -export const ClientRequest__FeedbackUploadParams = Schema.Struct({ - classification: Schema.String, - extraLogFiles: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - includeLogs: Schema.optionalKey(Schema.Boolean), - reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - tags: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), +export const ClientRequest__ThreadReadParams = Schema.Struct({ + includeTurns: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When true, include turns and their items from rollout history. Full-history hydration is deprecated for paginated threads; prefer a metadata-only read and page with `thread/turns/list` and `thread/items/list`.", + }), ), - threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadReadParams" }); -export type ClientRequest__FsCopyParams = { - readonly destinationPath: string; - readonly recursive?: boolean; - readonly sourcePath: string; +export type ClientRequest__TurnItemsView = "notLoaded" | "summary" | "full"; +export const ClientRequest__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ClientRequest__TurnItemsView" }); + +export type ClientRequest__ThreadInjectItemsParams = { + readonly items: ReadonlyArray; + readonly threadId: string; }; -export const ClientRequest__FsCopyParams = Schema.Struct({ - destinationPath: 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 const ClientRequest__ThreadInjectItemsParams = Schema.Struct({ + items: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ + description: "Raw Responses API items to append to the thread's model-visible history.", }), - recursive: Schema.optionalKey( + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadInjectItemsParams" }); + +export type ClientRequest__SkillsListParams = { + readonly cwds?: ReadonlyArray; + readonly forceReload?: boolean; +}; +export const ClientRequest__SkillsListParams = Schema.Struct({ + cwds: Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + description: "When empty, defaults to the current session working directory.", + }), + ), + forceReload: Schema.optionalKey( Schema.Boolean.annotate({ - description: "Required for directory copies; ignored for file copies.", + description: "When true, bypass the skills cache and re-scan skills from disk.", }), ), - sourcePath: 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.", - }), -}).annotate({ description: "Copy a file or directory tree on the host filesystem." }); +}).annotate({ identifier: "ClientRequest__SkillsListParams" }); -export type ClientRequest__FsCreateDirectoryParams = { - readonly path: string; - readonly recursive?: boolean | null; -}; -export const ClientRequest__FsCreateDirectoryParams = Schema.Struct({ - path: 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.", - }), - recursive: Schema.optionalKey( - Schema.Union([ - Schema.Boolean.annotate({ - description: "Whether parent directories should also be created. Defaults to `true`.", - }), - Schema.Null, - ]), +export type ClientRequest__AbsolutePathBuf = string; +export const ClientRequest__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.", + identifier: "ClientRequest__AbsolutePathBuf", +}); + +export type ClientRequest__HooksListParams = { readonly cwds?: ReadonlyArray }; +export const ClientRequest__HooksListParams = Schema.Struct({ + cwds: Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + description: "When empty, defaults to the current session working directory.", + }), ), -}).annotate({ description: "Create a directory on the host filesystem." }); +}).annotate({ identifier: "ClientRequest__HooksListParams" }); -export type ClientRequest__FsGetMetadataParams = { readonly path: string }; -export const ClientRequest__FsGetMetadataParams = Schema.Struct({ - path: 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.", - }), -}).annotate({ description: "Request metadata for an absolute path." }); +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])), +}).annotate({ identifier: "ClientRequest__MarketplaceAddParams" }); -export type ClientRequest__FsReadDirectoryParams = { readonly path: string }; -export const ClientRequest__FsReadDirectoryParams = Schema.Struct({ - path: 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.", - }), -}).annotate({ description: "List direct child names for a directory." }); +export type ClientRequest__MarketplaceRemoveParams = { readonly marketplaceName: string }; +export const ClientRequest__MarketplaceRemoveParams = Schema.Struct({ + marketplaceName: Schema.String, +}).annotate({ identifier: "ClientRequest__MarketplaceRemoveParams" }); -export type ClientRequest__FsReadFileParams = { readonly path: string }; -export const ClientRequest__FsReadFileParams = Schema.Struct({ - path: 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.", - }), -}).annotate({ description: "Read a file from the host filesystem." }); +export type ClientRequest__MarketplaceUpgradeParams = { readonly marketplaceName?: string | null }; +export const ClientRequest__MarketplaceUpgradeParams = Schema.Struct({ + marketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ClientRequest__MarketplaceUpgradeParams" }); -export type ClientRequest__FsRemoveParams = { - readonly force?: boolean | null; - readonly path: string; - readonly recursive?: boolean | null; -}; -export const ClientRequest__FsRemoveParams = Schema.Struct({ - force: Schema.optionalKey( - Schema.Union([ - Schema.Boolean.annotate({ - description: "Whether missing paths should be ignored. Defaults to `true`.", - }), - Schema.Null, - ]), - ), - path: 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.", - }), - recursive: Schema.optionalKey( +export type ClientRequest__PluginListMarketplaceKind = + | "local" + | "vertical" + | "workspace-directory" + | "shared-with-me" + | "created-by-me-remote"; +export const ClientRequest__PluginListMarketplaceKind = Schema.Literals([ + "local", + "vertical", + "workspace-directory", + "shared-with-me", + "created-by-me-remote", +]).annotate({ identifier: "ClientRequest__PluginListMarketplaceKind" }); + +export type ClientRequest__PluginReconcileParams = { readonly reason?: string | null }; +export const ClientRequest__PluginReconcileParams = Schema.Struct({ + reason: Schema.optionalKey( Schema.Union([ - Schema.Boolean.annotate({ - description: "Whether directory removal should recurse. Defaults to `true`.", + Schema.String.annotate({ + description: "Optional client-provided reason recorded with the reconciliation attempt.", }), Schema.Null, ]), ), -}).annotate({ description: "Remove a file or directory tree from the host filesystem." }); +}).annotate({ identifier: "ClientRequest__PluginReconcileParams" }); -export type ClientRequest__FsUnwatchParams = { readonly watchId: string }; -export const ClientRequest__FsUnwatchParams = Schema.Struct({ - watchId: Schema.String.annotate({ - description: "Watch identifier previously provided to `fs/watch`.", - }), -}).annotate({ description: "Stop filesystem watch notifications for a prior `fs/watch`." }); +export type ClientRequest__PluginSkillReadParams = { + readonly remoteMarketplaceName: string; + readonly remotePluginId: string; + readonly skillName: string; +}; +export const ClientRequest__PluginSkillReadParams = Schema.Struct({ + remoteMarketplaceName: Schema.String, + remotePluginId: Schema.String, + skillName: Schema.String, +}).annotate({ identifier: "ClientRequest__PluginSkillReadParams" }); -export type ClientRequest__FsWatchParams = { readonly path: string; readonly watchId: string }; -export const ClientRequest__FsWatchParams = Schema.Struct({ - path: 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.", - }), - watchId: Schema.String.annotate({ - description: "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.", - }), -}).annotate({ description: "Start filesystem watch notifications for an absolute path." }); +export type ClientRequest__PluginShareDiscoverability = "LISTED" | "UNLISTED" | "PRIVATE"; +export const ClientRequest__PluginShareDiscoverability = Schema.Literals([ + "LISTED", + "UNLISTED", + "PRIVATE", +]).annotate({ identifier: "ClientRequest__PluginShareDiscoverability" }); -export type ClientRequest__FsWriteFileParams = { - readonly dataBase64: string; - readonly path: string; -}; -export const ClientRequest__FsWriteFileParams = Schema.Struct({ - dataBase64: Schema.String.annotate({ description: "File contents encoded as base64." }), - path: 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.", - }), -}).annotate({ description: "Write a file on the host filesystem." }); +export type ClientRequest__PluginSharePrincipalType = "user" | "group" | "workspace"; +export const ClientRequest__PluginSharePrincipalType = Schema.Literals([ + "user", + "group", + "workspace", +]).annotate({ identifier: "ClientRequest__PluginSharePrincipalType" }); -export type ClientRequest__FuzzyFileSearchParams = { - readonly cancellationToken?: string | null; - readonly query: string; - readonly roots: ReadonlyArray; -}; -export const ClientRequest__FuzzyFileSearchParams = Schema.Struct({ - cancellationToken: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - query: Schema.String, - roots: Schema.Array(Schema.String), +export type ClientRequest__PluginShareTargetRole = "reader" | "editor"; +export const ClientRequest__PluginShareTargetRole = Schema.Literals(["reader", "editor"]).annotate({ + identifier: "ClientRequest__PluginShareTargetRole", }); -export type ClientRequest__GetAccountParams = { readonly refreshToken?: boolean }; -export const ClientRequest__GetAccountParams = Schema.Struct({ - refreshToken: Schema.optionalKey( +export type ClientRequest__PluginShareUpdateDiscoverability = "UNLISTED" | "PRIVATE" | "LISTED"; +export const ClientRequest__PluginShareUpdateDiscoverability = Schema.Literals([ + "UNLISTED", + "PRIVATE", + "LISTED", +]).annotate({ identifier: "ClientRequest__PluginShareUpdateDiscoverability" }); + +export type ClientRequest__PluginShareListParams = { readonly [x: string]: Schema.Json }; +export const ClientRequest__PluginShareListParams = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ identifier: "ClientRequest__PluginShareListParams" }); + +export type ClientRequest__PluginShareCheckoutParams = { readonly remotePluginId: string }; +export const ClientRequest__PluginShareCheckoutParams = Schema.Struct({ + remotePluginId: Schema.String, +}).annotate({ identifier: "ClientRequest__PluginShareCheckoutParams" }); + +export type ClientRequest__PluginShareDeleteParams = { readonly remotePluginId: string }; +export const ClientRequest__PluginShareDeleteParams = Schema.Struct({ + remotePluginId: Schema.String, +}).annotate({ identifier: "ClientRequest__PluginShareDeleteParams" }); + +export type ClientRequest__AppsReadParams = { + readonly appIds: ReadonlyArray; + readonly includeTools?: boolean; + readonly threadId?: string | null; +}; +export const ClientRequest__AppsReadParams = Schema.Struct({ + appIds: Schema.Array(Schema.String).annotate({ + description: + "App ids to read. The server accepts at most 100 ids and deduplicates repeated ids while preserving their first-request order.", + }), + includeTools: Schema.optionalKey( Schema.Boolean.annotate({ description: - "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.", + "When true, include display-only public tool summaries in the returned metadata.", }), ), -}); - -export type ClientRequest__HookMigration = { readonly name: string }; -export const ClientRequest__HookMigration = Schema.Struct({ name: Schema.String }); - -export type ClientRequest__HooksListParams = { readonly cwds?: ReadonlyArray }; -export const ClientRequest__HooksListParams = Schema.Struct({ - cwds: Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - description: "When empty, defaults to the current session working directory.", - }), + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional loaded thread id used to evaluate effective app configuration.", + }), + Schema.Null, + ]), ), +}).annotate({ + description: "EXPERIMENTAL - read metadata for specific apps/connectors.", + identifier: "ClientRequest__AppsReadParams", }); -export type ClientRequest__ImageDetail = "auto" | "low" | "high" | "original"; -export const ClientRequest__ImageDetail = Schema.Literals(["auto", "low", "high", "original"]); - -export type ClientRequest__InitializeCapabilities = { - readonly experimentalApi?: boolean; - readonly mcpServerOpenaiFormElicitation?: boolean; - readonly optOutNotificationMethods?: ReadonlyArray | null; - readonly requestAttestation?: boolean; +export type ClientRequest__AppsListParams = { + readonly cursor?: string | null; + readonly forceRefetch?: boolean; + readonly limit?: number | null; + readonly threadId?: string | null; }; -export const ClientRequest__InitializeCapabilities = Schema.Struct({ - experimentalApi: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "Opt into receiving experimental API methods and fields.", - default: false, - }), +export const ClientRequest__AppsListParams = Schema.Struct({ + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque pagination cursor returned by a previous call.", + }), + Schema.Null, + ]), ), - mcpServerOpenaiFormElicitation: Schema.optionalKey( + forceRefetch: Schema.optionalKey( Schema.Boolean.annotate({ - description: "Allow downstream MCP servers to request OpenAI extended form elicitations.", + description: "When true, bypass app caches and fetch the latest data from sources.", }), ), - optOutNotificationMethods: Schema.optionalKey( + limit: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.String).annotate({ + Schema.Number.annotate({ + description: "Optional page size; defaults to a reasonable server-side value.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: - "Exact notification method names that should be suppressed for this connection (for example `thread/started`).", + "Optional thread id used to evaluate app feature gating from that thread's config.", }), Schema.Null, ]), ), - requestAttestation: Schema.optionalKey( +}).annotate({ + description: "EXPERIMENTAL - list available apps/connectors.", + identifier: "ClientRequest__AppsListParams", +}); + +export type ClientRequest__AppsInstalledParams = { + readonly forceRefresh?: boolean; + readonly threadId?: string | null; +}; +export const ClientRequest__AppsInstalledParams = Schema.Struct({ + forceRefresh: Schema.optionalKey( Schema.Boolean.annotate({ - description: "Opt into `attestation/generate` requests for upstream `x-oai-attestation`.", - default: false, + description: + "When true and Apps are permitted, refresh and publish the hosted connector runtime tool snapshot first.", }), ), -}).annotate({ description: "Client-declared capabilities negotiated during initialize." }); + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional loaded thread id used to evaluate effective app configuration.", + }), + Schema.Null, + ]), + ), +}).annotate({ + description: "Read the committed installed connector runtime snapshot.", + identifier: "ClientRequest__AppsInstalledParams", +}); -export type ClientRequest__InternalChatMessageMetadataPassthrough = { - readonly turn_id?: string | null; -}; -export const ClientRequest__InternalChatMessageMetadataPassthrough = Schema.Struct({ - turn_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +export type ClientRequest__FsUnwatchParams = { readonly watchId: string }; +export const ClientRequest__FsUnwatchParams = Schema.Struct({ + watchId: Schema.String.annotate({ + description: "Watch identifier previously provided to `fs/watch`.", + }), }).annotate({ - description: - "Internal Responses API passthrough metadata copied into underlying chat messages.\n\nResponses API strongly types this payload. Do not modify it without first getting API approval and making the corresponding Responses API change.", + description: "Stop filesystem watch notifications for a prior `fs/watch`.", + identifier: "ClientRequest__FsUnwatchParams", }); -export type ClientRequest__LegacyAppPathString = string; -export const ClientRequest__LegacyAppPathString = Schema.String; +export type ClientRequest__PluginUninstallParams = { readonly pluginId: string }; +export const ClientRequest__PluginUninstallParams = Schema.Struct({ + pluginId: Schema.String, +}).annotate({ identifier: "ClientRequest__PluginUninstallParams" }); -export type ClientRequest__LocalShellAction = { - readonly command: ReadonlyArray; - readonly env?: { readonly [x: string]: string } | null; - readonly timeout_ms?: number | null; - readonly type: "exec"; - readonly user?: string | null; - readonly working_directory?: string | null; -}; -export const ClientRequest__LocalShellAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.Array(Schema.String), - env: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - timeout_ms: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - type: Schema.Literal("exec").annotate({ title: "ExecLocalShellActionType" }), - user: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - working_directory: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }).annotate({ title: "ExecLocalShellAction" }), - ], - { mode: "oneOf" }, +export type ClientRequest__ReasoningEffort = string; +export const ClientRequest__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "ClientRequest__ReasoningEffort", + }), ); -export type ClientRequest__LocalShellStatus = "completed" | "in_progress" | "incomplete"; -export const ClientRequest__LocalShellStatus = Schema.Literals([ - "completed", - "in_progress", - "incomplete", -]); - -export type ClientRequest__LoginAppBrand = "codex" | "chatgpt"; -export const ClientRequest__LoginAppBrand = Schema.Literals(["codex", "chatgpt"]); - -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__ByteRange = { readonly end: number; readonly start: number }; +export const ClientRequest__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "ClientRequest__ByteRange" }); -export type ClientRequest__MarketplaceUpgradeParams = { readonly marketplaceName?: string | null }; -export const ClientRequest__MarketplaceUpgradeParams = Schema.Struct({ - marketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +export type ClientRequest__ImageDetail = "auto" | "low" | "high" | "original"; +export const ClientRequest__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "ClientRequest__ImageDetail" }); -export type ClientRequest__McpResourceReadParams = { - readonly server: string; - readonly threadId?: string | null; - readonly uri: string; -}; -export const ClientRequest__McpResourceReadParams = Schema.Struct({ - server: Schema.String, - threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - uri: Schema.String, +export type ClientRequest__NetworkAccess = "restricted" | "enabled"; +export const ClientRequest__NetworkAccess = Schema.Literals(["restricted", "enabled"]).annotate({ + identifier: "ClientRequest__NetworkAccess", }); -export type ClientRequest__McpServerMigration = { readonly name: string }; -export const ClientRequest__McpServerMigration = Schema.Struct({ name: Schema.String }); - -export type ClientRequest__McpServerOauthLoginParams = { - readonly name: string; - readonly scopes?: ReadonlyArray | null; - readonly threadId?: string | null; - readonly timeoutSecs?: number | null; -}; -export const ClientRequest__McpServerOauthLoginParams = Schema.Struct({ - name: Schema.String, - scopes: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - timeoutSecs: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), +export type ClientRequest__ReasoningSummary = "auto" | "concise" | "detailed" | "none"; +export const ClientRequest__ReasoningSummary = Schema.Union( + [ + Schema.Literals(["auto", "concise", "detailed"]), + Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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", + identifier: "ClientRequest__ReasoningSummary", }); -export type ClientRequest__McpServerStatusDetail = "full" | "toolsAndAuthOnly"; -export const ClientRequest__McpServerStatusDetail = Schema.Literals(["full", "toolsAndAuthOnly"]); - -export type ClientRequest__McpServerToolCallParams = { - readonly _meta?: unknown; - readonly arguments?: unknown; - readonly server: string; +export type ClientRequest__TurnInterruptParams = { readonly threadId: string; - readonly tool: string; + readonly turnId: string; }; -export const ClientRequest__McpServerToolCallParams = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - arguments: Schema.optionalKey(Schema.Unknown), - server: Schema.String, +export const ClientRequest__TurnInterruptParams = Schema.Struct({ threadId: Schema.String, - tool: Schema.String, -}); - -export type ClientRequest__MergeStrategy = "replace" | "upsert"; -export const ClientRequest__MergeStrategy = Schema.Literals(["replace", "upsert"]); - -export type ClientRequest__MessagePhase = "commentary" | "final_answer"; -export const ClientRequest__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.', - }, -); + turnId: Schema.String, +}).annotate({ identifier: "ClientRequest__TurnInterruptParams" }); -export type ClientRequest__ModeKind = "plan" | "default"; -export const ClientRequest__ModeKind = Schema.Literals(["plan", "default"]).annotate({ - description: "Initial collaboration mode to use when the TUI starts.", +export type ClientRequest__ReviewDelivery = "inline" | "detached"; +export const ClientRequest__ReviewDelivery = Schema.Literals(["inline", "detached"]).annotate({ + identifier: "ClientRequest__ReviewDelivery", }); +export type ClientRequest__ReviewTarget = + | { readonly type: "uncommittedChanges" } + | { readonly branch: string; readonly type: "baseBranch" } + | { readonly sha: string; readonly title?: string | null; readonly type: "commit" } + | { readonly instructions: string; readonly type: "custom" }; +export const ClientRequest__ReviewTarget = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("uncommittedChanges").annotate({ + title: "UncommittedChangesReviewTargetType", + }), + }).annotate({ + title: "UncommittedChangesReviewTarget", + description: "Review the working tree: staged, unstaged, and untracked files.", + }), + Schema.Struct({ + branch: Schema.String, + type: Schema.Literal("baseBranch").annotate({ title: "BaseBranchReviewTargetType" }), + }).annotate({ + title: "BaseBranchReviewTarget", + description: "Review changes between the current branch and the given base branch.", + }), + Schema.Struct({ + sha: Schema.String, + title: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional human-readable label (e.g., commit subject) for UIs.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("commit").annotate({ title: "CommitReviewTargetType" }), + }).annotate({ + title: "CommitReviewTarget", + description: "Review the changes introduced by a specific commit.", + }), + Schema.Struct({ + instructions: Schema.String, + type: Schema.Literal("custom").annotate({ title: "CustomReviewTargetType" }), + }).annotate({ + title: "CustomReviewTarget", + description: "Arbitrary instructions, equivalent to the old free-form prompt.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ClientRequest__ReviewTarget" }); + export type ClientRequest__ModelListParams = { readonly cursor?: string | null; readonly includeHidden?: boolean | null; @@ -837,17 +973,66 @@ export const ClientRequest__ModelListParams = Schema.Struct({ description: "Optional page size; defaults to a reasonable server-side value.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), -}); +}).annotate({ identifier: "ClientRequest__ModelListParams" }); -export type ClientRequest__ModelProviderCapabilitiesReadParams = {}; -export const ClientRequest__ModelProviderCapabilitiesReadParams = Schema.Struct({}); +export type ClientRequest__ModelProviderCapabilitiesReadParams = { + readonly [x: string]: Schema.Json; +}; +export const ClientRequest__ModelProviderCapabilitiesReadParams = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ identifier: "ClientRequest__ModelProviderCapabilitiesReadParams" }); -export type ClientRequest__PermissionProfileListParams = { +export type ClientRequest__ExperimentalFeatureListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; + readonly threadId?: string | null; +}; +export const ClientRequest__ExperimentalFeatureListParams = Schema.Struct({ + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque pagination cursor returned by a previous call.", + }), + Schema.Null, + ]), + ), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Optional page size; defaults to a reasonable server-side value.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional loaded thread id. Pass this when showing feature state for an existing thread so enablement is computed from that thread's refreshed config, including project-local config for the thread's cwd.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__ExperimentalFeatureListParams" }); + +export type ClientRequest__PermissionProfileListParams = { readonly cursor?: string | null; readonly cwd?: string | null; readonly limit?: number | null; @@ -875,80 +1060,341 @@ export const ClientRequest__PermissionProfileListParams = Schema.Struct({ description: "Optional page size; defaults to the full result set.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), -}); +}).annotate({ identifier: "ClientRequest__PermissionProfileListParams" }); -export type ClientRequest__Personality = "none" | "friendly" | "pragmatic"; -export const ClientRequest__Personality = Schema.Literals(["none", "friendly", "pragmatic"]); +export type ClientRequest__ExperimentalFeatureEnablementSetParams = { + readonly enablement: { readonly [x: string]: boolean }; +}; +export const ClientRequest__ExperimentalFeatureEnablementSetParams = Schema.Struct({ + enablement: Schema.Record(Schema.String, Schema.Boolean).annotate({ + description: + "Process-wide runtime feature enablement keyed by canonical feature name.\n\nOnly named features are updated. Omitted features are left unchanged. Send an empty map for a no-op.", + }), +}).annotate({ identifier: "ClientRequest__ExperimentalFeatureEnablementSetParams" }); -export type ClientRequest__PluginListMarketplaceKind = - | "local" - | "vertical" - | "workspace-directory" - | "shared-with-me" - | "created-by-me-remote"; -export const ClientRequest__PluginListMarketplaceKind = Schema.Literals([ - "local", - "vertical", - "workspace-directory", - "shared-with-me", - "created-by-me-remote", -]); +export type ClientRequest__McpServerOauthClientRegistration = "auto" | "cimd" | "dcr"; +export const ClientRequest__McpServerOauthClientRegistration = Schema.Literals([ + "auto", + "cimd", + "dcr", +]).annotate({ identifier: "ClientRequest__McpServerOauthClientRegistration" }); -export type ClientRequest__PluginShareCheckoutParams = { readonly remotePluginId: string }; -export const ClientRequest__PluginShareCheckoutParams = Schema.Struct({ - remotePluginId: Schema.String, +export type ClientRequest__McpServerStatusDetail = "full" | "toolsAndAuthOnly"; +export const ClientRequest__McpServerStatusDetail = Schema.Literals([ + "full", + "toolsAndAuthOnly", +]).annotate({ identifier: "ClientRequest__McpServerStatusDetail" }); + +export type ClientRequest__McpResourceReadParams = { + readonly connectorId?: string | null; + readonly originCallId?: string | null; + readonly server: string; + readonly threadId?: string | null; + readonly uri: string; +}; +export const ClientRequest__McpResourceReadParams = Schema.Struct({ + connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + originCallId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Originating MCP tool call used to select the resource's app.", + }), + Schema.Null, + ]), + ), + server: Schema.String, + threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + uri: Schema.String, +}).annotate({ identifier: "ClientRequest__McpResourceReadParams" }); + +export type ClientRequest__McpServerToolCallParams = { + readonly _meta?: Schema.Json; + readonly arguments?: Schema.Json; + readonly server: string; + readonly threadId: string; + readonly tool: string; +}; +export const ClientRequest__McpServerToolCallParams = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + arguments: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + server: Schema.String, + threadId: Schema.String, + tool: Schema.String, +}).annotate({ identifier: "ClientRequest__McpServerToolCallParams" }); + +export type ClientRequest__WindowsSandboxSetupMode = "elevated" | "unelevated"; +export const ClientRequest__WindowsSandboxSetupMode = Schema.Literals([ + "elevated", + "unelevated", +]).annotate({ identifier: "ClientRequest__WindowsSandboxSetupMode" }); + +export type ClientRequest__LoginAppBrand = "codex" | "chatgpt"; +export const ClientRequest__LoginAppBrand = Schema.Literals(["codex", "chatgpt"]).annotate({ + identifier: "ClientRequest__LoginAppBrand", }); -export type ClientRequest__PluginShareDeleteParams = { readonly remotePluginId: string }; -export const ClientRequest__PluginShareDeleteParams = Schema.Struct({ - remotePluginId: Schema.String, +export type ClientRequest__CancelLoginAccountParams = { readonly loginId: string }; +export const ClientRequest__CancelLoginAccountParams = Schema.Struct({ + loginId: Schema.String, +}).annotate({ identifier: "ClientRequest__CancelLoginAccountParams" }); + +export type ClientRequest__GetAccountRateLimitsParams = { + readonly excludeResetCreditDetails?: boolean; + readonly supportsLunaReserve?: boolean; +}; +export const ClientRequest__GetAccountRateLimitsParams = Schema.Struct({ + excludeResetCreditDetails: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "Skip the separate reset-credit detail lookup for background usage polls. The usage response still includes the available count; omitted/false preserves detailed reads.", + }), + ), + supportsLunaReserve: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "The client supports automatic Luna Reserve fallback. For eligible ChatGPT CLI users, allow the backend to record experiment exposure after ordinary usage is blocked.", + }), + ), +}).annotate({ + description: + "Usage-read capabilities of the requesting client, never inferred from its experiment arm.", + identifier: "ClientRequest__GetAccountRateLimitsParams", }); -export type ClientRequest__PluginShareDiscoverability = "LISTED" | "UNLISTED" | "PRIVATE"; -export const ClientRequest__PluginShareDiscoverability = Schema.Literals([ - "LISTED", - "UNLISTED", - "PRIVATE", -]); +export type ClientRequest__ConsumeAccountRateLimitResetCreditParams = { + readonly creditId?: string | null; + readonly idempotencyKey: string; +}; +export const ClientRequest__ConsumeAccountRateLimitResetCreditParams = Schema.Struct({ + creditId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Opaque reset-credit identifier to redeem. When omitted, the backend selects the next available credit.", + }), + Schema.Null, + ]), + ), + idempotencyKey: Schema.String.annotate({ + description: + "Identifies one logical reset attempt. A UUID is recommended; reuse the same value when retrying that attempt.", + }), +}).annotate({ identifier: "ClientRequest__ConsumeAccountRateLimitResetCreditParams" }); -export type ClientRequest__PluginShareListParams = {}; -export const ClientRequest__PluginShareListParams = Schema.Struct({}); +export type ClientRequest__GetAccountTokenUsageParams = { readonly threadId?: string | null }; +export const ClientRequest__GetAccountTokenUsageParams = Schema.Struct({ + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "When present, read estimated usage for this thread instead of account-wide token activity.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__GetAccountTokenUsageParams" }); -export type ClientRequest__PluginSharePrincipalType = "user" | "group" | "workspace"; -export const ClientRequest__PluginSharePrincipalType = Schema.Literals([ - "user", - "group", - "workspace", -]); +export type ClientRequest__AddCreditsNudgeCreditType = "credits" | "usage_limit"; +export const ClientRequest__AddCreditsNudgeCreditType = Schema.Literals([ + "credits", + "usage_limit", +]).annotate({ identifier: "ClientRequest__AddCreditsNudgeCreditType" }); -export type ClientRequest__PluginShareTargetRole = "reader" | "editor"; -export const ClientRequest__PluginShareTargetRole = Schema.Literals(["reader", "editor"]); +export type ClientRequest__FeedbackUploadParams = { + readonly classification: string; + 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({ + classification: Schema.String, + extraLogFiles: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + includeLogs: Schema.optionalKey(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({ identifier: "ClientRequest__FeedbackUploadParams" }); -export type ClientRequest__PluginShareUpdateDiscoverability = "UNLISTED" | "PRIVATE" | "LISTED"; -export const ClientRequest__PluginShareUpdateDiscoverability = Schema.Literals([ - "UNLISTED", - "PRIVATE", - "LISTED", -]); +export type ClientRequest__CommandExecTerminalSize = { + readonly cols: number; + readonly rows: number; +}; +export const ClientRequest__CommandExecTerminalSize = Schema.Struct({ + cols: Schema.Number.annotate({ + description: "Terminal width in character cells.", + format: "uint16", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + rows: Schema.Number.annotate({ + description: "Terminal height in character cells.", + format: "uint16", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ + description: "PTY size in character cells for `command/exec` PTY sessions.", + identifier: "ClientRequest__CommandExecTerminalSize", +}); -export type ClientRequest__PluginSkillReadParams = { - readonly remoteMarketplaceName: string; - readonly remotePluginId: string; - readonly skillName: string; +export type ClientRequest__CommandExecWriteParams = { + readonly closeStdin?: boolean; + readonly deltaBase64?: string | null; + readonly processId: string; }; -export const ClientRequest__PluginSkillReadParams = Schema.Struct({ - remoteMarketplaceName: Schema.String, - remotePluginId: Schema.String, - skillName: Schema.String, +export const ClientRequest__CommandExecWriteParams = Schema.Struct({ + closeStdin: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "Close stdin after writing `deltaBase64`, if present.", + }), + ), + deltaBase64: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Optional base64-encoded stdin bytes to write." }), + Schema.Null, + ]), + ), + processId: Schema.String.annotate({ + description: + "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + }), +}).annotate({ + description: "Write stdin bytes to a running `command/exec` session, close stdin, or both.", + identifier: "ClientRequest__CommandExecWriteParams", }); -export type ClientRequest__PluginUninstallParams = { readonly pluginId: string }; -export const ClientRequest__PluginUninstallParams = Schema.Struct({ pluginId: Schema.String }); +export type ClientRequest__CommandExecTerminateParams = { readonly processId: string }; +export const ClientRequest__CommandExecTerminateParams = Schema.Struct({ + processId: Schema.String.annotate({ + description: + "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + }), +}).annotate({ + description: "Terminate a running `command/exec` session.", + identifier: "ClientRequest__CommandExecTerminateParams", +}); + +export type ClientRequest__ConfigReadParams = { + readonly cwd?: string | null; + readonly includeLayers?: boolean; +}; +export const ClientRequest__ConfigReadParams = Schema.Struct({ + cwd: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional working directory to resolve project config layers. If specified, return the effective config as seen from that directory (i.e., including any project layers between `cwd` and the project/repo root).", + }), + Schema.Null, + ]), + ), + includeLayers: Schema.optionalKey(Schema.Boolean), +}).annotate({ identifier: "ClientRequest__ConfigReadParams" }); + +export type ClientRequest__ExternalAgentConfigDetectParams = { + readonly cwds?: ReadonlyArray | null; + readonly includeHome?: boolean; + readonly maxSessionAgeDays?: number | null; + readonly maxSessions?: number | null; + readonly migrationSource?: string | null; + readonly source?: string | null; +}; +export const ClientRequest__ExternalAgentConfigDetectParams = Schema.Struct({ + cwds: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + description: "Zero or more working directories to include for repo-scoped detection.", + }), + Schema.Null, + ]), + ), + includeHome: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "If true, include detection under the user's home directory.", + }), + ), + maxSessionAgeDays: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Maximum age in days for detected sessions. Missing values use the default limit.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + maxSessions: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Maximum number of sessions to detect. Missing values use the default limit.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + migrationSource: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional migration-source selector. Missing or unrecognized values use the default source.", + }), + Schema.Null, + ]), + ), + source: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Deprecated field retained for compatibility. This field is ignored; use `migrationSource` to select the migration source.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__ExternalAgentConfigDetectParams" }); + +export type ClientRequest__CommandMigration = { readonly name: string }; +export const ClientRequest__CommandMigration = Schema.Struct({ name: Schema.String }).annotate({ + identifier: "ClientRequest__CommandMigration", +}); + +export type ClientRequest__HookMigration = { readonly name: string }; +export const ClientRequest__HookMigration = Schema.Struct({ name: Schema.String }).annotate({ + identifier: "ClientRequest__HookMigration", +}); + +export type ClientRequest__McpServerMigration = { readonly name: string }; +export const ClientRequest__McpServerMigration = Schema.Struct({ name: Schema.String }).annotate({ + identifier: "ClientRequest__McpServerMigration", +}); export type ClientRequest__PluginsMigration = { readonly marketplaceName: string; @@ -957,12 +1403,160 @@ export type ClientRequest__PluginsMigration = { export const ClientRequest__PluginsMigration = Schema.Struct({ marketplaceName: Schema.String, pluginNames: Schema.Array(Schema.String), +}).annotate({ identifier: "ClientRequest__PluginsMigration" }); + +export type ClientRequest__SessionMigration = { + readonly cwd: string; + readonly path: string; + readonly title?: string | null; +}; +export const ClientRequest__SessionMigration = Schema.Struct({ + cwd: Schema.String, + path: Schema.String, + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ClientRequest__SessionMigration" }); + +export type ClientRequest__SkillMigration = { readonly name: string }; +export const ClientRequest__SkillMigration = Schema.Struct({ name: Schema.String }).annotate({ + identifier: "ClientRequest__SkillMigration", }); -export type ClientRequest__ReasoningEffort = string; -export const ClientRequest__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +export type ClientRequest__SubagentMigration = { readonly name: string }; +export const ClientRequest__SubagentMigration = Schema.Struct({ name: Schema.String }).annotate({ + identifier: "ClientRequest__SubagentMigration", +}); + +export type ClientRequest__ExternalAgentConfigMigrationItemType = + | "AGENTS_MD" + | "CONFIG" + | "SKILLS" + | "PLUGINS" + | "MCP_SERVER_CONFIG" + | "SUBAGENTS" + | "HOOKS" + | "COMMANDS" + | "MEMORY" + | "SESSIONS"; +export const ClientRequest__ExternalAgentConfigMigrationItemType = Schema.Literals([ + "AGENTS_MD", + "CONFIG", + "SKILLS", + "PLUGINS", + "MCP_SERVER_CONFIG", + "SUBAGENTS", + "HOOKS", + "COMMANDS", + "MEMORY", + "SESSIONS", +]).annotate({ identifier: "ClientRequest__ExternalAgentConfigMigrationItemType" }); + +export type ClientRequest__MergeStrategy = "replace" | "upsert"; +export const ClientRequest__MergeStrategy = Schema.Literals(["replace", "upsert"]).annotate({ + identifier: "ClientRequest__MergeStrategy", +}); + +export type ClientRequest__GetAccountParams = { readonly refreshToken?: boolean }; +export const ClientRequest__GetAccountParams = Schema.Struct({ + refreshToken: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When `true`, requests a proactive token refresh before returning.\n\nIn managed auth mode this triggers the normal refresh-token flow. In external auth mode this flag is ignored. Clients should refresh tokens themselves and call `account/login/start` with `chatgptAuthTokens`.", + }), + ), +}).annotate({ identifier: "ClientRequest__GetAccountParams" }); + +export type ClientRequest__FuzzyFileSearchParams = { + readonly cancellationToken?: string | null; + readonly query: string; + readonly roots: ReadonlyArray; +}; +export const ClientRequest__FuzzyFileSearchParams = Schema.Struct({ + cancellationToken: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + query: Schema.String, + roots: Schema.Array(Schema.String), +}).annotate({ identifier: "ClientRequest__FuzzyFileSearchParams" }); + +export type ClientRequest__AdditionalContextKind = "untrusted" | "application"; +export const ClientRequest__AdditionalContextKind = Schema.Literals([ + "untrusted", + "application", +]).annotate({ identifier: "ClientRequest__AdditionalContextKind" }); + +export type ClientRequest__ModeKind = "plan" | "default"; +export const ClientRequest__ModeKind = Schema.Literals(["plan", "default"]).annotate({ + description: "Initial collaboration mode to use when the TUI starts.", + identifier: "ClientRequest__ModeKind", +}); + +export type ClientRequest__DynamicToolNamespaceTool = { + readonly deferLoading?: boolean; + readonly description: string; + readonly inputSchema: Schema.Json; + readonly name: string; + readonly type: "function"; +}; +export const ClientRequest__DynamicToolNamespaceTool = Schema.Union( + [ + Schema.Struct({ + deferLoading: Schema.optionalKey(Schema.Boolean), + description: Schema.String, + inputSchema: Schema.Json.annotate({ expected: "JSON value" }), + name: Schema.String, + type: Schema.Literal("function").annotate({ title: "FunctionDynamicToolNamespaceToolType" }), + }).annotate({ title: "FunctionDynamicToolNamespaceTool" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ClientRequest__DynamicToolNamespaceTool" }); + +export type ClientRequest__InternalChatMessageMetadataPassthrough = { + readonly turn_id?: string | null; +}; +export const ClientRequest__InternalChatMessageMetadataPassthrough = Schema.Struct({ + turn_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: + "Internal Responses API passthrough metadata copied into underlying chat messages.\n\nResponses API strongly types this payload. Do not modify it without first getting API approval and making the corresponding Responses API change.", + identifier: "ClientRequest__InternalChatMessageMetadataPassthrough", +}); + +export type ClientRequest__MessagePhase = "commentary" | "final_answer"; +export const ClientRequest__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "ClientRequest__MessagePhase", +}); + +export type ClientRequest__AgentMessageInputContent = + | { readonly text: string; readonly type: "input_text" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const ClientRequest__AgentMessageInputContent = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextAgentMessageInputContentType", + }), + }).annotate({ title: "InputTextAgentMessageInputContent" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentAgentMessageInputContentType", + }), + }).annotate({ title: "EncryptedContentAgentMessageInputContent" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ClientRequest__AgentMessageInputContent" }); export type ClientRequest__ReasoningItemContent = | { readonly text: string; readonly type: "reasoning_text" } @@ -981,7 +1575,7 @@ export const ClientRequest__ReasoningItemContent = Schema.Union( }).annotate({ title: "TextReasoningItemContent" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ClientRequest__ReasoningItemContent" }); export type ClientRequest__ReasoningItemReasoningSummary = { readonly text: string; @@ -997,25 +1591,49 @@ export const ClientRequest__ReasoningItemReasoningSummary = Schema.Union( }).annotate({ title: "SummaryTextReasoningItemReasoningSummary" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ClientRequest__ReasoningItemReasoningSummary" }); -export type ClientRequest__ReasoningSummary = "auto" | "concise" | "detailed" | "none"; -export const ClientRequest__ReasoningSummary = Schema.Union( +export type ClientRequest__LocalShellAction = { + readonly command: ReadonlyArray; + readonly env?: { readonly [x: string]: string } | null; + readonly timeout_ms?: number | null; + readonly type: "exec"; + readonly user?: string | null; + readonly working_directory?: string | null; +}; +export const ClientRequest__LocalShellAction = Schema.Union( [ - Schema.Literals(["auto", "concise", "detailed"]), - Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), + Schema.Struct({ + command: Schema.Array(Schema.String), + env: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + timeout_ms: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("exec").annotate({ title: "ExecLocalShellActionType" }), + user: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + working_directory: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "ExecLocalShellAction" }), ], { mode: "oneOf" }, -).annotate({ - description: - "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", -}); +).annotate({ identifier: "ClientRequest__LocalShellAction" }); -export type ClientRequest__RequestId = string | number; -export const ClientRequest__RequestId = Schema.Union([ - Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); +export type ClientRequest__LocalShellStatus = "completed" | "in_progress" | "incomplete"; +export const ClientRequest__LocalShellStatus = Schema.Literals([ + "completed", + "in_progress", + "incomplete", +]).annotate({ identifier: "ClientRequest__LocalShellStatus" }); export type ClientRequest__ResponsesApiWebSearchAction = | { @@ -1055,378 +1673,62 @@ export const ClientRequest__ResponsesApiWebSearchAction = Schema.Union( }).annotate({ title: "OtherResponsesApiWebSearchAction" }), ], { mode: "oneOf" }, -); - -export type ClientRequest__ReviewDelivery = "inline" | "detached"; -export const ClientRequest__ReviewDelivery = Schema.Literals(["inline", "detached"]); +).annotate({ identifier: "ClientRequest__ResponsesApiWebSearchAction" }); -export type ClientRequest__ReviewTarget = - | { readonly type: "uncommittedChanges" } - | { readonly branch: string; readonly type: "baseBranch" } - | { readonly sha: string; readonly title?: string | null; readonly type: "commit" } - | { readonly instructions: string; readonly type: "custom" }; -export const ClientRequest__ReviewTarget = Schema.Union( +export type ClientRequest__CapabilityRootLocation = { + readonly environmentId: string; + readonly path: string; + readonly type: "environment"; +}; +export const ClientRequest__CapabilityRootLocation = Schema.Union( [ Schema.Struct({ - type: Schema.Literal("uncommittedChanges").annotate({ - title: "UncommittedChangesReviewTargetType", - }), - }).annotate({ - title: "UncommittedChangesReviewTarget", - description: "Review the working tree: staged, unstaged, and untracked files.", - }), - Schema.Struct({ - branch: Schema.String, - type: Schema.Literal("baseBranch").annotate({ title: "BaseBranchReviewTargetType" }), - }).annotate({ - title: "BaseBranchReviewTarget", - description: "Review changes between the current branch and the given base branch.", - }), - Schema.Struct({ - sha: Schema.String, - title: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Optional human-readable label (e.g., commit subject) for UIs.", - }), - Schema.Null, - ]), - ), - type: Schema.Literal("commit").annotate({ title: "CommitReviewTargetType" }), - }).annotate({ - title: "CommitReviewTarget", - description: "Review the changes introduced by a specific commit.", - }), - Schema.Struct({ - instructions: Schema.String, - type: Schema.Literal("custom").annotate({ title: "CustomReviewTargetType" }), + environmentId: Schema.String, + path: Schema.String.annotate({ + description: "Absolute path for the root in the selected environment.", + }), + type: Schema.Literal("environment").annotate({ + title: "EnvironmentCapabilityRootLocationType", + }), }).annotate({ - title: "CustomReviewTarget", - description: "Arbitrary instructions, equivalent to the old free-form prompt.", + title: "EnvironmentCapabilityRootLocation", + description: "A path owned by an execution environment.", }), ], { mode: "oneOf" }, -); - -export type ClientRequest__SandboxMode = "read-only" | "workspace-write" | "danger-full-access"; -export const ClientRequest__SandboxMode = Schema.Literals([ - "read-only", - "workspace-write", - "danger-full-access", -]); - -export type ClientRequest__SessionMigration = { - readonly cwd: string; - readonly path: string; - readonly title?: string | null; -}; -export const ClientRequest__SessionMigration = Schema.Struct({ - cwd: Schema.String, - path: Schema.String, - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ClientRequest__SkillMigration = { readonly name: string }; -export const ClientRequest__SkillMigration = Schema.Struct({ name: Schema.String }); - -export type ClientRequest__SkillsListParams = { - readonly cwds?: ReadonlyArray; - readonly forceReload?: boolean; -}; -export const ClientRequest__SkillsListParams = Schema.Struct({ - cwds: Schema.optionalKey( - Schema.Array(Schema.String).annotate({ - description: "When empty, defaults to the current session working directory.", - }), - ), - forceReload: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "When true, bypass the skills cache and re-scan skills from disk.", - }), - ), -}); - -export type ClientRequest__SortDirection = "asc" | "desc"; -export const ClientRequest__SortDirection = Schema.Literals(["asc", "desc"]); - -export type ClientRequest__SubagentMigration = { readonly name: string }; -export const ClientRequest__SubagentMigration = Schema.Struct({ name: Schema.String }); - -export type ClientRequest__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const ClientRequest__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 ClientRequest__ThreadApproveGuardianDeniedActionParams = { - readonly event: unknown; - readonly threadId: string; -}; -export const ClientRequest__ThreadApproveGuardianDeniedActionParams = Schema.Struct({ - event: Schema.Unknown.annotate({ - description: "Serialized `codex_protocol::protocol::GuardianAssessmentEvent`.", - }), - threadId: Schema.String, -}); - -export type ClientRequest__ThreadArchiveParams = { readonly threadId: string }; -export const ClientRequest__ThreadArchiveParams = Schema.Struct({ threadId: Schema.String }); - -export type ClientRequest__ThreadCompactStartParams = { readonly threadId: string }; -export const ClientRequest__ThreadCompactStartParams = Schema.Struct({ threadId: Schema.String }); - -export type ClientRequest__ThreadDeleteParams = { readonly threadId: string }; -export const ClientRequest__ThreadDeleteParams = Schema.Struct({ threadId: Schema.String }); - -export type ClientRequest__ThreadGoalClearParams = { readonly threadId: string }; -export const ClientRequest__ThreadGoalClearParams = Schema.Struct({ threadId: Schema.String }); - -export type ClientRequest__ThreadGoalGetParams = { readonly threadId: string }; -export const ClientRequest__ThreadGoalGetParams = Schema.Struct({ threadId: Schema.String }); - -export type ClientRequest__ThreadGoalStatus = - | "active" - | "paused" - | "blocked" - | "usageLimited" - | "budgetLimited" - | "complete"; -export const ClientRequest__ThreadGoalStatus = Schema.Literals([ - "active", - "paused", - "blocked", - "usageLimited", - "budgetLimited", - "complete", -]); - -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__ThreadListCwdFilter = string | ReadonlyArray; -export const ClientRequest__ThreadListCwdFilter = Schema.Union([ - Schema.String, - Schema.Array(Schema.String), -]); - -export type ClientRequest__ThreadLoadedListParams = { - readonly cursor?: string | null; - readonly limit?: number | null; -}; -export const ClientRequest__ThreadLoadedListParams = Schema.Struct({ - cursor: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Opaque pagination cursor returned by a previous call.", - }), - Schema.Null, - ]), - ), - limit: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ - description: "Optional page size; defaults to no limit.", - format: "uint32", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), -}); - -export type ClientRequest__ThreadMetadataGitInfoUpdateParams = { - readonly branch?: string | null; - readonly originUrl?: string | null; - readonly sha?: string | null; -}; -export const ClientRequest__ThreadMetadataGitInfoUpdateParams = Schema.Struct({ - branch: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Omit to leave the stored branch unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", - }), - Schema.Null, - ]), - ), - originUrl: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Omit to leave the stored origin URL unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", - }), - Schema.Null, - ]), - ), - sha: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Omit to leave the stored commit unchanged, set to `null` to clear it, or provide a non-empty string to replace it.", - }), - Schema.Null, - ]), - ), -}); - -export type ClientRequest__ThreadReadParams = { - readonly includeTurns?: boolean; - readonly threadId: string; -}; -export const ClientRequest__ThreadReadParams = Schema.Struct({ - includeTurns: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "When true, include turns and their items from rollout history.", - }), - ), - threadId: Schema.String, -}); - -export type ClientRequest__ThreadRollbackParams = { - readonly numTurns: number; - readonly threadId: string; -}; -export const ClientRequest__ThreadRollbackParams = Schema.Struct({ - numTurns: Schema.Number.annotate({ - description: - "The number of turns to drop from the end of the thread. Must be >= 1.\n\nThis only modifies the thread's history and does not revert local file changes that have been made by the agent. Clients are responsible for reverting these changes.", - format: "uint32", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - threadId: Schema.String, -}).annotate({ description: "DEPRECATED: `thread/rollback` will be removed soon." }); - -export type ClientRequest__ThreadSetNameParams = { - readonly name: string; - readonly threadId: string; -}; -export const ClientRequest__ThreadSetNameParams = Schema.Struct({ - name: Schema.String, - threadId: Schema.String, -}); - -export type ClientRequest__ThreadShellCommandParams = { - readonly command: string; - readonly threadId: string; -}; -export const ClientRequest__ThreadShellCommandParams = Schema.Struct({ - command: Schema.String.annotate({ - description: - "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.", - }), - threadId: Schema.String, +).annotate({ + description: "Location used to resolve a selected capability root.", + identifier: "ClientRequest__CapabilityRootLocation", }); -export type ClientRequest__ThreadSortKey = "created_at" | "updated_at" | "recency_at"; -export const ClientRequest__ThreadSortKey = Schema.Literals([ - "created_at", - "updated_at", - "recency_at", -]); - -export type ClientRequest__ThreadSource = string; -export const ClientRequest__ThreadSource = Schema.String; - -export type ClientRequest__ThreadSourceKind = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "subAgent" - | "subAgentReview" - | "subAgentCompact" - | "subAgentThreadSpawn" - | "subAgentOther" - | "unknown"; -export const ClientRequest__ThreadSourceKind = Schema.Literals([ - "cli", - "vscode", - "exec", - "appServer", - "subAgent", - "subAgentReview", - "subAgentCompact", - "subAgentThreadSpawn", - "subAgentOther", - "unknown", -]); - -export type ClientRequest__ThreadStartSource = "startup" | "clear"; -export const ClientRequest__ThreadStartSource = Schema.Literals(["startup", "clear"]); - -export type ClientRequest__ThreadUnarchiveParams = { readonly threadId: string }; -export const ClientRequest__ThreadUnarchiveParams = Schema.Struct({ threadId: Schema.String }); - -export type ClientRequest__ThreadUnsubscribeParams = { readonly threadId: string }; -export const ClientRequest__ThreadUnsubscribeParams = Schema.Struct({ threadId: Schema.String }); +export type ClientRequest__ConversationTextRole = "user" | "developer" | "assistant"; +export const ClientRequest__ConversationTextRole = Schema.Literals([ + "user", + "developer", + "assistant", +]).annotate({ identifier: "ClientRequest__ConversationTextRole" }); -export type ClientRequest__TurnInterruptParams = { - readonly threadId: string; - readonly turnId: string; -}; -export const ClientRequest__TurnInterruptParams = Schema.Struct({ - threadId: Schema.String, - turnId: Schema.String, +export type ClientRequest__LegacyAppPathString = string; +export const ClientRequest__LegacyAppPathString = Schema.String.annotate({ + identifier: "ClientRequest__LegacyAppPathString", }); -export type ClientRequest__TurnItemsView = "notLoaded" | "summary" | "full"; -export const ClientRequest__TurnItemsView = Schema.Literals(["notLoaded", "summary", "full"]); - -export type ClientRequest__WindowsSandboxSetupMode = "elevated" | "unelevated"; -export const ClientRequest__WindowsSandboxSetupMode = Schema.Literals(["elevated", "unelevated"]); - -export type CommandExecutionRequestApprovalParams__AbsolutePathBuf = string; -export const CommandExecutionRequestApprovalParams__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 CommandExecutionRequestApprovalParams__LegacyAppPathString = string; +export const CommandExecutionRequestApprovalParams__LegacyAppPathString = Schema.String.annotate({ + identifier: "CommandExecutionRequestApprovalParams__LegacyAppPathString", }); -export type CommandExecutionRequestApprovalParams__AdditionalNetworkPermissions = { - readonly enabled?: boolean | null; -}; -export const CommandExecutionRequestApprovalParams__AdditionalNetworkPermissions = Schema.Struct({ - enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), +export type CommandExecutionRequestApprovalParams__CommandExecutionApprovalKind = + | "command" + | "writeStdin"; +export const CommandExecutionRequestApprovalParams__CommandExecutionApprovalKind = Schema.Literals([ + "command", + "writeStdin", +]).annotate({ + description: "Distinguishes a command approval from input sent to an existing terminal.", + identifier: "CommandExecutionRequestApprovalParams__CommandExecutionApprovalKind", }); -export type CommandExecutionRequestApprovalParams__FileSystemAccessMode = "read" | "write" | "deny"; -export const CommandExecutionRequestApprovalParams__FileSystemAccessMode = Schema.Literals([ - "read", - "write", - "deny", -]); - -export type CommandExecutionRequestApprovalParams__LegacyAppPathString = string; -export const CommandExecutionRequestApprovalParams__LegacyAppPathString = Schema.String; - export type CommandExecutionRequestApprovalParams__NetworkApprovalProtocol = | "http" | "https" @@ -1437,19 +1739,33 @@ export const CommandExecutionRequestApprovalParams__NetworkApprovalProtocol = Sc "https", "socks5Tcp", "socks5Udp", -]); +]).annotate({ identifier: "CommandExecutionRequestApprovalParams__NetworkApprovalProtocol" }); export type CommandExecutionRequestApprovalParams__NetworkPolicyRuleAction = "allow" | "deny"; export const CommandExecutionRequestApprovalParams__NetworkPolicyRuleAction = Schema.Literals([ "allow", "deny", -]); +]).annotate({ identifier: "CommandExecutionRequestApprovalParams__NetworkPolicyRuleAction" }); + +export type CommandExecutionRequestApprovalParams__FileSystemAccessMode = "read" | "write" | "deny"; +export const CommandExecutionRequestApprovalParams__FileSystemAccessMode = Schema.Literals([ + "read", + "write", + "deny", +]).annotate({ identifier: "CommandExecutionRequestApprovalParams__FileSystemAccessMode" }); + +export type CommandExecutionRequestApprovalParams__AdditionalNetworkPermissions = { + readonly enabled?: boolean | null; +}; +export const CommandExecutionRequestApprovalParams__AdditionalNetworkPermissions = Schema.Struct({ + enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), +}).annotate({ identifier: "CommandExecutionRequestApprovalParams__AdditionalNetworkPermissions" }); export type CommandExecutionRequestApprovalResponse__NetworkPolicyRuleAction = "allow" | "deny"; export const CommandExecutionRequestApprovalResponse__NetworkPolicyRuleAction = Schema.Literals([ "allow", "deny", -]); +]).annotate({ identifier: "CommandExecutionRequestApprovalResponse__NetworkPolicyRuleAction" }); export type DynamicToolCallResponse__DynamicToolCallOutputContentItem = | { readonly text: string; readonly type: "inputText" } @@ -1477,7 +1793,12 @@ export const DynamicToolCallResponse__DynamicToolCallOutputContentItem = Schema. }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "DynamicToolCallResponse__DynamicToolCallOutputContentItem" }); + +export type ExecCommandApprovalParams__ThreadId = string; +export const ExecCommandApprovalParams__ThreadId = Schema.String.annotate({ + identifier: "ExecCommandApprovalParams__ThreadId", +}); export type ExecCommandApprovalParams__ParsedCommand = | { readonly cmd: string; readonly name: string; readonly path: string; readonly type: "read" } @@ -1517,34 +1838,41 @@ export const ExecCommandApprovalParams__ParsedCommand = Schema.Union( }).annotate({ title: "UnknownParsedCommand" }), ], { mode: "oneOf" }, -); - -export type ExecCommandApprovalParams__ThreadId = string; -export const ExecCommandApprovalParams__ThreadId = Schema.String; +).annotate({ identifier: "ExecCommandApprovalParams__ParsedCommand" }); export type ExecCommandApprovalResponse__NetworkPolicyRuleAction = "allow" | "deny"; export const ExecCommandApprovalResponse__NetworkPolicyRuleAction = Schema.Literals([ "allow", "deny", -]); +]).annotate({ identifier: "ExecCommandApprovalResponse__NetworkPolicyRuleAction" }); export type FileChangeRequestApprovalResponse__FileChangeApprovalDecision = | "accept" | "acceptForSession" | "decline" | "cancel"; -export const FileChangeRequestApprovalResponse__FileChangeApprovalDecision = Schema.Literals([ - "accept", - "acceptForSession", - "decline", - "cancel", -]); +export const FileChangeRequestApprovalResponse__FileChangeApprovalDecision = Schema.Union( + [ + Schema.Literal("accept").annotate({ description: "User approved the file changes." }), + Schema.Literal("acceptForSession").annotate({ + description: + "User approved the file changes and future changes to the same files should run without prompting.", + }), + Schema.Literal("decline").annotate({ + description: "User denied the file changes. The agent will continue the turn.", + }), + Schema.Literal("cancel").annotate({ + description: "User denied the file changes. The turn will also be immediately interrupted.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "FileChangeRequestApprovalResponse__FileChangeApprovalDecision" }); export type FuzzyFileSearchResponse__FuzzyFileSearchMatchType = "file" | "directory"; export const FuzzyFileSearchResponse__FuzzyFileSearchMatchType = Schema.Literals([ "file", "directory", -]); +]).annotate({ identifier: "FuzzyFileSearchResponse__FuzzyFileSearchMatchType" }); export type FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType = | "file" @@ -1552,50 +1880,36 @@ export type FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType export const FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType = Schema.Literals([ "file", "directory", -]); +]).annotate({ identifier: "FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType" }); export type JSONRPCError__JSONRPCErrorError = { readonly code: number; - readonly data?: unknown; + readonly data?: Schema.Json; readonly message: string; }; export const JSONRPCError__JSONRPCErrorError = Schema.Struct({ - code: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - data: Schema.optionalKey(Schema.Unknown), + code: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + data: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), message: Schema.String, -}); +}).annotate({ identifier: "JSONRPCError__JSONRPCErrorError" }); export type JSONRPCError__RequestId = string | number; export const JSONRPCError__RequestId = Schema.Union([ Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); - -export type JSONRPCMessage__JSONRPCErrorError = { - readonly code: number; - readonly data?: unknown; - readonly message: string; -}; -export const JSONRPCMessage__JSONRPCErrorError = Schema.Struct({ - code: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - data: Schema.optionalKey(Schema.Unknown), - message: Schema.String, -}); - -export type JSONRPCMessage__JSONRPCNotification = { - readonly method: string; - readonly params?: unknown; -}; -export const JSONRPCMessage__JSONRPCNotification = Schema.Struct({ - method: Schema.String, - params: Schema.optionalKey(Schema.Unknown), -}).annotate({ description: "A notification which does not expect a response." }); + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "JSONRPCError__RequestId" }); export type JSONRPCMessage__RequestId = string | number; export const JSONRPCMessage__RequestId = Schema.Union([ Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "JSONRPCMessage__RequestId" }); export type JSONRPCMessage__W3cTraceContext = { readonly traceparent?: string | null; @@ -1604,13 +1918,40 @@ export type JSONRPCMessage__W3cTraceContext = { export const JSONRPCMessage__W3cTraceContext = Schema.Struct({ traceparent: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), tracestate: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "JSONRPCMessage__W3cTraceContext" }); + +export type JSONRPCMessage__JSONRPCNotification = { + readonly method: string; + readonly params?: Schema.Json; +}; +export const JSONRPCMessage__JSONRPCNotification = Schema.Struct({ + method: Schema.String, + params: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ + description: "A notification which does not expect a response.", + identifier: "JSONRPCMessage__JSONRPCNotification", }); +export type JSONRPCMessage__JSONRPCErrorError = { + readonly code: number; + readonly data?: Schema.Json; + readonly message: string; +}; +export const JSONRPCMessage__JSONRPCErrorError = Schema.Struct({ + code: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + data: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + message: Schema.String, +}).annotate({ identifier: "JSONRPCMessage__JSONRPCErrorError" }); + export type JSONRPCRequest__RequestId = string | number; export const JSONRPCRequest__RequestId = Schema.Union([ Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "JSONRPCRequest__RequestId" }); export type JSONRPCRequest__W3cTraceContext = { readonly traceparent?: string | null; @@ -1619,20 +1960,20 @@ export type JSONRPCRequest__W3cTraceContext = { export const JSONRPCRequest__W3cTraceContext = Schema.Struct({ traceparent: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), tracestate: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "JSONRPCRequest__W3cTraceContext" }); export type JSONRPCResponse__RequestId = string | number; export const JSONRPCResponse__RequestId = Schema.Union([ Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); - -export type McpServerElicitationRequestParams__McpElicitationArrayType = "array"; -export const McpServerElicitationRequestParams__McpElicitationArrayType = Schema.Literal("array"); + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "JSONRPCResponse__RequestId" }); -export type McpServerElicitationRequestParams__McpElicitationBooleanType = "boolean"; -export const McpServerElicitationRequestParams__McpElicitationBooleanType = - Schema.Literal("boolean"); +export type McpServerElicitationRequestParams__McpElicitationStringType = "string"; +export const McpServerElicitationRequestParams__McpElicitationStringType = Schema.Literal( + "string", +).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationStringType" }); export type McpServerElicitationRequestParams__McpElicitationConstOption = { readonly const: string; @@ -1641,16 +1982,12 @@ export type McpServerElicitationRequestParams__McpElicitationConstOption = { export const McpServerElicitationRequestParams__McpElicitationConstOption = Schema.Struct({ const: Schema.String, title: Schema.String, -}); - -export type McpServerElicitationRequestParams__McpElicitationNumberType = "number" | "integer"; -export const McpServerElicitationRequestParams__McpElicitationNumberType = Schema.Literals([ - "number", - "integer", -]); +}).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationConstOption" }); -export type McpServerElicitationRequestParams__McpElicitationObjectType = "object"; -export const McpServerElicitationRequestParams__McpElicitationObjectType = Schema.Literal("object"); +export type McpServerElicitationRequestParams__McpElicitationArrayType = "array"; +export const McpServerElicitationRequestParams__McpElicitationArrayType = Schema.Literal( + "array", +).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationArrayType" }); export type McpServerElicitationRequestParams__McpElicitationStringFormat = | "email" @@ -1662,10 +1999,23 @@ export const McpServerElicitationRequestParams__McpElicitationStringFormat = Sch "uri", "date", "date-time", -]); +]).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationStringFormat" }); -export type McpServerElicitationRequestParams__McpElicitationStringType = "string"; -export const McpServerElicitationRequestParams__McpElicitationStringType = Schema.Literal("string"); +export type McpServerElicitationRequestParams__McpElicitationNumberType = "number" | "integer"; +export const McpServerElicitationRequestParams__McpElicitationNumberType = Schema.Literals([ + "number", + "integer", +]).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationNumberType" }); + +export type McpServerElicitationRequestParams__McpElicitationBooleanType = "boolean"; +export const McpServerElicitationRequestParams__McpElicitationBooleanType = Schema.Literal( + "boolean", +).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationBooleanType" }); + +export type McpServerElicitationRequestParams__McpElicitationObjectType = "object"; +export const McpServerElicitationRequestParams__McpElicitationObjectType = Schema.Literal( + "object", +).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationObjectType" }); export type McpServerElicitationRequestResponse__McpServerElicitationAction = | "accept" @@ -1675,256 +2025,231 @@ export const McpServerElicitationRequestResponse__McpServerElicitationAction = S "accept", "decline", "cancel", -]); +]).annotate({ identifier: "McpServerElicitationRequestResponse__McpServerElicitationAction" }); -export type PermissionsRequestApprovalParams__AbsolutePathBuf = string; -export const PermissionsRequestApprovalParams__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 PermissionsRequestApprovalParams__LegacyAppPathString = string; +export const PermissionsRequestApprovalParams__LegacyAppPathString = Schema.String.annotate({ + identifier: "PermissionsRequestApprovalParams__LegacyAppPathString", }); +export type PermissionsRequestApprovalParams__FileSystemAccessMode = "read" | "write" | "deny"; +export const PermissionsRequestApprovalParams__FileSystemAccessMode = Schema.Literals([ + "read", + "write", + "deny", +]).annotate({ identifier: "PermissionsRequestApprovalParams__FileSystemAccessMode" }); + export type PermissionsRequestApprovalParams__AdditionalNetworkPermissions = { readonly enabled?: boolean | null; }; export const PermissionsRequestApprovalParams__AdditionalNetworkPermissions = Schema.Struct({ enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), -}); +}).annotate({ identifier: "PermissionsRequestApprovalParams__AdditionalNetworkPermissions" }); -export type PermissionsRequestApprovalParams__FileSystemAccessMode = "read" | "write" | "deny"; -export const PermissionsRequestApprovalParams__FileSystemAccessMode = Schema.Literals([ +export type PermissionsRequestApprovalResponse__FileSystemAccessMode = "read" | "write" | "deny"; +export const PermissionsRequestApprovalResponse__FileSystemAccessMode = Schema.Literals([ "read", "write", "deny", -]); +]).annotate({ identifier: "PermissionsRequestApprovalResponse__FileSystemAccessMode" }); -export type PermissionsRequestApprovalParams__LegacyAppPathString = string; -export const PermissionsRequestApprovalParams__LegacyAppPathString = Schema.String; +export type PermissionsRequestApprovalResponse__LegacyAppPathString = string; +export const PermissionsRequestApprovalResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "PermissionsRequestApprovalResponse__LegacyAppPathString", +}); export type PermissionsRequestApprovalResponse__AdditionalNetworkPermissions = { readonly enabled?: boolean | null; }; export const PermissionsRequestApprovalResponse__AdditionalNetworkPermissions = Schema.Struct({ enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), -}); +}).annotate({ identifier: "PermissionsRequestApprovalResponse__AdditionalNetworkPermissions" }); -export type PermissionsRequestApprovalResponse__FileSystemAccessMode = "read" | "write" | "deny"; -export const PermissionsRequestApprovalResponse__FileSystemAccessMode = Schema.Literals([ - "read", - "write", - "deny", -]); +export type PermissionsRequestApprovalResponse__PermissionGrantScope = "turn" | "session"; +export const PermissionsRequestApprovalResponse__PermissionGrantScope = Schema.Literals([ + "turn", + "session", +]).annotate({ identifier: "PermissionsRequestApprovalResponse__PermissionGrantScope" }); -export type PermissionsRequestApprovalResponse__LegacyAppPathString = string; -export const PermissionsRequestApprovalResponse__LegacyAppPathString = Schema.String; +export type ServerNotification__NonSteerableTurnKind = "review" | "compact"; +export const ServerNotification__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "ServerNotification__NonSteerableTurnKind" }); + +export type ServerNotification__MisalignmentSteer = { readonly message: string }; +export const ServerNotification__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "ServerNotification__MisalignmentSteer" }); export type ServerNotification__AbsolutePathBuf = string; export const ServerNotification__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.", + identifier: "ServerNotification__AbsolutePathBuf", }); -export type ServerNotification__AccountLoginCompletedNotification = { - readonly error?: string | null; - readonly loginId?: string | null; - readonly success: boolean; +export type ServerNotification__GitInfo = { + readonly branch?: string | null; + readonly originUrl?: string | null; + readonly sha?: string | null; }; -export const ServerNotification__AccountLoginCompletedNotification = Schema.Struct({ - error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - loginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - success: Schema.Boolean, -}); +export const ServerNotification__GitInfo = Schema.Struct({ + branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__GitInfo" }); -export type ServerNotification__ActivePermissionProfile = { - readonly extends?: string | null; - readonly id: string; -}; -export const ServerNotification__ActivePermissionProfile = Schema.Struct({ - extends: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", - }), - Schema.Null, - ]), - ), - id: Schema.String.annotate({ - description: - "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", +export type ServerNotification__ThreadHistoryMode = "legacy" | "paginated"; +export const ServerNotification__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "ServerNotification__ThreadHistoryMode" }); + +export type ServerNotification__ReasoningEffort = string; +export const ServerNotification__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "ServerNotification__ReasoningEffort", }), -}); +); -export type ServerNotification__AdditionalNetworkPermissions = { - readonly enabled?: boolean | null; +export type ServerNotification__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | 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; - readonly threadId: string; - readonly turnId: string; -}; -export const ServerNotification__AgentMessageDeltaNotification = Schema.Struct({ - delta: Schema.String, - itemId: Schema.String, - threadId: Schema.String, - turnId: Schema.String, +export const ServerNotification__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "ServerNotification__ThreadSectionAppearance", }); export type ServerNotification__AgentPath = string; -export const ServerNotification__AgentPath = Schema.String; +export const ServerNotification__AgentPath = Schema.String.annotate({ + identifier: "ServerNotification__AgentPath", +}); -export type ServerNotification__AppBranding = { - readonly category?: string | null; - readonly developer?: string | null; - readonly isDiscoverableApp: boolean; - readonly privacyPolicy?: string | null; - readonly termsOfService?: string | null; - readonly website?: string | null; -}; -export const ServerNotification__AppBranding = Schema.Struct({ - category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - isDiscoverableApp: Schema.Boolean, - privacyPolicy: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - termsOfService: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - website: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}).annotate({ description: "EXPERIMENTAL - app metadata returned by app-list APIs." }); +export type ServerNotification__ThreadId = string; +export const ServerNotification__ThreadId = Schema.String.annotate({ + identifier: "ServerNotification__ThreadId", +}); -export type ServerNotification__AppReview = { readonly status: string }; -export const ServerNotification__AppReview = Schema.Struct({ status: Schema.String }); +export type ServerNotification__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; +export const ServerNotification__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "ServerNotification__ThreadActiveFlag" }); -export type ServerNotification__AppScreenshot = { - readonly fileId?: string | null; - readonly url?: string | null; - readonly userPrompt: string; -}; -export const ServerNotification__AppScreenshot = Schema.Struct({ - fileId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - userPrompt: Schema.String, +export type ServerNotification__ThreadSource = string; +export const ServerNotification__ThreadSource = Schema.String.annotate({ + identifier: "ServerNotification__ThreadSource", }); -export type ServerNotification__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const ServerNotification__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); +export type ServerNotification__ByteRange = { readonly end: number; readonly start: number }; +export const ServerNotification__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "ServerNotification__ByteRange" }); -export type ServerNotification__AskForApproval = - | "untrusted" - | "on-request" - | "never" - | { - readonly granular: { - readonly mcp_elicitations: boolean; - readonly request_permissions?: boolean; - readonly rules: boolean; - readonly sandbox_approval: boolean; - readonly skill_approval?: boolean; - }; - }; -export const ServerNotification__AskForApproval = Schema.Union( - [ - Schema.Literals(["untrusted", "on-request", "never"]), - Schema.Struct({ - granular: Schema.Struct({ - mcp_elicitations: Schema.Boolean, - request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - rules: Schema.Boolean, - sandbox_approval: Schema.Boolean, - skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - }), - }).annotate({ title: "GranularAskForApproval" }), - ], - { mode: "oneOf" }, -); +export type ServerNotification__ImageDetail = "auto" | "low" | "high" | "original"; +export const ServerNotification__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "ServerNotification__ImageDetail" }); -export type ServerNotification__AuthMode = - | "apikey" - | "chatgpt" - | "chatgptAuthTokens" - | "headers" - | "agentIdentity" - | "personalAccessToken" - | "bedrockApiKey"; -export const ServerNotification__AuthMode = Schema.Literals([ - "apikey", - "chatgpt", - "chatgptAuthTokens", - "headers", - "agentIdentity", - "personalAccessToken", - "bedrockApiKey", -]).annotate({ description: "Authentication mode for OpenAI-backed providers." }); +export type ServerNotification__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; +}; +export const ServerNotification__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "ServerNotification__HookPromptFragment" }); -export type ServerNotification__AutoReviewDecisionSource = "agent"; -export const ServerNotification__AutoReviewDecisionSource = Schema.Literal("agent").annotate({ - description: "[UNSTABLE] Source that produced a terminal approval auto-review decision.", +export type ServerNotification__AgentMessageDelivery = "async"; +export const ServerNotification__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "ServerNotification__AgentMessageDelivery", }); -export type ServerNotification__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const ServerNotification__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); - -export type ServerNotification__CommandExecOutputDeltaNotification = { - readonly capReached: boolean; - readonly deltaBase64: string; - readonly processId: string; - readonly stream: "stdout" | "stderr"; +export type ServerNotification__MemoryCitationEntry = { + readonly lineEnd: number; + readonly lineStart: number; + readonly note: string; + readonly path: string; }; -export const ServerNotification__CommandExecOutputDeltaNotification = Schema.Struct({ - capReached: Schema.Boolean.annotate({ - description: - "`true` on the final streamed chunk for a stream when `outputBytesCap` truncated later output on that stream.", - }), - deltaBase64: Schema.String.annotate({ description: "Base64-encoded output bytes." }), - processId: Schema.String.annotate({ - description: - "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", - }), - stream: Schema.Literals(["stdout", "stderr"]).annotate({ - description: "Stream label for `command/exec/outputDelta` notifications.", - }), -}).annotate({ +export const ServerNotification__MemoryCitationEntry = Schema.Struct({ + lineEnd: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + lineStart: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + note: Schema.String, + path: Schema.String, +}).annotate({ identifier: "ServerNotification__MemoryCitationEntry" }); + +export type ServerNotification__MessagePhase = "commentary" | "final_answer"; +export const ServerNotification__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ description: - "Base64-encoded output chunk emitted for a streaming `command/exec` request.\n\nThese notifications are connection-scoped. If the originating connection closes, the server terminates the process.", + '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.', + identifier: "ServerNotification__MessagePhase", }); -export type ServerNotification__CommandExecutionOutputDeltaNotification = { - readonly delta: string; - readonly itemId: string; - readonly threadId: string; - readonly turnId: string; +export type ServerNotification__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; }; -export const ServerNotification__CommandExecutionOutputDeltaNotification = Schema.Struct({ - delta: Schema.String, - itemId: Schema.String, - threadId: Schema.String, - turnId: Schema.String, +export const ServerNotification__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "ServerNotification__AsyncUserInputQuestion" }); + +export type ServerNotification__LegacyAppPathString = string; +export const ServerNotification__LegacyAppPathString = Schema.String.annotate({ + identifier: "ServerNotification__LegacyAppPathString", }); +export type ServerNotification__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const ServerNotification__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "ServerNotification__CommandExecutionSource" }); + export type ServerNotification__CommandExecutionStatus = | "inProgress" | "completed" @@ -1935,43 +2260,83 @@ export const ServerNotification__CommandExecutionStatus = Schema.Literals([ "completed", "failed", "declined", -]); +]).annotate({ identifier: "ServerNotification__CommandExecutionStatus" }); -export type ServerNotification__ContextCompactedNotification = { - readonly threadId: string; - readonly turnId: string; -}; -export const ServerNotification__ContextCompactedNotification = Schema.Struct({ - threadId: Schema.String, - turnId: Schema.String, -}).annotate({ description: "Deprecated: Use `ContextCompaction` item type instead." }); +export type ServerNotification__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const ServerNotification__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" }, +).annotate({ identifier: "ServerNotification__PatchChangeKind" }); -export type ServerNotification__CreditsSnapshot = { - readonly balance?: string | null; - readonly hasCredits: boolean; - readonly unlimited: boolean; +export type ServerNotification__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const ServerNotification__PatchApplyStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]).annotate({ identifier: "ServerNotification__PatchApplyStatus" }); + +export type ServerNotification__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const ServerNotification__CreditsSnapshot = Schema.Struct({ - balance: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - hasCredits: Schema.Boolean, - unlimited: Schema.Boolean, -}); +export const ServerNotification__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__McpToolCallAppContext" }); -export type ServerNotification__DeprecationNoticeNotification = { - readonly details?: string | null; - readonly summary: string; +export type ServerNotification__McpToolCallError = { readonly message: string }; +export const ServerNotification__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "ServerNotification__McpToolCallError" }); + +export type ServerNotification__McpAppDisplayMode = "inline" | "fullscreen"; +export const ServerNotification__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "ServerNotification__McpAppDisplayMode" }); + +export type ServerNotification__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; }; -export const ServerNotification__DeprecationNoticeNotification = Schema.Struct({ - details: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Optional extra guidance, such as migration steps or rationale.", - }), - Schema.Null, - ]), - ), - summary: Schema.String.annotate({ description: "Concise summary of what is deprecated." }), -}); +export const ServerNotification__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "ServerNotification__McpToolCallResult" }); + +export type ServerNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const ServerNotification__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "ServerNotification__McpToolCallStatus" }); export type ServerNotification__DynamicToolCallOutputContentItem = | { readonly text: string; readonly type: "inputText" } @@ -1999,144 +2364,418 @@ export const ServerNotification__DynamicToolCallOutputContentItem = Schema.Union }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerNotification__DynamicToolCallOutputContentItem" }); export type ServerNotification__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; export const ServerNotification__DynamicToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); +]).annotate({ identifier: "ServerNotification__DynamicToolCallStatus" }); -export type ServerNotification__EnvironmentConnectionNotification = { - readonly environmentId: string; - readonly threadId: string; -}; -export const ServerNotification__EnvironmentConnectionNotification = Schema.Struct({ - environmentId: Schema.String, - threadId: Schema.String, -}); +export type ServerNotification__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const ServerNotification__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "ServerNotification__CollabAgentStatus" }); -export type ServerNotification__ExternalAgentConfigMigrationItemType = - | "AGENTS_MD" - | "CONFIG" - | "SKILLS" - | "PLUGINS" - | "MCP_SERVER_CONFIG" - | "SUBAGENTS" - | "HOOKS" - | "COMMANDS" - | "MEMORY" - | "SESSIONS"; -export const ServerNotification__ExternalAgentConfigMigrationItemType = Schema.Literals([ - "AGENTS_MD", - "CONFIG", - "SKILLS", - "PLUGINS", - "MCP_SERVER_CONFIG", - "SUBAGENTS", - "HOOKS", - "COMMANDS", - "MEMORY", - "SESSIONS", -]); +export type ServerNotification__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const ServerNotification__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "interrupted", +]).annotate({ identifier: "ServerNotification__CollabAgentToolCallStatus" }); -export type ServerNotification__FileChangeOutputDeltaNotification = { - readonly delta: string; - readonly itemId: string; - readonly threadId: string; - readonly turnId: string; -}; -export const ServerNotification__FileChangeOutputDeltaNotification = Schema.Struct({ - delta: Schema.String, - itemId: Schema.String, - threadId: Schema.String, - turnId: Schema.String, -}).annotate({ - description: - "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.", -}); +export type ServerNotification__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const ServerNotification__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "ServerNotification__CollabAgentTool" }); -export type ServerNotification__FileSystemAccessMode = "read" | "write" | "deny"; -export const ServerNotification__FileSystemAccessMode = Schema.Literals(["read", "write", "deny"]); +export type ServerNotification__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const ServerNotification__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "ServerNotification__SubAgentActivityKind" }); -export type ServerNotification__FuzzyFileSearchMatchType = "file" | "directory"; -export const ServerNotification__FuzzyFileSearchMatchType = Schema.Literals(["file", "directory"]); +export type ServerNotification__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 ServerNotification__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: "SearchWebSearchActionType" }), + }).annotate({ title: "SearchWebSearchAction" }), + Schema.Struct({ + 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" }, +).annotate({ identifier: "ServerNotification__WebSearchAction" }); -export type ServerNotification__FuzzyFileSearchSessionCompletedNotification = { - readonly sessionId: string; +export type ServerNotification__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; }; -export const ServerNotification__FuzzyFileSearchSessionCompletedNotification = Schema.Struct({ - sessionId: Schema.String, -}); +export const ServerNotification__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ServerNotification__ImageGenerationFailure" }); -export type ServerNotification__GitInfo = { - readonly branch?: string | null; - readonly originUrl?: string | null; - readonly sha?: string | null; -}; -export const ServerNotification__GitInfo = Schema.Struct({ - branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +export type ServerNotification__TurnItemsView = "notLoaded" | "summary" | "full"; +export const ServerNotification__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ServerNotification__TurnItemsView" }); -export type ServerNotification__GuardianApprovalReviewStatus = - | "inProgress" - | "approved" - | "denied" - | "timedOut" - | "aborted"; -export const ServerNotification__GuardianApprovalReviewStatus = Schema.Literals([ +export type ServerNotification__TurnStatus = "completed" | "interrupted" | "failed" | "inProgress"; +export const ServerNotification__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", "inProgress", - "approved", - "denied", - "timedOut", - "aborted", -]).annotate({ description: "[UNSTABLE] Lifecycle state for an approval auto-review." }); +]).annotate({ identifier: "ServerNotification__TurnStatus" }); -export type ServerNotification__GuardianCommandSource = "shell" | "unifiedExec"; -export const ServerNotification__GuardianCommandSource = Schema.Literals(["shell", "unifiedExec"]); +export type ServerNotification__ThreadArchivedNotification = { readonly threadId: string }; +export const ServerNotification__ThreadArchivedNotification = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadArchivedNotification" }); -export type ServerNotification__GuardianRiskLevel = "low" | "medium" | "high" | "critical"; -export const ServerNotification__GuardianRiskLevel = Schema.Literals([ - "low", - "medium", - "high", - "critical", -]).annotate({ description: "[UNSTABLE] Risk level assigned by approval auto-review." }); +export type ServerNotification__ThreadDeletedNotification = { readonly threadId: string }; +export const ServerNotification__ThreadDeletedNotification = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadDeletedNotification" }); -export type ServerNotification__GuardianUserAuthorization = "unknown" | "low" | "medium" | "high"; -export const ServerNotification__GuardianUserAuthorization = Schema.Literals([ - "unknown", - "low", - "medium", - "high", -]).annotate({ description: "[UNSTABLE] Authorization level assigned by approval auto-review." }); +export type ServerNotification__ThreadUnarchivedNotification = { readonly threadId: string }; +export const ServerNotification__ThreadUnarchivedNotification = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadUnarchivedNotification" }); -export type ServerNotification__GuardianWarningNotification = { - readonly message: string; +export type ServerNotification__ThreadClosedNotification = { readonly threadId: string }; +export const ServerNotification__ThreadClosedNotification = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadClosedNotification" }); + +export type ServerNotification__ThreadRevertedNotification = { readonly threadId: string }; +export const ServerNotification__ThreadRevertedNotification = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadRevertedNotification" }); + +export type ServerNotification__SkillsChangedNotification = { readonly [x: string]: Schema.Json }; +export const ServerNotification__SkillsChangedNotification = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ + description: + "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.", + identifier: "ServerNotification__SkillsChangedNotification", +}); + +export type ServerNotification__ThreadNameUpdatedNotification = { readonly threadId: string; + readonly threadName?: string | null; }; -export const ServerNotification__GuardianWarningNotification = Schema.Struct({ - message: Schema.String.annotate({ - description: "Concise guardian warning message for the user.", - }), - threadId: Schema.String.annotate({ description: "Thread target for the guardian warning." }), +export const ServerNotification__ThreadNameUpdatedNotification = Schema.Struct({ + threadId: Schema.String, + threadName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__ThreadNameUpdatedNotification" }); + +export type ServerNotification__ThreadAttachmentOperation = "created" | "deleted"; +export const ServerNotification__ThreadAttachmentOperation = Schema.Literals([ + "created", + "deleted", +]).annotate({ + description: "The persisted attachment change represented by a notification.", + identifier: "ServerNotification__ThreadAttachmentOperation", }); -export type ServerNotification__HookEventName = - | "preToolUse" - | "permissionRequest" - | "postToolUse" - | "preCompact" - | "postCompact" - | "sessionStart" +export type ServerNotification__ThreadGoalStatus = + | "active" + | "paused" + | "blocked" + | "usageLimited" + | "budgetLimited" + | "complete"; +export const ServerNotification__ThreadGoalStatus = Schema.Literals([ + "active", + "paused", + "blocked", + "usageLimited", + "budgetLimited", + "complete", +]).annotate({ identifier: "ServerNotification__ThreadGoalStatus" }); + +export type ServerNotification__ThreadGoalClearedNotification = { readonly threadId: string }; +export const ServerNotification__ThreadGoalClearedNotification = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadGoalClearedNotification" }); + +export type ServerNotification__ThreadQueueChangedNotification = { readonly threadId: string }; +export const ServerNotification__ThreadQueueChangedNotification = Schema.Struct({ + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadQueueChangedNotification" }); + +export type ServerNotification__ProjectChangeType = "created" | "updated" | "deleted"; +export const ServerNotification__ProjectChangeType = Schema.Literals([ + "created", + "updated", + "deleted", +]).annotate({ identifier: "ServerNotification__ProjectChangeType" }); + +export type ServerNotification__ThreadProjectUpdatedNotification = { + readonly projectId: string | null; + readonly threadId: string; +}; +export const ServerNotification__ThreadProjectUpdatedNotification = Schema.Struct({ + projectId: Schema.Union([Schema.String, Schema.Null]), + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadProjectUpdatedNotification" }); + +export type ServerNotification__EnvironmentConnectionNotification = { + readonly environmentId: string; + readonly threadId: string; +}; +export const ServerNotification__EnvironmentConnectionNotification = Schema.Struct({ + environmentId: Schema.String, + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__EnvironmentConnectionNotification" }); + +export type ServerNotification__ActivePermissionProfile = { + readonly extends?: string | null; + readonly id: string; +}; +export const ServerNotification__ActivePermissionProfile = Schema.Struct({ + extends: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", + }), + Schema.Null, + ]), + ), + id: Schema.String.annotate({ + description: + "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", + }), +}).annotate({ identifier: "ServerNotification__ActivePermissionProfile" }); + +export type ServerNotification__AskForApproval = + | "untrusted" + | "on-request" + | "never" + | { + readonly granular: { + readonly mcp_elicitations: boolean; + readonly request_permissions?: boolean; + readonly rules: boolean; + readonly sandbox_approval: boolean; + readonly skill_approval?: boolean; + }; + }; +export const ServerNotification__AskForApproval = Schema.Union( + [ + Schema.Literals(["untrusted", "on-request", "never"]), + Schema.Struct({ + granular: Schema.Struct({ + mcp_elicitations: Schema.Boolean, + request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + rules: Schema.Boolean, + sandbox_approval: Schema.Boolean, + skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + }), + }).annotate({ title: "GranularAskForApproval" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ServerNotification__AskForApproval" }); + +export type ServerNotification__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const ServerNotification__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "ServerNotification__ApprovalsReviewer", +}); + +export type ServerNotification__ModeKind = "plan" | "default"; +export const ServerNotification__ModeKind = Schema.Literals(["plan", "default"]).annotate({ + description: "Initial collaboration mode to use when the TUI starts.", + identifier: "ServerNotification__ModeKind", +}); + +export type ServerNotification__Personality = "none" | "friendly" | "pragmatic"; +export const ServerNotification__Personality = Schema.Literals([ + "none", + "friendly", + "pragmatic", +]).annotate({ + description: "Deprecated: `friendly` and `pragmatic` no longer select a style.", + identifier: "ServerNotification__Personality", +}); + +export type ServerNotification__NetworkAccess = "restricted" | "enabled"; +export const ServerNotification__NetworkAccess = Schema.Literals([ + "restricted", + "enabled", +]).annotate({ identifier: "ServerNotification__NetworkAccess" }); + +export type ServerNotification__ReasoningSummary = "auto" | "concise" | "detailed" | "none"; +export const ServerNotification__ReasoningSummary = Schema.Union( + [ + Schema.Literals(["auto", "concise", "detailed"]), + Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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", + identifier: "ServerNotification__ReasoningSummary", +}); + +export type ServerNotification__TokenUsageBreakdown = { + readonly cacheWriteInputTokens?: number; + readonly cachedInputTokens: number; + readonly inputTokens: number; + readonly outputTokens: number; + readonly reasoningOutputTokens: number; + readonly totalTokens: number; +}; +export const ServerNotification__TokenUsageBreakdown = Schema.Struct({ + cacheWriteInputTokens: Schema.optionalKey( + Schema.Number.annotate({ default: 0, format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + ), + cachedInputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + inputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + outputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + reasoningOutputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + totalTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +}).annotate({ identifier: "ServerNotification__TokenUsageBreakdown" }); + +export type ServerNotification__HookOutputEntryKind = + | "warning" + | "stop" + | "feedback" + | "context" + | "error"; +export const ServerNotification__HookOutputEntryKind = Schema.Literals([ + "warning", + "stop", + "feedback", + "context", + "error", +]).annotate({ identifier: "ServerNotification__HookOutputEntryKind" }); + +export type ServerNotification__HookEventName = + | "preToolUse" + | "permissionRequest" + | "postToolUse" + | "preCompact" + | "postCompact" + | "sessionStart" | "sessionEnd" | "userPromptSubmit" | "subagentStart" | "subagentStop" - | "stop"; + | "stop" + | "interrupt"; export const ServerNotification__HookEventName = Schema.Literals([ "preToolUse", "permissionRequest", @@ -2149,37 +2788,53 @@ export const ServerNotification__HookEventName = Schema.Literals([ "subagentStart", "subagentStop", "stop", -]); + "interrupt", +]).annotate({ identifier: "ServerNotification__HookEventName" }); export type ServerNotification__HookExecutionMode = "sync" | "async"; -export const ServerNotification__HookExecutionMode = Schema.Literals(["sync", "async"]); - -export type ServerNotification__HookHandlerType = "command" | "prompt" | "agent"; -export const ServerNotification__HookHandlerType = Schema.Literals(["command", "prompt", "agent"]); +export const ServerNotification__HookExecutionMode = Schema.Literals(["sync", "async"]).annotate({ + identifier: "ServerNotification__HookExecutionMode", +}); -export type ServerNotification__HookOutputEntryKind = - | "warning" - | "stop" - | "feedback" - | "context" - | "error"; -export const ServerNotification__HookOutputEntryKind = Schema.Literals([ - "warning", - "stop", - "feedback", - "context", - "error", -]); +export type ServerNotification__HookHandlerType = "command" | "mcpTool" | "prompt" | "agent"; +export const ServerNotification__HookHandlerType = Schema.Literals([ + "command", + "mcpTool", + "prompt", + "agent", +]).annotate({ identifier: "ServerNotification__HookHandlerType" }); -export type ServerNotification__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; -}; -export const ServerNotification__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export type ServerNotification__HookScope = "thread" | "turn"; +export const ServerNotification__HookScope = Schema.Literals(["thread", "turn"]).annotate({ + identifier: "ServerNotification__HookScope", }); +export type ServerNotification__HookSource = + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "plugin" + | "cloudRequirements" + | "cloudManagedConfig" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; +export const ServerNotification__HookSource = Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "plugin", + "cloudRequirements", + "cloudManagedConfig", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", +]).annotate({ identifier: "ServerNotification__HookSource" }); + export type ServerNotification__HookRunStatus = | "running" | "completed" @@ -2192,151 +2847,35 @@ export const ServerNotification__HookRunStatus = Schema.Literals([ "failed", "blocked", "stopped", -]); - -export type ServerNotification__HookScope = "thread" | "turn"; -export const ServerNotification__HookScope = Schema.Literals(["thread", "turn"]); - -export type ServerNotification__ImageDetail = "auto" | "low" | "high" | "original"; -export const ServerNotification__ImageDetail = Schema.Literals(["auto", "low", "high", "original"]); - -export type ServerNotification__LegacyAppPathString = string; -export const ServerNotification__LegacyAppPathString = Schema.String; - -export type ServerNotification__McpServerOauthLoginCompletedNotification = { - readonly error?: string | null; - readonly name: string; - readonly success: boolean; - readonly threadId?: string | null; -}; -export const ServerNotification__McpServerOauthLoginCompletedNotification = Schema.Struct({ - error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - name: Schema.String, - success: Schema.Boolean, - threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ServerNotification__McpServerStartupFailureReason = "reauthenticationRequired"; -export const ServerNotification__McpServerStartupFailureReason = Schema.Literal( - "reauthenticationRequired", -); - -export type ServerNotification__McpServerStartupState = - | "starting" - | "ready" - | "failed" - | "cancelled"; -export const ServerNotification__McpServerStartupState = Schema.Literals([ - "starting", - "ready", - "failed", - "cancelled", -]); - -export type ServerNotification__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; -}; -export const ServerNotification__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ServerNotification__McpToolCallError = { readonly message: string }; -export const ServerNotification__McpToolCallError = Schema.Struct({ message: Schema.String }); +]).annotate({ identifier: "ServerNotification__HookRunStatus" }); -export type ServerNotification__McpToolCallProgressNotification = { - readonly itemId: string; - readonly message: string; +export type ServerNotification__TurnDiffUpdatedNotification = { + readonly diff: string; readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__McpToolCallProgressNotification = Schema.Struct({ - itemId: Schema.String, - message: Schema.String, +export const ServerNotification__TurnDiffUpdatedNotification = Schema.Struct({ + diff: Schema.String, threadId: Schema.String, turnId: Schema.String, +}).annotate({ + description: + "Notification that the turn-level unified diff has changed. Contains the latest aggregated diff across all file changes in the turn.", + identifier: "ServerNotification__TurnDiffUpdatedNotification", }); -export type ServerNotification__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const ServerNotification__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); - -export type ServerNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const ServerNotification__McpToolCallStatus = Schema.Literals([ +export type ServerNotification__TurnPlanStepStatus = "pending" | "inProgress" | "completed"; +export const ServerNotification__TurnPlanStepStatus = Schema.Literals([ + "pending", "inProgress", "completed", - "failed", -]); - -export type ServerNotification__MemoryCitationEntry = { - readonly lineEnd: number; - readonly lineStart: number; - readonly note: string; - readonly path: string; -}; -export const ServerNotification__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 ServerNotification__MessagePhase = "commentary" | "final_answer"; -export const ServerNotification__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 ServerNotification__ModeKind = "plan" | "default"; -export const ServerNotification__ModeKind = Schema.Literals(["plan", "default"]).annotate({ - description: "Initial collaboration mode to use when the TUI starts.", -}); - -export type ServerNotification__ModelRerouteReason = "highRiskCyberActivity"; -export const ServerNotification__ModelRerouteReason = Schema.Literal("highRiskCyberActivity"); - -export type ServerNotification__ModelSafetyBufferingUpdatedNotification = { - readonly fasterModel?: string | null; - readonly model: string; - readonly reasons: ReadonlyArray; - readonly showBufferingUi: boolean; - readonly threadId: string; - readonly turnId: string; - readonly useCases: ReadonlyArray; -}; -export const ServerNotification__ModelSafetyBufferingUpdatedNotification = Schema.Struct({ - fasterModel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - model: Schema.String, - reasons: Schema.Array(Schema.String), - showBufferingUi: Schema.Boolean, - threadId: Schema.String, - turnId: Schema.String, - useCases: Schema.Array(Schema.String), -}); +]).annotate({ identifier: "ServerNotification__TurnPlanStepStatus" }); -export type ServerNotification__ModelVerification = "trustedAccessForCyber"; -export const ServerNotification__ModelVerification = Schema.Literal("trustedAccessForCyber"); +export type ServerNotification__GuardianCommandSource = "shell" | "unifiedExec"; +export const ServerNotification__GuardianCommandSource = Schema.Literals([ + "shell", + "unifiedExec", +]).annotate({ identifier: "ServerNotification__GuardianCommandSource" }); export type ServerNotification__NetworkApprovalProtocol = | "http" @@ -2348,45 +2887,93 @@ export const ServerNotification__NetworkApprovalProtocol = Schema.Literals([ "https", "socks5Tcp", "socks5Udp", -]); +]).annotate({ identifier: "ServerNotification__NetworkApprovalProtocol" }); -export type ServerNotification__NonSteerableTurnKind = "review" | "compact"; -export const ServerNotification__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type ServerNotification__FileSystemAccessMode = "read" | "write" | "deny"; +export const ServerNotification__FileSystemAccessMode = Schema.Literals([ + "read", + "write", + "deny", +]).annotate({ identifier: "ServerNotification__FileSystemAccessMode" }); -export type ServerNotification__PatchApplyStatus = +export type ServerNotification__AdditionalNetworkPermissions = { + readonly enabled?: boolean | null; +}; +export const ServerNotification__AdditionalNetworkPermissions = Schema.Struct({ + enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), +}).annotate({ identifier: "ServerNotification__AdditionalNetworkPermissions" }); + +export type ServerNotification__GuardianRiskLevel = "low" | "medium" | "high" | "critical"; +export const ServerNotification__GuardianRiskLevel = Schema.Literals([ + "low", + "medium", + "high", + "critical", +]).annotate({ + description: "[UNSTABLE] Risk level assigned by approval auto-review.", + identifier: "ServerNotification__GuardianRiskLevel", +}); + +export type ServerNotification__GuardianApprovalReviewStatus = | "inProgress" - | "completed" - | "failed" - | "declined"; -export const ServerNotification__PatchApplyStatus = Schema.Literals([ + | "approved" + | "denied" + | "timedOut" + | "aborted"; +export const ServerNotification__GuardianApprovalReviewStatus = Schema.Literals([ "inProgress", - "completed", - "failed", - "declined", -]); + "approved", + "denied", + "timedOut", + "aborted", +]).annotate({ + description: "[UNSTABLE] Lifecycle state for an approval auto-review.", + identifier: "ServerNotification__GuardianApprovalReviewStatus", +}); -export type ServerNotification__PatchChangeKind = - | { readonly type: "add" } - | { readonly type: "delete" } - | { readonly move_path?: string | null; readonly type: "update" }; -export const ServerNotification__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 ServerNotification__GuardianUserAuthorization = "unknown" | "low" | "medium" | "high"; +export const ServerNotification__GuardianUserAuthorization = Schema.Literals([ + "unknown", + "low", + "medium", + "high", +]).annotate({ + description: "[UNSTABLE] Authorization level assigned by approval auto-review.", + identifier: "ServerNotification__GuardianUserAuthorization", +}); -export type ServerNotification__Personality = "none" | "friendly" | "pragmatic"; -export const ServerNotification__Personality = Schema.Literals(["none", "friendly", "pragmatic"]); +export type ServerNotification__AutoReviewDecisionSource = "agent"; +export const ServerNotification__AutoReviewDecisionSource = Schema.Literal("agent").annotate({ + description: "[UNSTABLE] Source that produced a terminal approval auto-review decision.", + identifier: "ServerNotification__AutoReviewDecisionSource", +}); + +export type ServerNotification__StrictReviewRequiredNotification = { + readonly startedAtMs: number; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerNotification__StrictReviewRequiredNotification = Schema.Struct({ + startedAtMs: Schema.Number.annotate({ + description: "Unix timestamp (in milliseconds) when this review started.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__StrictReviewRequiredNotification" }); + +export type ServerNotification__AgentMessageDeltaNotification = { + readonly delta: string; + readonly itemId: string; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerNotification__AgentMessageDeltaNotification = Schema.Struct({ + delta: Schema.String, + itemId: Schema.String, + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__AgentMessageDeltaNotification" }); export type ServerNotification__PlanDeltaNotification = { readonly delta: string; @@ -2402,45 +2989,36 @@ export const ServerNotification__PlanDeltaNotification = Schema.Struct({ }).annotate({ description: "EXPERIMENTAL - proposed plan streaming deltas for plan items. Clients should not assume concatenated deltas match the completed plan item content.", + identifier: "ServerNotification__PlanDeltaNotification", }); -export type ServerNotification__PlanType = - | "free" - | "go" - | "plus" - | "pro" - | "prolite" - | "team" - | "self_serve_business_prolite" - | "self_serve_business_usage_based" - | "business" - | "ent26" - | "enterprise_cbp_automation" - | "enterprise_cbp_usage_based" - | "enterprise" - | "edu" - | "edu_plus" - | "edu_pro" - | "unknown"; -export const ServerNotification__PlanType = Schema.Literals([ - "free", - "go", - "plus", - "pro", - "prolite", - "team", - "self_serve_business_prolite", - "self_serve_business_usage_based", - "business", - "ent26", - "enterprise_cbp_automation", - "enterprise_cbp_usage_based", - "enterprise", - "edu", - "edu_plus", - "edu_pro", - "unknown", -]); +export type ServerNotification__CommandExecOutputStream = "stdout" | "stderr"; +export const ServerNotification__CommandExecOutputStream = Schema.Union( + [ + Schema.Literal("stdout").annotate({ + description: "stdout stream. PTY mode multiplexes terminal output here.", + }), + Schema.Literal("stderr").annotate({ description: "stderr stream." }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Stream label for `command/exec/outputDelta` notifications.", + identifier: "ServerNotification__CommandExecOutputStream", +}); + +export type ServerNotification__ProcessOutputStream = "stdout" | "stderr"; +export const ServerNotification__ProcessOutputStream = Schema.Union( + [ + Schema.Literal("stdout").annotate({ + description: "stdout stream. PTY mode multiplexes terminal output here.", + }), + Schema.Literal("stderr").annotate({ description: "stderr stream." }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Stream label for `process/outputDelta` notifications.", + identifier: "ServerNotification__ProcessOutputStream", +}); export type ServerNotification__ProcessExitedNotification = { readonly exitCode: number; @@ -2452,7 +3030,7 @@ export type ServerNotification__ProcessExitedNotification = { }; export const ServerNotification__ProcessExitedNotification = Schema.Struct({ exitCode: Schema.Number.annotate({ description: "Process exit code.", format: "int32" }).check( - Schema.isInt(), + Schema.isInt().annotate({ expected: "an integer" }), ), processHandle: Schema.String.annotate({ description: "Client-supplied, connection-scoped `processHandle` from `process/spawn`.", @@ -2473,438 +3051,451 @@ export const ServerNotification__ProcessExitedNotification = Schema.Struct({ description: "Whether stdout reached `outputBytesCap`.\n\nIn streaming mode, stdout is empty and cap state is also reported on the final stdout `process/outputDelta` notification.", }), -}).annotate({ description: "Final process exit notification for `process/spawn`." }); - -export type ServerNotification__ProcessOutputDeltaNotification = { - readonly capReached: boolean; - readonly deltaBase64: string; - readonly processHandle: string; - readonly stream: "stdout" | "stderr"; -}; -export const ServerNotification__ProcessOutputDeltaNotification = Schema.Struct({ - capReached: Schema.Boolean.annotate({ - description: - "True on the final streamed chunk for this stream when output was truncated by `outputBytesCap`.", - }), - deltaBase64: Schema.String.annotate({ description: "Base64-encoded output bytes." }), - processHandle: Schema.String.annotate({ - description: "Client-supplied, connection-scoped `processHandle` from `process/spawn`.", - }), - stream: Schema.Literals(["stdout", "stderr"]).annotate({ - description: "Stream label for `process/outputDelta` notifications.", - }), }).annotate({ - description: "Base64-encoded output chunk emitted for a streaming `process/spawn` request.", + description: "Final process exit notification for `process/spawn`.", + identifier: "ServerNotification__ProcessExitedNotification", }); -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; - readonly windowDurationMins?: number | null; -}; -export const ServerNotification__RateLimitWindow = Schema.Struct({ - resetsAt: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - usedPercent: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), - windowDurationMins: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), -}); - -export type ServerNotification__RealtimeConversationVersion = "v1" | "v2" | "v3"; -export const ServerNotification__RealtimeConversationVersion = Schema.Literals(["v1", "v2", "v3"]); - -export type ServerNotification__ReasoningEffort = string; -export const ServerNotification__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); - -export type ServerNotification__ReasoningSummary = "auto" | "concise" | "detailed" | "none"; -export const ServerNotification__ReasoningSummary = Schema.Union( - [ - Schema.Literals(["auto", "concise", "detailed"]), - Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), - ], - { mode: "oneOf" }, -).annotate({ - description: - "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 ServerNotification__ReasoningSummaryPartAddedNotification = { +export type ServerNotification__CommandExecutionOutputDeltaNotification = { + readonly delta: string; readonly itemId: string; - readonly summaryIndex: number; readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__ReasoningSummaryPartAddedNotification = Schema.Struct({ +export const ServerNotification__CommandExecutionOutputDeltaNotification = Schema.Struct({ + delta: Schema.String, itemId: Schema.String, - summaryIndex: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), threadId: Schema.String, turnId: Schema.String, -}); +}).annotate({ identifier: "ServerNotification__CommandExecutionOutputDeltaNotification" }); -export type ServerNotification__ReasoningSummaryTextDeltaNotification = { - readonly delta: string; +export type ServerNotification__TerminalInteractionNotification = { readonly itemId: string; - readonly summaryIndex: number; + readonly processId: string; + readonly stdin: string; readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__ReasoningSummaryTextDeltaNotification = Schema.Struct({ - delta: Schema.String, +export const ServerNotification__TerminalInteractionNotification = Schema.Struct({ itemId: Schema.String, - summaryIndex: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + processId: Schema.String, + stdin: Schema.String, threadId: Schema.String, turnId: Schema.String, -}); +}).annotate({ identifier: "ServerNotification__TerminalInteractionNotification" }); -export type ServerNotification__ReasoningTextDeltaNotification = { - readonly contentIndex: number; +export type ServerNotification__FileChangeOutputDeltaNotification = { readonly delta: string; readonly itemId: string; readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__ReasoningTextDeltaNotification = Schema.Struct({ - contentIndex: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), +export const ServerNotification__FileChangeOutputDeltaNotification = Schema.Struct({ delta: Schema.String, itemId: Schema.String, threadId: Schema.String, turnId: Schema.String, +}).annotate({ + description: + "Deprecated legacy notification for `apply_patch` textual output.\n\nThe server no longer emits this notification.", + identifier: "ServerNotification__FileChangeOutputDeltaNotification", }); -export type ServerNotification__RemoteControlConnectionStatus = - | "disabled" - | "connecting" - | "connected" - | "errored"; -export const ServerNotification__RemoteControlConnectionStatus = Schema.Literals([ - "disabled", - "connecting", - "connected", - "errored", -]); - export type ServerNotification__RequestId = string | number; export const ServerNotification__RequestId = Schema.Union([ Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); - -export type ServerNotification__SkillsChangedNotification = {}; -export const ServerNotification__SkillsChangedNotification = Schema.Struct({}).annotate({ - description: - "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.", -}); - -export type ServerNotification__SpendControlLimitSnapshot = { - readonly limit: string; - readonly remainingPercent: number; - readonly resetsAt: number; - readonly used: string; -}; -export const ServerNotification__SpendControlLimitSnapshot = Schema.Struct({ - limit: Schema.String, - remainingPercent: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), - resetsAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - used: Schema.String, -}); - -export type ServerNotification__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const ServerNotification__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", - "completed", -]); + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "ServerNotification__RequestId" }); -export type ServerNotification__TerminalInteractionNotification = { +export type ServerNotification__McpToolCallProgressNotification = { readonly itemId: string; - readonly processId: string; - readonly stdin: string; + readonly message: string; readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__TerminalInteractionNotification = Schema.Struct({ +export const ServerNotification__McpToolCallProgressNotification = Schema.Struct({ itemId: Schema.String, - processId: Schema.String, - stdin: Schema.String, + message: Schema.String, threadId: Schema.String, turnId: Schema.String, -}); +}).annotate({ identifier: "ServerNotification__McpToolCallProgressNotification" }); -export type ServerNotification__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type ServerNotification__McpServerOauthLoginCompletedNotification = { + readonly error?: string | null; + readonly name: string; + readonly success: boolean; + readonly threadId?: string | null; }; -export const ServerNotification__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 ServerNotification__TextPosition = { readonly column: number; readonly line: number }; -export const ServerNotification__TextPosition = Schema.Struct({ - column: Schema.Number.annotate({ - description: "1-based column number (in Unicode scalar values).", - format: "uint", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - line: Schema.Number.annotate({ description: "1-based line number.", format: "uint" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}); - -export type ServerNotification__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; -export const ServerNotification__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export const ServerNotification__McpServerOauthLoginCompletedNotification = Schema.Struct({ + error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + name: Schema.String, + success: Schema.Boolean, + threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__McpServerOauthLoginCompletedNotification" }); -export type ServerNotification__ThreadArchivedNotification = { readonly threadId: string }; -export const ServerNotification__ThreadArchivedNotification = Schema.Struct({ - threadId: Schema.String, -}); +export type ServerNotification__McpServerStartupFailureReason = "reauthenticationRequired"; +export const ServerNotification__McpServerStartupFailureReason = Schema.Literal( + "reauthenticationRequired", +).annotate({ identifier: "ServerNotification__McpServerStartupFailureReason" }); -export type ServerNotification__ThreadClosedNotification = { readonly threadId: string }; -export const ServerNotification__ThreadClosedNotification = Schema.Struct({ - threadId: Schema.String, -}); +export type ServerNotification__McpServerStartupState = + | "starting" + | "ready" + | "failed" + | "cancelled"; +export const ServerNotification__McpServerStartupState = Schema.Literals([ + "starting", + "ready", + "failed", + "cancelled", +]).annotate({ identifier: "ServerNotification__McpServerStartupState" }); -export type ServerNotification__ThreadDeletedNotification = { readonly threadId: string }; -export const ServerNotification__ThreadDeletedNotification = Schema.Struct({ - threadId: Schema.String, -}); +export type ServerNotification__McpServerEventNotification = { + readonly method: string; + readonly params: Schema.Json; +}; +export const ServerNotification__McpServerEventNotification = Schema.Struct({ + method: Schema.String, + params: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ identifier: "ServerNotification__McpServerEventNotification" }); -export type ServerNotification__ThreadGoalClearedNotification = { readonly threadId: string }; -export const ServerNotification__ThreadGoalClearedNotification = Schema.Struct({ - threadId: Schema.String, +export type ServerNotification__AuthMode = + | "apikey" + | "chatgpt" + | "chatgptAuthTokens" + | "headers" + | "agentIdentity" + | "personalAccessToken" + | "bedrockApiKey" + | "bedrockAccessKeys"; +export const ServerNotification__AuthMode = Schema.Union( + [ + Schema.Literal("apikey").annotate({ + description: "OpenAI API key provided by the caller and stored by Codex.", + }), + Schema.Literal("chatgpt").annotate({ + description: "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + }), + Schema.Literal("chatgptAuthTokens").annotate({ + description: + "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + }), + Schema.Literal("headers").annotate({ + description: "Backend auth supplied as request headers.", + }), + Schema.Literal("agentIdentity").annotate({ + description: "Programmatic Codex auth backed by a registered Agent Identity.", + }), + Schema.Literal("personalAccessToken").annotate({ + description: "Programmatic Codex auth backed by a personal access token.", + }), + Schema.Literal("bedrockApiKey").annotate({ + description: "Amazon Bedrock bearer token managed by Codex.", + }), + Schema.Literal("bedrockAccessKeys").annotate({ + description: "Amazon Bedrock AWS access keys managed by Codex.", + }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Authentication mode for OpenAI-backed providers.", + identifier: "ServerNotification__AuthMode", }); -export type ServerNotification__ThreadGoalStatus = - | "active" - | "paused" - | "blocked" - | "usageLimited" - | "budgetLimited" - | "complete"; -export const ServerNotification__ThreadGoalStatus = Schema.Literals([ - "active", - "paused", - "blocked", - "usageLimited", - "budgetLimited", - "complete", -]); +export type ServerNotification__PlanType = + | "free" + | "go" + | "plus" + | "pro" + | "prolite" + | "team" + | "self_serve_business_prolite" + | "self_serve_business_usage_based" + | "business" + | "ent26" + | "enterprise_cbp_automation" + | "enterprise_cbp_usage_based" + | "enterprise" + | "edu" + | "edu_plus" + | "edu_pro" + | "unknown"; +export const ServerNotification__PlanType = Schema.Literals([ + "free", + "go", + "plus", + "pro", + "prolite", + "team", + "self_serve_business_prolite", + "self_serve_business_usage_based", + "business", + "ent26", + "enterprise_cbp_automation", + "enterprise_cbp_usage_based", + "enterprise", + "edu", + "edu_plus", + "edu_pro", + "unknown", +]).annotate({ identifier: "ServerNotification__PlanType" }); -export type ServerNotification__ThreadId = string; -export const ServerNotification__ThreadId = Schema.String; +export type ServerNotification__CreditsSnapshot = { + readonly balance?: string | null; + readonly hasCredits: boolean; + readonly unlimited: boolean; +}; +export const ServerNotification__CreditsSnapshot = Schema.Struct({ + balance: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + hasCredits: Schema.Boolean, + unlimited: Schema.Boolean, +}).annotate({ identifier: "ServerNotification__CreditsSnapshot" }); -export type ServerNotification__ThreadNameUpdatedNotification = { - readonly threadId: string; - readonly threadName?: string | null; +export type ServerNotification__SpendControlLimitSnapshot = { + readonly limit: string; + readonly remainingPercent: number; + readonly resetsAt: number; + readonly used: string; }; -export const ServerNotification__ThreadNameUpdatedNotification = Schema.Struct({ - threadId: Schema.String, - threadName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +export const ServerNotification__SpendControlLimitSnapshot = Schema.Struct({ + limit: Schema.String, + remainingPercent: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + resetsAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + used: Schema.String, +}).annotate({ identifier: "ServerNotification__SpendControlLimitSnapshot" }); -export type ServerNotification__ThreadRealtimeAudioChunk = { - readonly data: string; - readonly itemId?: string | null; - readonly numChannels: number; - readonly sampleRate: number; - readonly samplesPerChannel?: number | null; +export type ServerNotification__RateLimitWindow = { + readonly resetsAt?: number | null; + readonly usedPercent: number; + readonly windowDurationMins?: number | null; }; -export const ServerNotification__ThreadRealtimeAudioChunk = Schema.Struct({ - data: Schema.String, - itemId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - numChannels: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - sampleRate: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - samplesPerChannel: Schema.optionalKey( +export const ServerNotification__RateLimitWindow = Schema.Struct({ + resetsAt: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), Schema.Null, ]), ), -}).annotate({ description: "EXPERIMENTAL - thread realtime audio chunk." }); + usedPercent: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + windowDurationMins: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ServerNotification__RateLimitWindow" }); -export type ServerNotification__ThreadRealtimeClosedNotification = { - readonly reason?: string | null; - readonly threadId: string; +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", +]).annotate({ identifier: "ServerNotification__RateLimitReachedType" }); + +export type ServerNotification__AppReview = { readonly status: string }; +export const ServerNotification__AppReview = Schema.Struct({ status: Schema.String }).annotate({ + identifier: "ServerNotification__AppReview", +}); + +export type ServerNotification__AppScreenshot = { + readonly fileId?: string | null; + readonly url?: string | null; + readonly userPrompt: string; }; -export const ServerNotification__ThreadRealtimeClosedNotification = Schema.Struct({ - reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - threadId: Schema.String, -}).annotate({ description: "EXPERIMENTAL - emitted when thread realtime transport closes." }); +export const ServerNotification__AppScreenshot = Schema.Struct({ + fileId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + userPrompt: Schema.String, +}).annotate({ identifier: "ServerNotification__AppScreenshot" }); -export type ServerNotification__ThreadRealtimeErrorNotification = { - readonly message: string; - readonly threadId: string; +export type ServerNotification__AppBranding = { + readonly category?: string | null; + readonly developer?: string | null; + readonly isDiscoverableApp: boolean; + readonly privacyPolicy?: string | null; + readonly termsOfService?: string | null; + readonly website?: string | null; }; -export const ServerNotification__ThreadRealtimeErrorNotification = Schema.Struct({ - message: Schema.String, - threadId: Schema.String, -}).annotate({ description: "EXPERIMENTAL - emitted when thread realtime encounters an error." }); +export const ServerNotification__AppBranding = Schema.Struct({ + category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + isDiscoverableApp: Schema.Boolean, + privacyPolicy: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + termsOfService: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + website: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "EXPERIMENTAL - app metadata returned by app-list APIs.", + identifier: "ServerNotification__AppBranding", +}); -export type ServerNotification__ThreadRealtimeItemAddedNotification = { - readonly item: unknown; +export type ServerNotification__RemoteControlConnectionStatus = + | "disabled" + | "connecting" + | "connected" + | "errored"; +export const ServerNotification__RemoteControlConnectionStatus = Schema.Literals([ + "disabled", + "connecting", + "connected", + "errored", +]).annotate({ identifier: "ServerNotification__RemoteControlConnectionStatus" }); + +export type ServerNotification__ExternalAgentConfigMigrationItemType = + | "AGENTS_MD" + | "CONFIG" + | "SKILLS" + | "PLUGINS" + | "MCP_SERVER_CONFIG" + | "SUBAGENTS" + | "HOOKS" + | "COMMANDS" + | "MEMORY" + | "SESSIONS"; +export const ServerNotification__ExternalAgentConfigMigrationItemType = Schema.Literals([ + "AGENTS_MD", + "CONFIG", + "SKILLS", + "PLUGINS", + "MCP_SERVER_CONFIG", + "SUBAGENTS", + "HOOKS", + "COMMANDS", + "MEMORY", + "SESSIONS", +]).annotate({ identifier: "ServerNotification__ExternalAgentConfigMigrationItemType" }); + +export type ServerNotification__ReasoningSummaryTextDeltaNotification = { + readonly delta: string; + readonly itemId: string; + readonly summaryIndex: number; readonly threadId: string; + readonly turnId: string; }; -export const ServerNotification__ThreadRealtimeItemAddedNotification = Schema.Struct({ - item: Schema.Unknown, +export const ServerNotification__ReasoningSummaryTextDeltaNotification = Schema.Struct({ + delta: Schema.String, + itemId: Schema.String, + summaryIndex: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), threadId: Schema.String, -}).annotate({ - description: "EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.", -}); + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__ReasoningSummaryTextDeltaNotification" }); -export type ServerNotification__ThreadRealtimeSdpNotification = { - readonly sdp: string; +export type ServerNotification__ReasoningSummaryPartAddedNotification = { + readonly itemId: string; + readonly summaryIndex: number; readonly threadId: string; + readonly turnId: string; }; -export const ServerNotification__ThreadRealtimeSdpNotification = Schema.Struct({ - sdp: Schema.String, +export const ServerNotification__ReasoningSummaryPartAddedNotification = Schema.Struct({ + itemId: Schema.String, + summaryIndex: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), threadId: Schema.String, -}).annotate({ - description: "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.", -}); + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__ReasoningSummaryPartAddedNotification" }); -export type ServerNotification__ThreadRealtimeTranscriptDeltaNotification = { +export type ServerNotification__ReasoningTextDeltaNotification = { + readonly contentIndex: number; readonly delta: string; - readonly role: string; + readonly itemId: string; readonly threadId: string; + readonly turnId: string; }; -export const ServerNotification__ThreadRealtimeTranscriptDeltaNotification = Schema.Struct({ - delta: Schema.String.annotate({ description: "Live transcript delta from the realtime event." }), - role: Schema.String, +export const ServerNotification__ReasoningTextDeltaNotification = Schema.Struct({ + contentIndex: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + delta: Schema.String, + itemId: Schema.String, threadId: Schema.String, -}).annotate({ - description: - "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.", -}); + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__ReasoningTextDeltaNotification" }); -export type ServerNotification__ThreadRealtimeTranscriptDoneNotification = { - readonly role: string; - readonly text: string; +export type ServerNotification__ContextCompactedNotification = { readonly threadId: string; + readonly turnId: string; }; -export const ServerNotification__ThreadRealtimeTranscriptDoneNotification = Schema.Struct({ - role: Schema.String, - text: Schema.String.annotate({ description: "Final complete text for the transcript part." }), +export const ServerNotification__ContextCompactedNotification = Schema.Struct({ threadId: Schema.String, + turnId: Schema.String, }).annotate({ - description: - "EXPERIMENTAL - final transcript text emitted when realtime completes a transcript part.", + description: "Deprecated: Use `ContextCompaction` item type instead.", + identifier: "ServerNotification__ContextCompactedNotification", }); -export type ServerNotification__ThreadSource = string; -export const ServerNotification__ThreadSource = Schema.String; - -export type ServerNotification__ThreadUnarchivedNotification = { readonly threadId: string }; -export const ServerNotification__ThreadUnarchivedNotification = Schema.Struct({ - threadId: Schema.String, -}); +export type ServerNotification__ModelRerouteReason = "highRiskCyberActivity"; +export const ServerNotification__ModelRerouteReason = Schema.Literal( + "highRiskCyberActivity", +).annotate({ identifier: "ServerNotification__ModelRerouteReason" }); -export type ServerNotification__TokenUsageBreakdown = { - readonly cacheWriteInputTokens?: number; - readonly cachedInputTokens: number; - readonly inputTokens: number; - readonly outputTokens: number; - readonly reasoningOutputTokens: number; - readonly totalTokens: number; -}; -export const ServerNotification__TokenUsageBreakdown = Schema.Struct({ - cacheWriteInputTokens: Schema.optionalKey( - Schema.Number.annotate({ default: 0, format: "int64" }).check(Schema.isInt()), - ), - cachedInputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - inputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - outputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - reasoningOutputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - totalTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -}); +export type ServerNotification__ModelVerification = "trustedAccessForCyber"; +export const ServerNotification__ModelVerification = Schema.Literal( + "trustedAccessForCyber", +).annotate({ identifier: "ServerNotification__ModelVerification" }); -export type ServerNotification__TurnDiffUpdatedNotification = { - readonly diff: string; +export type ServerNotification__AuthRecoveryNotification = { + readonly message: string; + readonly provider: string; readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__TurnDiffUpdatedNotification = Schema.Struct({ - diff: Schema.String, +export const ServerNotification__AuthRecoveryNotification = Schema.Struct({ + message: Schema.String, + provider: Schema.String, threadId: Schema.String, turnId: Schema.String, -}).annotate({ - description: - "Notification that the turn-level unified diff has changed. Contains the latest aggregated diff across all file changes in the turn.", -}); +}).annotate({ identifier: "ServerNotification__AuthRecoveryNotification" }); export type ServerNotification__TurnModerationMetadataNotification = { - readonly metadata: unknown; + readonly metadata: Schema.Json; readonly threadId: string; readonly turnId: string; }; export const ServerNotification__TurnModerationMetadataNotification = Schema.Struct({ - metadata: Schema.Unknown, + metadata: Schema.Json.annotate({ expected: "JSON value" }), threadId: Schema.String, turnId: Schema.String, -}); +}).annotate({ identifier: "ServerNotification__TurnModerationMetadataNotification" }); -export type ServerNotification__TurnPlanStepStatus = "pending" | "inProgress" | "completed"; -export const ServerNotification__TurnPlanStepStatus = Schema.Literals([ - "pending", - "inProgress", - "completed", -]); - -export type ServerNotification__TurnStatus = "completed" | "interrupted" | "failed" | "inProgress"; -export const ServerNotification__TurnStatus = Schema.Literals([ - "completed", - "interrupted", - "failed", - "inProgress", -]); +export type ServerNotification__ModelSafetyBufferingUpdatedNotification = { + readonly fasterModel?: string | null; + readonly model: string; + readonly reasons: ReadonlyArray; + readonly showBufferingUi: boolean; + readonly threadId: string; + readonly turnId: string; + readonly useCases: ReadonlyArray; +}; +export const ServerNotification__ModelSafetyBufferingUpdatedNotification = Schema.Struct({ + fasterModel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + model: Schema.String, + reasons: Schema.Array(Schema.String), + showBufferingUi: Schema.Boolean, + threadId: Schema.String, + turnId: Schema.String, + useCases: Schema.Array(Schema.String), +}).annotate({ identifier: "ServerNotification__ModelSafetyBufferingUpdatedNotification" }); export type ServerNotification__WarningNotification = { readonly message: string; @@ -2920,45 +3511,247 @@ export const ServerNotification__WarningNotification = Schema.Struct({ Schema.Null, ]), ), +}).annotate({ identifier: "ServerNotification__WarningNotification" }); + +export type ServerNotification__GuardianWarningNotification = { + readonly message: string; + readonly threadId: string; +}; +export const ServerNotification__GuardianWarningNotification = Schema.Struct({ + message: Schema.String.annotate({ + description: "Concise guardian warning message for the user.", + }), + threadId: Schema.String.annotate({ description: "Thread target for the guardian warning." }), +}).annotate({ identifier: "ServerNotification__GuardianWarningNotification" }); + +export type ServerNotification__DeprecationNoticeNotification = { + readonly details?: string | null; + readonly summary: string; +}; +export const ServerNotification__DeprecationNoticeNotification = Schema.Struct({ + details: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional extra guidance, such as migration steps or rationale.", + }), + Schema.Null, + ]), + ), + summary: Schema.String.annotate({ description: "Concise summary of what is deprecated." }), +}).annotate({ identifier: "ServerNotification__DeprecationNoticeNotification" }); + +export type ServerNotification__TextPosition = { readonly column: number; readonly line: number }; +export const ServerNotification__TextPosition = Schema.Struct({ + column: Schema.Number.annotate({ + description: "1-based column number (in Unicode scalar values).", + format: "uint", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + line: Schema.Number.annotate({ description: "1-based line number.", format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "ServerNotification__TextPosition" }); + +export type ServerNotification__FuzzyFileSearchMatchType = "file" | "directory"; +export const ServerNotification__FuzzyFileSearchMatchType = Schema.Literals([ + "file", + "directory", +]).annotate({ identifier: "ServerNotification__FuzzyFileSearchMatchType" }); + +export type ServerNotification__FuzzyFileSearchSessionCompletedNotification = { + readonly sessionId: string; +}; +export const ServerNotification__FuzzyFileSearchSessionCompletedNotification = Schema.Struct({ + sessionId: Schema.String, +}).annotate({ identifier: "ServerNotification__FuzzyFileSearchSessionCompletedNotification" }); + +export type ServerNotification__RealtimeConversationVersion = "v1" | "v2" | "v3"; +export const ServerNotification__RealtimeConversationVersion = Schema.Literals([ + "v1", + "v2", + "v3", +]).annotate({ identifier: "ServerNotification__RealtimeConversationVersion" }); + +export type ServerNotification__ThreadRealtimeItemAddedNotification = { + readonly item: Schema.Json; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeItemAddedNotification = Schema.Struct({ + item: Schema.Json.annotate({ expected: "JSON value" }), + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.", + identifier: "ServerNotification__ThreadRealtimeItemAddedNotification", }); -export type ServerNotification__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 ServerNotification__WebSearchAction = Schema.Union( +export type ServerNotification__ThreadRealtimeTranscriptRole = "user" | "assistant"; +export const ServerNotification__ThreadRealtimeTranscriptRole = Schema.Literals([ + "user", + "assistant", +]).annotate({ identifier: "ServerNotification__ThreadRealtimeTranscriptRole" }); + +export type ServerNotification__ThreadRealtimeBemItemPresentation = + | { readonly type: "wholeItem" } + | { readonly type: "inlineMarkdown" } + | { readonly index: number; readonly type: "inlineVisualization" }; +export const ServerNotification__ThreadRealtimeBemItemPresentation = 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: "SearchWebSearchActionType" }), - }).annotate({ title: "SearchWebSearchAction" }), - Schema.Struct({ - type: Schema.Literal("openPage").annotate({ title: "OpenPageWebSearchActionType" }), - url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }).annotate({ title: "OpenPageWebSearchAction" }), + type: Schema.Literal("wholeItem").annotate({ + title: "WholeItemThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "WholeItemThreadRealtimeBemItemPresentation" }), 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" }), + type: Schema.Literal("inlineMarkdown").annotate({ + title: "InlineMarkdownThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "InlineMarkdownThreadRealtimeBemItemPresentation" }), Schema.Struct({ - type: Schema.Literal("other").annotate({ title: "OtherWebSearchActionType" }), - }).annotate({ title: "OtherWebSearchAction" }), + index: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + type: Schema.Literal("inlineVisualization").annotate({ + title: "InlineVisualizationThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "InlineVisualizationThreadRealtimeBemItemPresentation" }), ], { mode: "oneOf" }, -); +).annotate({ + description: "EXPERIMENTAL - how an existing agent item appears in a realtime conversation.", + identifier: "ServerNotification__ThreadRealtimeBemItemPresentation", +}); -export type ServerNotification__WindowsSandboxSetupMode = "elevated" | "unelevated"; -export const ServerNotification__WindowsSandboxSetupMode = Schema.Literals([ - "elevated", - "unelevated", -]); +export type ServerNotification__ThreadRealtimeSessionOutcome = "ended" | "failed"; +export const ServerNotification__ThreadRealtimeSessionOutcome = Schema.Literals([ + "ended", + "failed", +]).annotate({ identifier: "ServerNotification__ThreadRealtimeSessionOutcome" }); + +export type ServerNotification__ThreadRealtimeItemTranscriptDeltaNotification = { + readonly delta: string; + readonly itemId: string; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeItemTranscriptDeltaNotification = Schema.Struct({ + delta: Schema.String, + itemId: Schema.String, + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - text appended to an active realtime transcript item.", + identifier: "ServerNotification__ThreadRealtimeItemTranscriptDeltaNotification", +}); + +export type ServerNotification__ThreadRealtimeTranscriptDeltaNotification = { + readonly delta: string; + readonly role: string; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeTranscriptDeltaNotification = Schema.Struct({ + delta: Schema.String.annotate({ description: "Live transcript delta from the realtime event." }), + role: Schema.String, + threadId: Schema.String, +}).annotate({ + description: + "EXPERIMENTAL - flat transcript delta emitted whenever realtime transcript text changes.", + identifier: "ServerNotification__ThreadRealtimeTranscriptDeltaNotification", +}); + +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.", + identifier: "ServerNotification__ThreadRealtimeTranscriptDoneNotification", +}); + +export type ServerNotification__ThreadRealtimeAudioChunk = { + readonly data: string; + readonly itemId?: string | null; + readonly numChannels: number; + readonly sampleRate: number; + readonly samplesPerChannel?: number | null; +}; +export const ServerNotification__ThreadRealtimeAudioChunk = Schema.Struct({ + data: Schema.String, + itemId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + numChannels: Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + sampleRate: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + samplesPerChannel: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), +}).annotate({ + description: "EXPERIMENTAL - thread realtime audio chunk.", + identifier: "ServerNotification__ThreadRealtimeAudioChunk", +}); + +export type ServerNotification__ThreadRealtimeSdpNotification = { + readonly sdp: string; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeSdpNotification = Schema.Struct({ + sdp: Schema.String, + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - emitted with the remote SDP for a WebRTC realtime session.", + identifier: "ServerNotification__ThreadRealtimeSdpNotification", +}); + +export type ServerNotification__ThreadRealtimeErrorNotification = { + readonly message: string; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeErrorNotification = Schema.Struct({ + message: Schema.String, + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - emitted when thread realtime encounters an error.", + identifier: "ServerNotification__ThreadRealtimeErrorNotification", +}); + +export type ServerNotification__ThreadRealtimeClosedNotification = { + readonly reason?: string | null; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeClosedNotification = Schema.Struct({ + reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - emitted when thread realtime transport closes.", + identifier: "ServerNotification__ThreadRealtimeClosedNotification", +}); export type ServerNotification__WindowsWorldWritableWarningNotification = { readonly extraCount: number; @@ -2967,68 +3760,59 @@ export type ServerNotification__WindowsWorldWritableWarningNotification = { }; export const ServerNotification__WindowsWorldWritableWarningNotification = Schema.Struct({ extraCount: Schema.Number.annotate({ format: "uint" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), failedScan: Schema.Boolean, samplePaths: Schema.Array(Schema.String), -}); +}).annotate({ identifier: "ServerNotification__WindowsWorldWritableWarningNotification" }); -export type ServerRequest__AbsolutePathBuf = string; -export const ServerRequest__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 ServerNotification__WindowsSandboxSetupMode = "elevated" | "unelevated"; +export const ServerNotification__WindowsSandboxSetupMode = Schema.Literals([ + "elevated", + "unelevated", +]).annotate({ identifier: "ServerNotification__WindowsSandboxSetupMode" }); -export type ServerRequest__AdditionalNetworkPermissions = { readonly enabled?: boolean | null }; -export const ServerRequest__AdditionalNetworkPermissions = Schema.Struct({ - enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), -}); +export type ServerNotification__DesktopOnboardingEntrypoint = "life_sciences"; +export const ServerNotification__DesktopOnboardingEntrypoint = Schema.Literal( + "life_sciences", +).annotate({ identifier: "ServerNotification__DesktopOnboardingEntrypoint" }); -export type ServerRequest__AttestationGenerateParams = {}; -export const ServerRequest__AttestationGenerateParams = Schema.Struct({}); +export type ServerRequest__RequestId = string | number; +export const ServerRequest__RequestId = Schema.Union([ + Schema.String, + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "ServerRequest__RequestId" }); -export type ServerRequest__ChatgptAuthTokensRefreshReason = "unauthorized"; -export const ServerRequest__ChatgptAuthTokensRefreshReason = Schema.Literal("unauthorized"); +export type ServerRequest__LegacyAppPathString = string; +export const ServerRequest__LegacyAppPathString = Schema.String.annotate({ + identifier: "ServerRequest__LegacyAppPathString", +}); -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__CommandExecutionApprovalKind = "command" | "writeStdin"; +export const ServerRequest__CommandExecutionApprovalKind = Schema.Literals([ + "command", + "writeStdin", +]).annotate({ + description: "Distinguishes a command approval from input sent to an existing terminal.", + identifier: "ServerRequest__CommandExecutionApprovalKind", }); -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, - type: Schema.Literal("add").annotate({ title: "AddFileChangeType" }), - }).annotate({ title: "AddFileChange" }), - Schema.Struct({ - content: Schema.String, - type: Schema.Literal("delete").annotate({ title: "DeleteFileChangeType" }), - }).annotate({ title: "DeleteFileChange" }), - Schema.Struct({ - move_path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("update").annotate({ title: "UpdateFileChangeType" }), - unified_diff: Schema.String, - }).annotate({ title: "UpdateFileChange" }), - ], - { mode: "oneOf" }, -); +export type ServerRequest__NetworkApprovalProtocol = "http" | "https" | "socks5Tcp" | "socks5Udp"; +export const ServerRequest__NetworkApprovalProtocol = Schema.Literals([ + "http", + "https", + "socks5Tcp", + "socks5Udp", +]).annotate({ identifier: "ServerRequest__NetworkApprovalProtocol" }); + +export type ServerRequest__NetworkPolicyRuleAction = "allow" | "deny"; +export const ServerRequest__NetworkPolicyRuleAction = Schema.Literals(["allow", "deny"]).annotate({ + identifier: "ServerRequest__NetworkPolicyRuleAction", +}); export type ServerRequest__FileChangeRequestApprovalParams = { readonly grantRoot?: string | null; @@ -3060,22 +3844,27 @@ export const ServerRequest__FileChangeRequestApprovalParams = Schema.Struct({ startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this approval request started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), threadId: Schema.String, turnId: Schema.String, -}); - -export type ServerRequest__FileSystemAccessMode = "read" | "write" | "deny"; -export const ServerRequest__FileSystemAccessMode = Schema.Literals(["read", "write", "deny"]); +}).annotate({ identifier: "ServerRequest__FileChangeRequestApprovalParams" }); -export type ServerRequest__LegacyAppPathString = string; -export const ServerRequest__LegacyAppPathString = Schema.String; - -export type ServerRequest__McpElicitationArrayType = "array"; -export const ServerRequest__McpElicitationArrayType = Schema.Literal("array"); +export type ServerRequest__ToolRequestUserInputOption = { + readonly description: string; + readonly label: string; +}; +export const ServerRequest__ToolRequestUserInputOption = Schema.Struct({ + description: Schema.String, + label: Schema.String, +}).annotate({ + description: "EXPERIMENTAL. Defines a single selectable option for request_user_input.", + identifier: "ServerRequest__ToolRequestUserInputOption", +}); -export type ServerRequest__McpElicitationBooleanType = "boolean"; -export const ServerRequest__McpElicitationBooleanType = Schema.Literal("boolean"); +export type ServerRequest__McpElicitationStringType = "string"; +export const ServerRequest__McpElicitationStringType = Schema.Literal("string").annotate({ + identifier: "ServerRequest__McpElicitationStringType", +}); export type ServerRequest__McpElicitationConstOption = { readonly const: string; @@ -3084,13 +3873,12 @@ export type ServerRequest__McpElicitationConstOption = { export const ServerRequest__McpElicitationConstOption = Schema.Struct({ const: Schema.String, title: Schema.String, -}); - -export type ServerRequest__McpElicitationNumberType = "number" | "integer"; -export const ServerRequest__McpElicitationNumberType = Schema.Literals(["number", "integer"]); +}).annotate({ identifier: "ServerRequest__McpElicitationConstOption" }); -export type ServerRequest__McpElicitationObjectType = "object"; -export const ServerRequest__McpElicitationObjectType = Schema.Literal("object"); +export type ServerRequest__McpElicitationArrayType = "array"; +export const ServerRequest__McpElicitationArrayType = Schema.Literal("array").annotate({ + identifier: "ServerRequest__McpElicitationArrayType", +}); export type ServerRequest__McpElicitationStringFormat = "email" | "uri" | "date" | "date-time"; export const ServerRequest__McpElicitationStringFormat = Schema.Literals([ @@ -3098,21 +3886,96 @@ export const ServerRequest__McpElicitationStringFormat = Schema.Literals([ "uri", "date", "date-time", -]); +]).annotate({ identifier: "ServerRequest__McpElicitationStringFormat" }); -export type ServerRequest__McpElicitationStringType = "string"; -export const ServerRequest__McpElicitationStringType = Schema.Literal("string"); +export type ServerRequest__McpElicitationNumberType = "number" | "integer"; +export const ServerRequest__McpElicitationNumberType = Schema.Literals([ + "number", + "integer", +]).annotate({ identifier: "ServerRequest__McpElicitationNumberType" }); -export type ServerRequest__NetworkApprovalProtocol = "http" | "https" | "socks5Tcp" | "socks5Udp"; -export const ServerRequest__NetworkApprovalProtocol = Schema.Literals([ - "http", - "https", - "socks5Tcp", - "socks5Udp", -]); +export type ServerRequest__McpElicitationBooleanType = "boolean"; +export const ServerRequest__McpElicitationBooleanType = Schema.Literal("boolean").annotate({ + identifier: "ServerRequest__McpElicitationBooleanType", +}); -export type ServerRequest__NetworkPolicyRuleAction = "allow" | "deny"; -export const ServerRequest__NetworkPolicyRuleAction = Schema.Literals(["allow", "deny"]); +export type ServerRequest__McpElicitationObjectType = "object"; +export const ServerRequest__McpElicitationObjectType = Schema.Literal("object").annotate({ + identifier: "ServerRequest__McpElicitationObjectType", +}); + +export type ServerRequest__FileSystemAccessMode = "read" | "write" | "deny"; +export const ServerRequest__FileSystemAccessMode = Schema.Literals([ + "read", + "write", + "deny", +]).annotate({ identifier: "ServerRequest__FileSystemAccessMode" }); + +export type ServerRequest__AdditionalNetworkPermissions = { readonly enabled?: boolean | null }; +export const ServerRequest__AdditionalNetworkPermissions = Schema.Struct({ + enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), +}).annotate({ identifier: "ServerRequest__AdditionalNetworkPermissions" }); + +export type ServerRequest__DynamicToolCallParams = { + readonly arguments: Schema.Json; + readonly callId: string; + readonly namespace?: string | null; + readonly threadId: string; + readonly tool: string; + readonly turnId: string; +}; +export const ServerRequest__DynamicToolCallParams = Schema.Struct({ + arguments: Schema.Json.annotate({ expected: "JSON value" }), + callId: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + threadId: Schema.String, + tool: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerRequest__DynamicToolCallParams" }); + +export type ServerRequest__ChatgptAuthTokensRefreshReason = "unauthorized"; +export const ServerRequest__ChatgptAuthTokensRefreshReason = Schema.Union( + [ + Schema.Literal("unauthorized").annotate({ + description: "Codex attempted a backend request and received `401 Unauthorized`.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ServerRequest__ChatgptAuthTokensRefreshReason" }); + +export type ServerRequest__AttestationGenerateParams = { readonly [x: string]: Schema.Json }; +export const ServerRequest__AttestationGenerateParams = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ identifier: "ServerRequest__AttestationGenerateParams" }); + +export type ServerRequest__ThreadId = string; +export const ServerRequest__ThreadId = Schema.String.annotate({ + identifier: "ServerRequest__ThreadId", +}); + +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, + type: Schema.Literal("add").annotate({ title: "AddFileChangeType" }), + }).annotate({ title: "AddFileChange" }), + Schema.Struct({ + content: Schema.String, + type: Schema.Literal("delete").annotate({ title: "DeleteFileChangeType" }), + }).annotate({ title: "DeleteFileChange" }), + Schema.Struct({ + move_path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("update").annotate({ title: "UpdateFileChangeType" }), + unified_diff: Schema.String, + }).annotate({ title: "UpdateFileChange" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ServerRequest__FileChange" }); export type ServerRequest__ParsedCommand = | { readonly cmd: string; readonly name: string; readonly path: string; readonly type: "read" } @@ -3152,27 +4015,7 @@ export const ServerRequest__ParsedCommand = Schema.Union( }).annotate({ title: "UnknownParsedCommand" }), ], { mode: "oneOf" }, -); - -export type ServerRequest__RequestId = string | number; -export const ServerRequest__RequestId = Schema.Union([ - Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); - -export type ServerRequest__ThreadId = string; -export const ServerRequest__ThreadId = Schema.String; - -export type ServerRequest__ToolRequestUserInputOption = { - readonly description: string; - readonly label: string; -}; -export const ServerRequest__ToolRequestUserInputOption = Schema.Struct({ - description: Schema.String, - label: Schema.String, -}).annotate({ - description: "EXPERIMENTAL. Defines a single selectable option for request_user_input.", -}); +).annotate({ identifier: "ServerRequest__ParsedCommand" }); export type ToolRequestUserInputParams__ToolRequestUserInputOption = { readonly description: string; @@ -3183,6 +4026,7 @@ export const ToolRequestUserInputParams__ToolRequestUserInputOption = Schema.Str label: Schema.String, }).annotate({ description: "EXPERIMENTAL. Defines a single selectable option for request_user_input.", + identifier: "ToolRequestUserInputParams__ToolRequestUserInputOption", }); export type ToolRequestUserInputResponse__ToolRequestUserInputAnswer = { @@ -3192,21 +4036,12 @@ export const ToolRequestUserInputResponse__ToolRequestUserInputAnswer = Schema.S answers: Schema.Array(Schema.String), }).annotate({ description: "EXPERIMENTAL. Captures a user's answer to a request_user_input question.", -}); - -export type V1InitializeParams__ClientInfo = { - readonly name: string; - readonly title?: string | null; - readonly version: string; -}; -export const V1InitializeParams__ClientInfo = Schema.Struct({ - name: Schema.String, - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - version: Schema.String, + identifier: "ToolRequestUserInputResponse__ToolRequestUserInputAnswer", }); export type V1InitializeParams__InitializeCapabilities = { readonly experimentalApi?: boolean; + readonly extensions?: { readonly [x: string]: Schema.Json } | null; readonly mcpServerOpenaiFormElicitation?: boolean; readonly optOutNotificationMethods?: ReadonlyArray | null; readonly requestAttestation?: boolean; @@ -3218,9 +4053,18 @@ export const V1InitializeParams__InitializeCapabilities = Schema.Struct({ default: false, }), ), + extensions: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })).annotate({ + description: "MCP extension settings declared by the app-server client.", + }), + Schema.Null, + ]), + ), mcpServerOpenaiFormElicitation: Schema.optionalKey( Schema.Boolean.annotate({ - description: "Allow downstream MCP servers to request OpenAI extended form elicitations.", + description: + "Legacy opt-in for the `openai/form` MCP extension.\n\nNew clients should declare `openai/form` in [`Self::extensions`].", }), ), optOutNotificationMethods: Schema.optionalKey( @@ -3238,7 +4082,33 @@ export const V1InitializeParams__InitializeCapabilities = Schema.Struct({ default: false, }), ), -}).annotate({ description: "Client-declared capabilities negotiated during initialize." }); +}).annotate({ + description: "Client-declared capabilities negotiated during initialize.", + identifier: "V1InitializeParams__InitializeCapabilities", +}); + +export type V1InitializeParams__ClientInfo = { + readonly name: string; + readonly title?: string | null; + readonly version: string; +}; +export const V1InitializeParams__ClientInfo = Schema.Struct({ + name: Schema.String, + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + version: Schema.String, +}).annotate({ identifier: "V1InitializeParams__ClientInfo" }); + +export type V1InitializeResponse__AbsolutePathBuf = string; +export const V1InitializeResponse__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.", + identifier: "V1InitializeResponse__AbsolutePathBuf", +}); + +export type V2AccountLoginCompletedNotification__DesktopOnboardingEntrypoint = "life_sciences"; +export const V2AccountLoginCompletedNotification__DesktopOnboardingEntrypoint = Schema.Literal( + "life_sciences", +).annotate({ identifier: "V2AccountLoginCompletedNotification__DesktopOnboardingEntrypoint" }); export type V2AccountRateLimitsUpdatedNotification__CreditsSnapshot = { readonly balance?: string | null; @@ -3249,7 +4119,24 @@ export const V2AccountRateLimitsUpdatedNotification__CreditsSnapshot = Schema.St balance: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), hasCredits: Schema.Boolean, unlimited: Schema.Boolean, -}); +}).annotate({ identifier: "V2AccountRateLimitsUpdatedNotification__CreditsSnapshot" }); + +export type V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot = { + readonly limit: string; + readonly remainingPercent: number; + readonly resetsAt: number; + readonly used: string; +}; +export const V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot = Schema.Struct({ + limit: Schema.String, + remainingPercent: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + resetsAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + used: Schema.String, +}).annotate({ identifier: "V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot" }); export type V2AccountRateLimitsUpdatedNotification__PlanType = | "free" @@ -3287,7 +4174,34 @@ export const V2AccountRateLimitsUpdatedNotification__PlanType = Schema.Literals( "edu_plus", "edu_pro", "unknown", -]); +]).annotate({ identifier: "V2AccountRateLimitsUpdatedNotification__PlanType" }); + +export type V2AccountRateLimitsUpdatedNotification__RateLimitWindow = { + readonly resetsAt?: number | null; + readonly usedPercent: number; + readonly windowDurationMins?: number | null; +}; +export const V2AccountRateLimitsUpdatedNotification__RateLimitWindow = Schema.Struct({ + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + usedPercent: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + windowDurationMins: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2AccountRateLimitsUpdatedNotification__RateLimitWindow" }); export type V2AccountRateLimitsUpdatedNotification__RateLimitReachedType = | "rate_limit_reached" @@ -3301,35 +4215,7 @@ export const V2AccountRateLimitsUpdatedNotification__RateLimitReachedType = Sche "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; - readonly windowDurationMins?: number | null; -}; -export const V2AccountRateLimitsUpdatedNotification__RateLimitWindow = Schema.Struct({ - resetsAt: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - usedPercent: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), - windowDurationMins: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), -}); - -export type V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot = { - readonly limit: string; - readonly remainingPercent: number; - readonly resetsAt: number; - readonly used: string; -}; -export const V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot = Schema.Struct({ - limit: Schema.String, - remainingPercent: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), - resetsAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - used: Schema.String, -}); +]).annotate({ identifier: "V2AccountRateLimitsUpdatedNotification__RateLimitReachedType" }); export type V2AccountUpdatedNotification__AuthMode = | "apikey" @@ -3338,16 +4224,41 @@ export type V2AccountUpdatedNotification__AuthMode = | "headers" | "agentIdentity" | "personalAccessToken" - | "bedrockApiKey"; -export const V2AccountUpdatedNotification__AuthMode = Schema.Literals([ - "apikey", - "chatgpt", - "chatgptAuthTokens", - "headers", - "agentIdentity", - "personalAccessToken", - "bedrockApiKey", -]).annotate({ description: "Authentication mode for OpenAI-backed providers." }); + | "bedrockApiKey" + | "bedrockAccessKeys"; +export const V2AccountUpdatedNotification__AuthMode = Schema.Union( + [ + Schema.Literal("apikey").annotate({ + description: "OpenAI API key provided by the caller and stored by Codex.", + }), + Schema.Literal("chatgpt").annotate({ + description: "ChatGPT OAuth managed by Codex (tokens persisted and refreshed by Codex).", + }), + Schema.Literal("chatgptAuthTokens").annotate({ + description: + "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE.\n\nChatGPT auth tokens are supplied by an external host app and are only stored in memory. Token refresh must be handled by the external host app.", + }), + Schema.Literal("headers").annotate({ + description: "Backend auth supplied as request headers.", + }), + Schema.Literal("agentIdentity").annotate({ + description: "Programmatic Codex auth backed by a registered Agent Identity.", + }), + Schema.Literal("personalAccessToken").annotate({ + description: "Programmatic Codex auth backed by a personal access token.", + }), + Schema.Literal("bedrockApiKey").annotate({ + description: "Amazon Bedrock bearer token managed by Codex.", + }), + Schema.Literal("bedrockAccessKeys").annotate({ + description: "Amazon Bedrock AWS access keys managed by Codex.", + }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Authentication mode for OpenAI-backed providers.", + identifier: "V2AccountUpdatedNotification__AuthMode", +}); export type V2AccountUpdatedNotification__PlanType = | "free" @@ -3385,7 +4296,23 @@ export const V2AccountUpdatedNotification__PlanType = Schema.Literals([ "edu_plus", "edu_pro", "unknown", -]); +]).annotate({ identifier: "V2AccountUpdatedNotification__PlanType" }); + +export type V2AppListUpdatedNotification__AppReview = { readonly status: string }; +export const V2AppListUpdatedNotification__AppReview = Schema.Struct({ + status: Schema.String, +}).annotate({ identifier: "V2AppListUpdatedNotification__AppReview" }); + +export type V2AppListUpdatedNotification__AppScreenshot = { + readonly fileId?: string | null; + readonly url?: string | null; + readonly userPrompt: string; +}; +export const V2AppListUpdatedNotification__AppScreenshot = Schema.Struct({ + fileId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + userPrompt: Schema.String, +}).annotate({ identifier: "V2AppListUpdatedNotification__AppScreenshot" }); export type V2AppListUpdatedNotification__AppBranding = { readonly category?: string | null; @@ -3402,21 +4329,10 @@ export const V2AppListUpdatedNotification__AppBranding = Schema.Struct({ privacyPolicy: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), termsOfService: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), website: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}).annotate({ description: "EXPERIMENTAL - app metadata returned by app-list APIs." }); - -export type V2AppListUpdatedNotification__AppReview = { readonly status: string }; -export const V2AppListUpdatedNotification__AppReview = Schema.Struct({ status: Schema.String }); - -export type V2AppListUpdatedNotification__AppScreenshot = { - readonly fileId?: string | null; - readonly url?: string | null; - readonly userPrompt: string; -}; -export const V2AppListUpdatedNotification__AppScreenshot = Schema.Struct({ - fileId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - userPrompt: Schema.String, -}); +}).annotate({ + description: "EXPERIMENTAL - app metadata returned by app-list APIs.", + identifier: "V2AppListUpdatedNotification__AppBranding", +}); export type V2AppsInstalledResponse__InstalledApp = { readonly callable: boolean; @@ -3443,7 +4359,26 @@ export const V2AppsInstalledResponse__InstalledApp = Schema.Struct({ Schema.Null, ]), ), -}).annotate({ description: "Installed connector runtime state." }); +}).annotate({ + description: "Installed connector runtime state.", + identifier: "V2AppsInstalledResponse__InstalledApp", +}); + +export type V2AppsListResponse__AppReview = { readonly status: string }; +export const V2AppsListResponse__AppReview = Schema.Struct({ status: Schema.String }).annotate({ + identifier: "V2AppsListResponse__AppReview", +}); + +export type V2AppsListResponse__AppScreenshot = { + readonly fileId?: string | null; + readonly url?: string | null; + readonly userPrompt: string; +}; +export const V2AppsListResponse__AppScreenshot = Schema.Struct({ + fileId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + userPrompt: Schema.String, +}).annotate({ identifier: "V2AppsListResponse__AppScreenshot" }); export type V2AppsListResponse__AppBranding = { readonly category?: string | null; @@ -3460,43 +4395,62 @@ export const V2AppsListResponse__AppBranding = Schema.Struct({ privacyPolicy: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), termsOfService: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), website: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}).annotate({ description: "EXPERIMENTAL - app metadata returned by app-list APIs." }); - -export type V2AppsListResponse__AppReview = { readonly status: string }; -export const V2AppsListResponse__AppReview = Schema.Struct({ status: Schema.String }); - -export type V2AppsListResponse__AppScreenshot = { - readonly fileId?: string | null; - readonly url?: string | null; - readonly userPrompt: string; -}; -export const V2AppsListResponse__AppScreenshot = Schema.Struct({ - fileId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - userPrompt: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - app metadata returned by app-list APIs.", + identifier: "V2AppsListResponse__AppBranding", }); export type V2AppsReadResponse__AppToolSummary = { readonly description: string; + readonly disabledReason?: string | null; + readonly isEnabled?: boolean; + readonly isReadOnly?: boolean; readonly name: string; readonly title?: string | null; }; export const V2AppsReadResponse__AppToolSummary = Schema.Struct({ description: Schema.String, + disabledReason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + isEnabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), + isReadOnly: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), name: Schema.String, title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}).annotate({ description: "EXPERIMENTAL - metadata returned by app/read." }); +}).annotate({ + description: "EXPERIMENTAL - metadata returned by app/read.", + identifier: "V2AppsReadResponse__AppToolSummary", +}); export type V2CancelLoginAccountResponse__CancelLoginAccountStatus = "canceled" | "notFound"; export const V2CancelLoginAccountResponse__CancelLoginAccountStatus = Schema.Literals([ "canceled", "notFound", -]); +]).annotate({ identifier: "V2CancelLoginAccountResponse__CancelLoginAccountStatus" }); + +export type V2CommandExecOutputDeltaNotification__CommandExecOutputStream = "stdout" | "stderr"; +export const V2CommandExecOutputDeltaNotification__CommandExecOutputStream = Schema.Union( + [ + Schema.Literal("stdout").annotate({ + description: "stdout stream. PTY mode multiplexes terminal output here.", + }), + Schema.Literal("stderr").annotate({ description: "stderr stream." }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Stream label for `command/exec/outputDelta` notifications.", + identifier: "V2CommandExecOutputDeltaNotification__CommandExecOutputStream", +}); + +export type V2CommandExecParams__NetworkAccess = "restricted" | "enabled"; +export const V2CommandExecParams__NetworkAccess = Schema.Literals([ + "restricted", + "enabled", +]).annotate({ identifier: "V2CommandExecParams__NetworkAccess" }); export type V2CommandExecParams__AbsolutePathBuf = string; export const V2CommandExecParams__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.", + identifier: "V2CommandExecParams__AbsolutePathBuf", }); export type V2CommandExecParams__CommandExecTerminalSize = { @@ -3508,54 +4462,62 @@ export const V2CommandExecParams__CommandExecTerminalSize = Schema.Struct({ description: "Terminal width in character cells.", format: "uint16", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), rows: Schema.Number.annotate({ description: "Terminal height in character cells.", format: "uint16", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}).annotate({ description: "PTY size in character cells for `command/exec` PTY sessions." }); - -export type V2ConfigBatchWriteParams__MergeStrategy = "replace" | "upsert"; -export const V2ConfigBatchWriteParams__MergeStrategy = Schema.Literals(["replace", "upsert"]); + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ + description: "PTY size in character cells for `command/exec` PTY sessions.", + identifier: "V2CommandExecParams__CommandExecTerminalSize", +}); -export type V2ConfigReadResponse__AbsolutePathBuf = string; -export const V2ConfigReadResponse__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 V2CommandExecResizeParams__CommandExecTerminalSize = { + readonly cols: number; + readonly rows: number; +}; +export const V2CommandExecResizeParams__CommandExecTerminalSize = Schema.Struct({ + cols: Schema.Number.annotate({ + description: "Terminal width in character cells.", + format: "uint16", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + rows: Schema.Number.annotate({ + description: "Terminal height in character cells.", + format: "uint16", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ + description: "PTY size in character cells for `command/exec` PTY sessions.", + identifier: "V2CommandExecResizeParams__CommandExecTerminalSize", }); -export type V2ConfigReadResponse__AnalyticsConfig = { - readonly enabled?: boolean | null; - readonly [x: string]: unknown; +export type V2ConfigBatchWriteParams__MergeStrategy = "replace" | "upsert"; +export const V2ConfigBatchWriteParams__MergeStrategy = Schema.Literals([ + "replace", + "upsert", +]).annotate({ identifier: "V2ConfigBatchWriteParams__MergeStrategy" }); + +export type V2ConfigReadResponse__AnalyticsConfig = { readonly enabled?: boolean | null } & { + readonly [x: string]: Schema.Json; }; export const V2ConfigReadResponse__AnalyticsConfig = Schema.StructWithRest( Schema.Struct({ enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) }), - [Schema.Record(Schema.String, Schema.Unknown)], -); - -export type V2ConfigReadResponse__AppToolApproval = "auto" | "prompt" | "writes" | "approve"; -export const V2ConfigReadResponse__AppToolApproval = Schema.Literals([ - "auto", - "prompt", - "writes", - "approve", -]); - -export type V2ConfigReadResponse__AppToolsConfig = {}; -export const V2ConfigReadResponse__AppToolsConfig = Schema.Struct({}); - -export type V2ConfigReadResponse__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const V2ConfigReadResponse__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); + [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))], +).annotate({ identifier: "V2ConfigReadResponse__AnalyticsConfig" }); export type V2ConfigReadResponse__AskForApproval = | "untrusted" @@ -3584,32 +4546,65 @@ export const V2ConfigReadResponse__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ConfigReadResponse__AskForApproval" }); -export type V2ConfigReadResponse__AutoCompactTokenLimitScope = "total" | "body_after_prefix"; -export const V2ConfigReadResponse__AutoCompactTokenLimitScope = Schema.Literals([ - "total", - "body_after_prefix", +export type V2ConfigReadResponse__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const V2ConfigReadResponse__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", ]).annotate({ description: - "Selects which part of the active context is charged against `model_auto_compact_token_limit`.", + "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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ConfigReadResponse__ApprovalsReviewer", }); +export type V2ConfigReadResponse__AllowDenyRequirement = "allow" | "deny"; +export const V2ConfigReadResponse__AllowDenyRequirement = Schema.Literals([ + "allow", + "deny", +]).annotate({ identifier: "V2ConfigReadResponse__AllowDenyRequirement" }); + export type V2ConfigReadResponse__ForcedChatgptWorkspaceIds = string | ReadonlyArray; export const V2ConfigReadResponse__ForcedChatgptWorkspaceIds = Schema.Union([ Schema.String, Schema.Array(Schema.String), ]).annotate({ description: "Backward-compatible API shape for ChatGPT workspace login restrictions.", + identifier: "V2ConfigReadResponse__ForcedChatgptWorkspaceIds", }); export type V2ConfigReadResponse__ForcedLoginMethod = "chatgpt" | "api"; -export const V2ConfigReadResponse__ForcedLoginMethod = Schema.Literals(["chatgpt", "api"]); +export const V2ConfigReadResponse__ForcedLoginMethod = Schema.Literals(["chatgpt", "api"]).annotate( + { identifier: "V2ConfigReadResponse__ForcedLoginMethod" }, +); + +export type V2ConfigReadResponse__AutoCompactTokenLimitScope = "total" | "body_after_prefix"; +export const V2ConfigReadResponse__AutoCompactTokenLimitScope = Schema.Union( + [ + Schema.Literal("total").annotate({ + description: "Count the full active context against the limit.", + }), + Schema.Literal("body_after_prefix").annotate({ + description: "Count sampled output and later growth after the carried window prefix.", + }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "Selects which part of the active context is charged against `model_auto_compact_token_limit`.", + identifier: "V2ConfigReadResponse__AutoCompactTokenLimitScope", +}); export type V2ConfigReadResponse__ReasoningEffort = string; export const V2ConfigReadResponse__ReasoningEffort = Schema.String.annotate({ description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ConfigReadResponse__ReasoningEffort", + }), +); export type V2ConfigReadResponse__ReasoningSummary = "auto" | "concise" | "detailed" | "none"; export const V2ConfigReadResponse__ReasoningSummary = Schema.Union( @@ -3621,6 +4616,14 @@ export const V2ConfigReadResponse__ReasoningSummary = Schema.Union( ).annotate({ description: "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", + identifier: "V2ConfigReadResponse__ReasoningSummary", +}); + +export type V2ConfigReadResponse__Verbosity = "low" | "medium" | "high"; +export const V2ConfigReadResponse__Verbosity = Schema.Literals(["low", "medium", "high"]).annotate({ + description: + "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", + identifier: "V2ConfigReadResponse__Verbosity", }); export type V2ConfigReadResponse__SandboxMode = @@ -3631,7 +4634,7 @@ export const V2ConfigReadResponse__SandboxMode = Schema.Literals([ "read-only", "workspace-write", "danger-full-access", -]); +]).annotate({ identifier: "V2ConfigReadResponse__SandboxMode" }); export type V2ConfigReadResponse__SandboxWorkspaceWrite = { readonly exclude_slash_tmp?: boolean; @@ -3644,20 +4647,14 @@ export const V2ConfigReadResponse__SandboxWorkspaceWrite = Schema.Struct({ exclude_tmpdir_env_var: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), network_access: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), writable_roots: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), -}); - -export type V2ConfigReadResponse__Verbosity = "low" | "medium" | "high"; -export const V2ConfigReadResponse__Verbosity = Schema.Literals(["low", "medium", "high"]).annotate({ - description: - "Controls output length/detail on GPT-5 models via the Responses API. Serialized with lowercase values to match the OpenAI API.", -}); +}).annotate({ identifier: "V2ConfigReadResponse__SandboxWorkspaceWrite" }); export type V2ConfigReadResponse__WebSearchContextSize = "low" | "medium" | "high"; export const V2ConfigReadResponse__WebSearchContextSize = Schema.Literals([ "low", "medium", "high", -]); +]).annotate({ identifier: "V2ConfigReadResponse__WebSearchContextSize" }); export type V2ConfigReadResponse__WebSearchLocation = { readonly city?: string | null; @@ -3670,7 +4667,7 @@ export const V2ConfigReadResponse__WebSearchLocation = Schema.Struct({ country: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), region: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), timezone: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ConfigReadResponse__WebSearchLocation" }); export type V2ConfigReadResponse__WebSearchMode = "disabled" | "cached" | "indexed" | "live"; export const V2ConfigReadResponse__WebSearchMode = Schema.Literals([ @@ -3678,7 +4675,56 @@ export const V2ConfigReadResponse__WebSearchMode = Schema.Literals([ "cached", "indexed", "live", -]); +]).annotate({ identifier: "V2ConfigReadResponse__WebSearchMode" }); + +export type V2ConfigReadResponse__AbsolutePathBuf = string; +export const V2ConfigReadResponse__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.", + identifier: "V2ConfigReadResponse__AbsolutePathBuf", +}); + +export type V2ConfigReadResponse__AppToolApproval = "auto" | "prompt" | "writes" | "approve"; +export const V2ConfigReadResponse__AppToolApproval = Schema.Literals([ + "auto", + "prompt", + "writes", + "approve", +]).annotate({ identifier: "V2ConfigReadResponse__AppToolApproval" }); + +export type V2ConfigReadResponse__AppLinksConfig = { readonly [x: string]: Schema.Json }; +export const V2ConfigReadResponse__AppLinksConfig = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ + description: "Account settings for a single app.", + identifier: "V2ConfigReadResponse__AppLinksConfig", +}); + +export type V2ConfigReadResponse__ToolExposureSurface = "code_mode" | "deferred" | "direct"; +export const V2ConfigReadResponse__ToolExposureSurface = Schema.Union( + [ + Schema.Literal("code_mode").annotate({ + description: "Nested tools available to Code Mode scripts.", + }), + Schema.Literal("deferred").annotate({ + description: "Tools discovered later through tool search.", + }), + Schema.Literal("direct").annotate({ + description: "Tools present in the model's initial tool list.", + }), + ], + { mode: "oneOf" }, +).annotate({ + description: "A model-facing surface on which a tool can be exposed.", + identifier: "V2ConfigReadResponse__ToolExposureSurface", +}); + +export type V2ConfigReadResponse__AppToolsConfig = { readonly [x: string]: Schema.Json }; +export const V2ConfigReadResponse__AppToolsConfig = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ identifier: "V2ConfigReadResponse__AppToolsConfig" }); export type V2ConfigRequirementsReadResponse__AskForApproval = | "untrusted" @@ -3707,17 +4753,119 @@ export const V2ConfigRequirementsReadResponse__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, +).annotate({ identifier: "V2ConfigRequirementsReadResponse__AskForApproval" }); + +export type V2ConfigRequirementsReadResponse__ForcedLoginMethod = "chatgpt" | "api"; +export const V2ConfigRequirementsReadResponse__ForcedLoginMethod = Schema.Literals([ + "chatgpt", + "api", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__ForcedLoginMethod" }); + +export type V2ConfigRequirementsReadResponse__SandboxMode = + | "read-only" + | "workspace-write" + | "danger-full-access"; +export const V2ConfigRequirementsReadResponse__SandboxMode = Schema.Literals([ + "read-only", + "workspace-write", + "danger-full-access", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__SandboxMode" }); + +export type V2ConfigRequirementsReadResponse__WebSearchMode = + | "disabled" + | "cached" + | "indexed" + | "live"; +export const V2ConfigRequirementsReadResponse__WebSearchMode = Schema.Literals([ + "disabled", + "cached", + "indexed", + "live", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__WebSearchMode" }); + +export type V2ConfigRequirementsReadResponse__WindowsSandboxImplementation = + | "elevated" + | "unelevated" + | "mxc"; +export const V2ConfigRequirementsReadResponse__WindowsSandboxImplementation = Schema.Literals([ + "elevated", + "unelevated", + "mxc", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__WindowsSandboxImplementation" }); + +export type V2ConfigRequirementsReadResponse__AutoReviewRequirements = { + readonly ignoreRules?: ReadonlyArray | null; + readonly requiredOnModels?: ReadonlyArray | null; +}; +export const V2ConfigRequirementsReadResponse__AutoReviewRequirements = Schema.Struct({ + ignoreRules: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + requiredOnModels: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__AutoReviewRequirements" }); + +export type V2ConfigRequirementsReadResponse__AllowDenyRequirement = "allow" | "deny"; +export const V2ConfigRequirementsReadResponse__AllowDenyRequirement = Schema.Literals([ + "allow", + "deny", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__AllowDenyRequirement" }); + +export type V2ConfigRequirementsReadResponse__BrowserUseAccessApprovalLifetime = "turn" | "thread"; +export const V2ConfigRequirementsReadResponse__BrowserUseAccessApprovalLifetime = Schema.Literals([ + "turn", + "thread", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__BrowserUseAccessApprovalLifetime" }); + +export type V2ConfigRequirementsReadResponse__CliAuthCredentialsStoreMode = + | "file" + | "keyring" + | "auto" + | "ephemeral"; +export const V2ConfigRequirementsReadResponse__CliAuthCredentialsStoreMode = Schema.Literals([ + "file", + "keyring", + "auto", + "ephemeral", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__CliAuthCredentialsStoreMode" }); + +export type V2ConfigRequirementsReadResponse__ResidencyRequirement = "us"; +export const V2ConfigRequirementsReadResponse__ResidencyRequirement = Schema.Literal("us").annotate( + { identifier: "V2ConfigRequirementsReadResponse__ResidencyRequirement" }, ); -export type V2ConfigRequirementsReadResponse__ComputerUseRequirements = { - readonly allowLockedComputerUse?: boolean | null; +export type V2ConfigRequirementsReadResponse__FeedbackRequirements = { + readonly enabled?: boolean | null; }; -export const V2ConfigRequirementsReadResponse__ComputerUseRequirements = Schema.Struct({ - allowLockedComputerUse: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), -}); +export const V2ConfigRequirementsReadResponse__FeedbackRequirements = Schema.Struct({ + enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__FeedbackRequirements" }); + +export type V2ConfigRequirementsReadResponse__InAppBrowserRequirements = { + readonly allowExternalBrowserSettingsImport?: boolean | null; +}; +export const V2ConfigRequirementsReadResponse__InAppBrowserRequirements = Schema.Struct({ + allowExternalBrowserSettingsImport: Schema.optionalKey( + Schema.Union([Schema.Boolean, Schema.Null]), + ), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__InAppBrowserRequirements" }); + +export type V2ConfigRequirementsReadResponse__ReasoningEffort = string; +export const V2ConfigRequirementsReadResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ConfigRequirementsReadResponse__ReasoningEffort", + }), +); + +export type V2ConfigRequirementsReadResponse__NetworkDomainPermission = "allow" | "deny"; +export const V2ConfigRequirementsReadResponse__NetworkDomainPermission = Schema.Literals([ + "allow", + "deny", +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__NetworkDomainPermission" }); export type V2ConfigRequirementsReadResponse__ConfiguredHookHandler = | { + readonly additionalContextLimit?: number | null; readonly async: boolean; readonly command: string; readonly commandWindows?: string | null; @@ -3725,11 +4873,35 @@ export type V2ConfigRequirementsReadResponse__ConfiguredHookHandler = readonly timeoutSec?: number | null; readonly type: "command"; } + | { + readonly input: { readonly [x: string]: Schema.Json }; + readonly server: string; + readonly statusMessage?: string | null; + readonly timeoutSec?: number | null; + readonly tool: string; + readonly type: "mcp_tool"; + } | { readonly type: "prompt" } | { readonly type: "agent" }; export const V2ConfigRequirementsReadResponse__ConfiguredHookHandler = Schema.Union( [ Schema.Struct({ + additionalContextLimit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Approximate token threshold for spilling this hook's `additionalContext` to disk. `null` uses 2,500 tokens; `0` disables spilling for this hook. The threshold is evaluated against the original context; a spilled preview also includes recovery metadata.", + format: "uint", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), async: Schema.Boolean, command: Schema.String, commandWindows: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -3737,13 +4909,36 @@ export const V2ConfigRequirementsReadResponse__ConfiguredHookHandler = Schema.Un timeoutSec: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), type: Schema.Literal("command").annotate({ title: "CommandConfiguredHookHandlerType" }), }).annotate({ title: "CommandConfiguredHookHandler" }), + Schema.Struct({ + input: Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + server: Schema.String, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + timeoutSec: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + tool: Schema.String, + type: Schema.Literal("mcp_tool").annotate({ title: "McpToolConfiguredHookHandlerType" }), + }).annotate({ title: "McpToolConfiguredHookHandler" }), Schema.Struct({ type: Schema.Literal("prompt").annotate({ title: "PromptConfiguredHookHandlerType" }), }).annotate({ title: "PromptConfiguredHookHandler" }), @@ -3752,58 +4947,19 @@ export const V2ConfigRequirementsReadResponse__ConfiguredHookHandler = Schema.Un }).annotate({ title: "AgentConfiguredHookHandler" }), ], { mode: "oneOf" }, -); - -export type V2ConfigRequirementsReadResponse__NetworkDomainPermission = "allow" | "deny"; -export const V2ConfigRequirementsReadResponse__NetworkDomainPermission = Schema.Literals([ - "allow", - "deny", -]); +).annotate({ identifier: "V2ConfigRequirementsReadResponse__ConfiguredHookHandler" }); export type V2ConfigRequirementsReadResponse__NetworkUnixSocketPermission = "allow" | "deny"; export const V2ConfigRequirementsReadResponse__NetworkUnixSocketPermission = Schema.Literals([ "allow", "deny", -]); - -export type V2ConfigRequirementsReadResponse__ReasoningEffort = string; -export const V2ConfigRequirementsReadResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); - -export type V2ConfigRequirementsReadResponse__ResidencyRequirement = "us"; -export const V2ConfigRequirementsReadResponse__ResidencyRequirement = Schema.Literal("us"); - -export type V2ConfigRequirementsReadResponse__SandboxMode = - | "read-only" - | "workspace-write" - | "danger-full-access"; -export const V2ConfigRequirementsReadResponse__SandboxMode = Schema.Literals([ - "read-only", - "workspace-write", - "danger-full-access", -]); - -export type V2ConfigRequirementsReadResponse__WebSearchMode = - | "disabled" - | "cached" - | "indexed" - | "live"; -export const V2ConfigRequirementsReadResponse__WebSearchMode = Schema.Literals([ - "disabled", - "cached", - "indexed", - "live", -]); - -export type V2ConfigRequirementsReadResponse__WindowsSandboxSetupMode = "elevated" | "unelevated"; -export const V2ConfigRequirementsReadResponse__WindowsSandboxSetupMode = Schema.Literals([ - "elevated", - "unelevated", -]); +]).annotate({ identifier: "V2ConfigRequirementsReadResponse__NetworkUnixSocketPermission" }); export type V2ConfigValueWriteParams__MergeStrategy = "replace" | "upsert"; -export const V2ConfigValueWriteParams__MergeStrategy = Schema.Literals(["replace", "upsert"]); +export const V2ConfigValueWriteParams__MergeStrategy = Schema.Literals([ + "replace", + "upsert", +]).annotate({ identifier: "V2ConfigValueWriteParams__MergeStrategy" }); export type V2ConfigWarningNotification__TextPosition = { readonly column: number; @@ -3814,21 +4970,28 @@ export const V2ConfigWarningNotification__TextPosition = Schema.Struct({ description: "1-based column number (in Unicode scalar values).", format: "uint", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), line: Schema.Number.annotate({ description: "1-based line number.", format: "uint" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}); + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ConfigWarningNotification__TextPosition" }); export type V2ConfigWriteResponse__AbsolutePathBuf = string; export const V2ConfigWriteResponse__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.", + identifier: "V2ConfigWriteResponse__AbsolutePathBuf", }); export type V2ConfigWriteResponse__WriteStatus = "ok" | "okOverridden"; -export const V2ConfigWriteResponse__WriteStatus = Schema.Literals(["ok", "okOverridden"]); +export const V2ConfigWriteResponse__WriteStatus = Schema.Literals(["ok", "okOverridden"]).annotate({ + identifier: "V2ConfigWriteResponse__WriteStatus", +}); export type V2ConsumeAccountRateLimitResetCreditResponse__ConsumeAccountRateLimitResetCreditOutcome = | "reset" @@ -3836,101 +4999,85 @@ export type V2ConsumeAccountRateLimitResetCreditResponse__ConsumeAccountRateLimi | "noCredit" | "alreadyRedeemed"; export const V2ConsumeAccountRateLimitResetCreditResponse__ConsumeAccountRateLimitResetCreditOutcome = - Schema.Literals(["reset", "nothingToReset", "noCredit", "alreadyRedeemed"]); - -export type V2ErrorNotification__NonSteerableTurnKind = "review" | "compact"; -export const V2ErrorNotification__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); - -export type V2ExperimentalFeatureListResponse__ExperimentalFeature = { - readonly announcement?: string | null; - readonly defaultEnabled: boolean; - readonly description?: string | null; - readonly displayName?: string | null; - readonly enabled: boolean; - readonly name: string; - readonly stage: "beta" | "underDevelopment" | "stable" | "deprecated" | "removed"; -}; -export const V2ExperimentalFeatureListResponse__ExperimentalFeature = Schema.Struct({ - announcement: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.", + Schema.Union( + [ + Schema.Literal("reset").annotate({ + description: "A reset credit was consumed and the eligible rate-limit windows were reset.", }), - Schema.Null, - ]), - ), - defaultEnabled: Schema.Boolean.annotate({ - description: "Whether this feature is enabled by default.", - }), - description: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Short summary describing what the feature does. Null when this feature is not in beta.", + Schema.Literal("nothingToReset").annotate({ + description: "No current rate-limit window is eligible for a reset.", }), - Schema.Null, - ]), - ), - displayName: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.", + Schema.Literal("noCredit").annotate({ + description: "The account has no earned reset credits available.", }), - Schema.Null, - ]), - ), - enabled: Schema.Boolean.annotate({ - description: "Whether this feature is currently enabled in the loaded config.", - }), - name: Schema.String.annotate({ - description: "Stable key used in config.toml and CLI flag toggles.", - }), - stage: Schema.Literals(["beta", "underDevelopment", "stable", "deprecated", "removed"]).annotate({ - description: "Lifecycle stage of this feature flag.", - }), -}); + Schema.Literal("alreadyRedeemed").annotate({ + description: "The same idempotency key already completed a reset successfully.", + }), + ], + { mode: "oneOf" }, + ).annotate({ + identifier: + "V2ConsumeAccountRateLimitResetCreditResponse__ConsumeAccountRateLimitResetCreditOutcome", + }); + +export type V2ErrorNotification__NonSteerableTurnKind = "review" | "compact"; +export const V2ErrorNotification__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ErrorNotification__NonSteerableTurnKind" }); + +export type V2ErrorNotification__MisalignmentSteer = { readonly message: string }; +export const V2ErrorNotification__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ErrorNotification__MisalignmentSteer" }); + +export type V2ExperimentalFeatureListResponse__ExperimentalFeatureStage = + | "beta" + | "underDevelopment" + | "stable" + | "deprecated" + | "removed"; +export const V2ExperimentalFeatureListResponse__ExperimentalFeatureStage = Schema.Union( + [ + Schema.Literal("beta").annotate({ + description: "Feature is available for user testing and feedback.", + }), + Schema.Literal("underDevelopment").annotate({ + description: "Feature is still being built and not ready for broad use.", + }), + Schema.Literal("stable").annotate({ description: "Feature is production-ready." }), + Schema.Literal("deprecated").annotate({ + description: "Feature is deprecated and should be avoided.", + }), + Schema.Literal("removed").annotate({ + description: "Feature flag is retained only for backwards compatibility.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ExperimentalFeatureListResponse__ExperimentalFeatureStage" }); + +export type V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorSource = + | "remoteMcpServersConfig" + | "sessionToolUse"; +export const V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorSource = + Schema.Literals(["remoteMcpServersConfig", "sessionToolUse"]).annotate({ + identifier: "V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorSource", + }); export type V2ExternalAgentConfigDetectResponse__CommandMigration = { readonly name: string }; export const V2ExternalAgentConfigDetectResponse__CommandMigration = Schema.Struct({ name: Schema.String, -}); - -export type V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType = - | "AGENTS_MD" - | "CONFIG" - | "SKILLS" - | "PLUGINS" - | "MCP_SERVER_CONFIG" - | "SUBAGENTS" - | "HOOKS" - | "COMMANDS" - | "MEMORY" - | "SESSIONS"; -export const V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType = - Schema.Literals([ - "AGENTS_MD", - "CONFIG", - "SKILLS", - "PLUGINS", - "MCP_SERVER_CONFIG", - "SUBAGENTS", - "HOOKS", - "COMMANDS", - "MEMORY", - "SESSIONS", - ]); +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__CommandMigration" }); export type V2ExternalAgentConfigDetectResponse__HookMigration = { readonly name: string }; export const V2ExternalAgentConfigDetectResponse__HookMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__HookMigration" }); export type V2ExternalAgentConfigDetectResponse__McpServerMigration = { readonly name: string }; export const V2ExternalAgentConfigDetectResponse__McpServerMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__McpServerMigration" }); export type V2ExternalAgentConfigDetectResponse__PluginsMigration = { readonly marketplaceName: string; @@ -3939,7 +5086,7 @@ export type V2ExternalAgentConfigDetectResponse__PluginsMigration = { export const V2ExternalAgentConfigDetectResponse__PluginsMigration = Schema.Struct({ marketplaceName: Schema.String, pluginNames: Schema.Array(Schema.String), -}); +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__PluginsMigration" }); export type V2ExternalAgentConfigDetectResponse__SessionMigration = { readonly cwd: string; @@ -3950,19 +5097,19 @@ export const V2ExternalAgentConfigDetectResponse__SessionMigration = Schema.Stru cwd: Schema.String, path: Schema.String, title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__SessionMigration" }); export type V2ExternalAgentConfigDetectResponse__SkillMigration = { readonly name: string }; export const V2ExternalAgentConfigDetectResponse__SkillMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__SkillMigration" }); export type V2ExternalAgentConfigDetectResponse__SubagentMigration = { readonly name: string }; export const V2ExternalAgentConfigDetectResponse__SubagentMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__SubagentMigration" }); -export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType = +export type V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType = | "AGENTS_MD" | "CONFIG" | "SKILLS" @@ -3973,7 +5120,7 @@ export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfi | "COMMANDS" | "MEMORY" | "SESSIONS"; -export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType = +export const V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType = Schema.Literals([ "AGENTS_MD", "CONFIG", @@ -3985,9 +5132,11 @@ export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConf "COMMANDS", "MEMORY", "SESSIONS", - ]); + ]).annotate({ + identifier: "V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType", + }); -export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType = +export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType = | "AGENTS_MD" | "CONFIG" | "SKILLS" @@ -3998,7 +5147,7 @@ export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfi | "COMMANDS" | "MEMORY" | "SESSIONS"; -export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType = +export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType = Schema.Literals([ "AGENTS_MD", "CONFIG", @@ -4010,19 +5159,48 @@ export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConf "COMMANDS", "MEMORY", "SESSIONS", - ]); + ]).annotate({ + identifier: + "V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType", + }); export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorSource = "remoteMcpServersConfig"; export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorSource = - Schema.Literal("remoteMcpServersConfig"); + Schema.Literal("remoteMcpServersConfig").annotate({ + identifier: + "V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorSource", + }); -export type V2ExternalAgentConfigImportParams__CommandMigration = { readonly name: string }; -export const V2ExternalAgentConfigImportParams__CommandMigration = Schema.Struct({ - name: Schema.String, -}); +export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType = + | "AGENTS_MD" + | "CONFIG" + | "SKILLS" + | "PLUGINS" + | "MCP_SERVER_CONFIG" + | "SUBAGENTS" + | "HOOKS" + | "COMMANDS" + | "MEMORY" + | "SESSIONS"; +export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType = + Schema.Literals([ + "AGENTS_MD", + "CONFIG", + "SKILLS", + "PLUGINS", + "MCP_SERVER_CONFIG", + "SUBAGENTS", + "HOOKS", + "COMMANDS", + "MEMORY", + "SESSIONS", + ]).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType", + }); -export type V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType = +export type V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType = | "AGENTS_MD" | "CONFIG" | "SKILLS" @@ -4033,7 +5211,7 @@ export type V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemT | "COMMANDS" | "MEMORY" | "SESSIONS"; -export const V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType = +export const V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType = Schema.Literals([ "AGENTS_MD", "CONFIG", @@ -4045,17 +5223,25 @@ export const V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItem "COMMANDS", "MEMORY", "SESSIONS", - ]); + ]).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType", + }); + +export type V2ExternalAgentConfigImportParams__CommandMigration = { readonly name: string }; +export const V2ExternalAgentConfigImportParams__CommandMigration = Schema.Struct({ + name: Schema.String, +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__CommandMigration" }); export type V2ExternalAgentConfigImportParams__HookMigration = { readonly name: string }; export const V2ExternalAgentConfigImportParams__HookMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__HookMigration" }); export type V2ExternalAgentConfigImportParams__McpServerMigration = { readonly name: string }; export const V2ExternalAgentConfigImportParams__McpServerMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__McpServerMigration" }); export type V2ExternalAgentConfigImportParams__PluginsMigration = { readonly marketplaceName: string; @@ -4064,7 +5250,7 @@ export type V2ExternalAgentConfigImportParams__PluginsMigration = { export const V2ExternalAgentConfigImportParams__PluginsMigration = Schema.Struct({ marketplaceName: Schema.String, pluginNames: Schema.Array(Schema.String), -}); +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__PluginsMigration" }); export type V2ExternalAgentConfigImportParams__SessionMigration = { readonly cwd: string; @@ -4075,17 +5261,44 @@ export const V2ExternalAgentConfigImportParams__SessionMigration = Schema.Struct cwd: Schema.String, path: Schema.String, title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__SessionMigration" }); export type V2ExternalAgentConfigImportParams__SkillMigration = { readonly name: string }; export const V2ExternalAgentConfigImportParams__SkillMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__SkillMigration" }); export type V2ExternalAgentConfigImportParams__SubagentMigration = { readonly name: string }; export const V2ExternalAgentConfigImportParams__SubagentMigration = Schema.Struct({ name: Schema.String, -}); +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__SubagentMigration" }); + +export type V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType = + | "AGENTS_MD" + | "CONFIG" + | "SKILLS" + | "PLUGINS" + | "MCP_SERVER_CONFIG" + | "SUBAGENTS" + | "HOOKS" + | "COMMANDS" + | "MEMORY" + | "SESSIONS"; +export const V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType = + Schema.Literals([ + "AGENTS_MD", + "CONFIG", + "SKILLS", + "PLUGINS", + "MCP_SERVER_CONFIG", + "SUBAGENTS", + "HOOKS", + "COMMANDS", + "MEMORY", + "SESSIONS", + ]).annotate({ + identifier: "V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType", + }); export type V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType = | "AGENTS_MD" @@ -4110,7 +5323,10 @@ export const V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfi "COMMANDS", "MEMORY", "SESSIONS", - ]); + ]).annotate({ + identifier: + "V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType", + }); export type V2FileChangePatchUpdatedNotification__PatchChangeKind = | { readonly type: "add" } @@ -4130,33 +5346,118 @@ export const V2FileChangePatchUpdatedNotification__PatchChangeKind = Schema.Unio }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2FileChangePatchUpdatedNotification__PatchChangeKind" }); export type V2FsChangedNotification__AbsolutePathBuf = string; export const V2FsChangedNotification__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.", + identifier: "V2FsChangedNotification__AbsolutePathBuf", }); -export type V2FsReadDirectoryResponse__FsReadDirectoryEntry = { - readonly fileName: string; - readonly isDirectory: boolean; - readonly isFile: boolean; -}; -export const V2FsReadDirectoryResponse__FsReadDirectoryEntry = Schema.Struct({ - fileName: Schema.String.annotate({ - description: "Direct child entry name only, not an absolute or relative path.", - }), - isDirectory: Schema.Boolean.annotate({ - description: "Whether this entry resolves to a directory.", - }), - isFile: Schema.Boolean.annotate({ - description: "Whether this entry resolves to a regular file.", - }), -}).annotate({ description: "A directory entry returned by `fs/readDirectory`." }); +export type V2FsCopyParams__AbsolutePathBuf = string; +export const V2FsCopyParams__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.", + identifier: "V2FsCopyParams__AbsolutePathBuf", +}); -export type V2GetAccountRateLimitsResponse__CreditsSnapshot = { - readonly balance?: string | null; +export type V2FsCreateDirectoryParams__AbsolutePathBuf = string; +export const V2FsCreateDirectoryParams__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.", + identifier: "V2FsCreateDirectoryParams__AbsolutePathBuf", +}); + +export type V2FsGetMetadataParams__AbsolutePathBuf = string; +export const V2FsGetMetadataParams__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.", + identifier: "V2FsGetMetadataParams__AbsolutePathBuf", +}); + +export type V2FsReadDirectoryParams__AbsolutePathBuf = string; +export const V2FsReadDirectoryParams__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.", + identifier: "V2FsReadDirectoryParams__AbsolutePathBuf", +}); + +export type V2FsReadDirectoryResponse__FsReadDirectoryEntry = { + readonly fileName: string; + readonly isDirectory: boolean; + readonly isFile: boolean; +}; +export const V2FsReadDirectoryResponse__FsReadDirectoryEntry = Schema.Struct({ + fileName: Schema.String.annotate({ + description: "Direct child entry name only, not an absolute or relative path.", + }), + isDirectory: Schema.Boolean.annotate({ + description: "Whether this entry resolves to a directory.", + }), + isFile: Schema.Boolean.annotate({ + description: "Whether this entry resolves to a regular file.", + }), +}).annotate({ + description: "A directory entry returned by `fs/readDirectory`.", + identifier: "V2FsReadDirectoryResponse__FsReadDirectoryEntry", +}); + +export type V2FsReadFileParams__AbsolutePathBuf = string; +export const V2FsReadFileParams__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.", + identifier: "V2FsReadFileParams__AbsolutePathBuf", +}); + +export type V2FsRemoveParams__AbsolutePathBuf = string; +export const V2FsRemoveParams__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.", + identifier: "V2FsRemoveParams__AbsolutePathBuf", +}); + +export type V2FsWatchParams__AbsolutePathBuf = string; +export const V2FsWatchParams__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.", + identifier: "V2FsWatchParams__AbsolutePathBuf", +}); + +export type V2FsWatchResponse__AbsolutePathBuf = string; +export const V2FsWatchResponse__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.", + identifier: "V2FsWatchResponse__AbsolutePathBuf", +}); + +export type V2FsWriteFileParams__AbsolutePathBuf = string; +export const V2FsWriteFileParams__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.", + identifier: "V2FsWriteFileParams__AbsolutePathBuf", +}); + +export type V2GetAccountRateLimitsResponse__RateLimitResetType = "codexRateLimits" | "unknown"; +export const V2GetAccountRateLimitsResponse__RateLimitResetType = Schema.Literals([ + "codexRateLimits", + "unknown", +]).annotate({ identifier: "V2GetAccountRateLimitsResponse__RateLimitResetType" }); + +export type V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus = + | "available" + | "redeeming" + | "redeemed" + | "unknown"; +export const V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus = Schema.Literals([ + "available", + "redeeming", + "redeemed", + "unknown", +]).annotate({ identifier: "V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus" }); + +export type V2GetAccountRateLimitsResponse__CreditsSnapshot = { + readonly balance?: string | null; readonly hasCredits: boolean; readonly unlimited: boolean; }; @@ -4164,7 +5465,24 @@ export const V2GetAccountRateLimitsResponse__CreditsSnapshot = Schema.Struct({ balance: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), hasCredits: Schema.Boolean, unlimited: Schema.Boolean, -}); +}).annotate({ identifier: "V2GetAccountRateLimitsResponse__CreditsSnapshot" }); + +export type V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot = { + readonly limit: string; + readonly remainingPercent: number; + readonly resetsAt: number; + readonly used: string; +}; +export const V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot = Schema.Struct({ + limit: Schema.String, + remainingPercent: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + resetsAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + used: Schema.String, +}).annotate({ identifier: "V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot" }); export type V2GetAccountRateLimitsResponse__PlanType = | "free" @@ -4202,7 +5520,34 @@ export const V2GetAccountRateLimitsResponse__PlanType = Schema.Literals([ "edu_plus", "edu_pro", "unknown", -]); +]).annotate({ identifier: "V2GetAccountRateLimitsResponse__PlanType" }); + +export type V2GetAccountRateLimitsResponse__RateLimitWindow = { + readonly resetsAt?: number | null; + readonly usedPercent: number; + readonly windowDurationMins?: number | null; +}; +export const V2GetAccountRateLimitsResponse__RateLimitWindow = Schema.Struct({ + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + usedPercent: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + windowDurationMins: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2GetAccountRateLimitsResponse__RateLimitWindow" }); export type V2GetAccountRateLimitsResponse__RateLimitReachedType = | "rate_limit_reached" @@ -4216,53 +5561,7 @@ export const V2GetAccountRateLimitsResponse__RateLimitReachedType = Schema.Liter "workspace_member_credits_depleted", "workspace_owner_usage_limit_reached", "workspace_member_usage_limit_reached", -]); - -export type V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus = - | "available" - | "redeeming" - | "redeemed" - | "unknown"; -export const V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus = Schema.Literals([ - "available", - "redeeming", - "redeemed", - "unknown", -]); - -export type V2GetAccountRateLimitsResponse__RateLimitResetType = "codexRateLimits" | "unknown"; -export const V2GetAccountRateLimitsResponse__RateLimitResetType = Schema.Literals([ - "codexRateLimits", - "unknown", -]); - -export type V2GetAccountRateLimitsResponse__RateLimitWindow = { - readonly resetsAt?: number | null; - readonly usedPercent: number; - readonly windowDurationMins?: number | null; -}; -export const V2GetAccountRateLimitsResponse__RateLimitWindow = Schema.Struct({ - resetsAt: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - usedPercent: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), - windowDurationMins: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), -}); - -export type V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot = { - readonly limit: string; - readonly remainingPercent: number; - readonly resetsAt: number; - readonly used: string; -}; -export const V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot = Schema.Struct({ - limit: Schema.String, - remainingPercent: Schema.Number.annotate({ format: "int32" }).check(Schema.isInt()), - resetsAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - used: Schema.String, -}); +]).annotate({ identifier: "V2GetAccountRateLimitsResponse__RateLimitReachedType" }); export type V2GetAccountResponse__PlanType = | "free" @@ -4300,7 +5599,17 @@ export const V2GetAccountResponse__PlanType = Schema.Literals([ "edu_plus", "edu_pro", "unknown", -]); +]).annotate({ identifier: "V2GetAccountResponse__PlanType" }); + +export type V2GetAccountResponse__AccountRoutingOverride = "NO_CONSTRAINT" | "us" | "us_cr"; +export const V2GetAccountResponse__AccountRoutingOverride = Schema.Literals([ + "NO_CONSTRAINT", + "us", + "us_cr", +]).annotate({ + description: "Backend routing policy. Wire values match the accounts/check contract.", + identifier: "V2GetAccountResponse__AccountRoutingOverride", +}); export type V2GetAccountTokenUsageResponse__AccountTokenUsageDailyBucket = { readonly startDate: string; @@ -4308,8 +5617,10 @@ export type V2GetAccountTokenUsageResponse__AccountTokenUsageDailyBucket = { }; export const V2GetAccountTokenUsageResponse__AccountTokenUsageDailyBucket = Schema.Struct({ startDate: Schema.String, - tokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -}); + tokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +}).annotate({ identifier: "V2GetAccountTokenUsageResponse__AccountTokenUsageDailyBucket" }); export type V2GetAccountTokenUsageResponse__AccountTokenUsageSummary = { readonly currentStreakDays?: number | null; @@ -4320,21 +5631,106 @@ export type V2GetAccountTokenUsageResponse__AccountTokenUsageSummary = { }; export const V2GetAccountTokenUsageResponse__AccountTokenUsageSummary = Schema.Struct({ currentStreakDays: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), lifetimeTokens: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), longestRunningTurnSec: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), longestStreakDays: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), peakDailyTokens: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), -}); +}).annotate({ identifier: "V2GetAccountTokenUsageResponse__AccountTokenUsageSummary" }); + +export type V2GetAccountTokenUsageResponse__ThreadUsageBreakdownGroup = { + readonly cachedInputTokens?: number | null; + readonly estimatedUsageCreditsMicros: number; + readonly inputTokens?: number | null; + readonly model?: string | null; + readonly netNewInputTokens?: number | null; + readonly outputTokens?: number | null; + readonly reasoningEffort?: string | null; + readonly speed?: string | null; + readonly totalTokens?: number | null; +}; +export const V2GetAccountTokenUsageResponse__ThreadUsageBreakdownGroup = Schema.Struct({ + cachedInputTokens: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + estimatedUsageCreditsMicros: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + inputTokens: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + model: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + netNewInputTokens: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + outputTokens: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + reasoningEffort: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + speed: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + totalTokens: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2GetAccountTokenUsageResponse__ThreadUsageBreakdownGroup" }); export type V2GetWorkspaceMessagesResponse__WorkspaceMessageType = | "headline" @@ -4344,13 +5740,21 @@ export const V2GetWorkspaceMessagesResponse__WorkspaceMessageType = Schema.Liter "headline", "announcement", "unknown", -]); +]).annotate({ identifier: "V2GetWorkspaceMessagesResponse__WorkspaceMessageType" }); -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__HookOutputEntryKind = + | "warning" + | "stop" + | "feedback" + | "context" + | "error"; +export const V2HookCompletedNotification__HookOutputEntryKind = Schema.Literals([ + "warning", + "stop", + "feedback", + "context", + "error", +]).annotate({ identifier: "V2HookCompletedNotification__HookOutputEntryKind" }); export type V2HookCompletedNotification__HookEventName = | "preToolUse" @@ -4363,7 +5767,8 @@ export type V2HookCompletedNotification__HookEventName = | "userPromptSubmit" | "subagentStart" | "subagentStop" - | "stop"; + | "stop" + | "interrupt"; export const V2HookCompletedNotification__HookEventName = Schema.Literals([ "preToolUse", "permissionRequest", @@ -4376,31 +5781,64 @@ export const V2HookCompletedNotification__HookEventName = Schema.Literals([ "subagentStart", "subagentStop", "stop", -]); + "interrupt", +]).annotate({ identifier: "V2HookCompletedNotification__HookEventName" }); export type V2HookCompletedNotification__HookExecutionMode = "sync" | "async"; -export const V2HookCompletedNotification__HookExecutionMode = Schema.Literals(["sync", "async"]); - -export type V2HookCompletedNotification__HookHandlerType = "command" | "prompt" | "agent"; +export const V2HookCompletedNotification__HookExecutionMode = Schema.Literals([ + "sync", + "async", +]).annotate({ identifier: "V2HookCompletedNotification__HookExecutionMode" }); + +export type V2HookCompletedNotification__HookHandlerType = + | "command" + | "mcpTool" + | "prompt" + | "agent"; export const V2HookCompletedNotification__HookHandlerType = Schema.Literals([ "command", + "mcpTool", "prompt", "agent", -]); +]).annotate({ identifier: "V2HookCompletedNotification__HookHandlerType" }); -export type V2HookCompletedNotification__HookOutputEntryKind = - | "warning" - | "stop" - | "feedback" - | "context" - | "error"; -export const V2HookCompletedNotification__HookOutputEntryKind = Schema.Literals([ - "warning", - "stop", - "feedback", - "context", - "error", -]); +export type V2HookCompletedNotification__HookScope = "thread" | "turn"; +export const V2HookCompletedNotification__HookScope = Schema.Literals(["thread", "turn"]).annotate({ + identifier: "V2HookCompletedNotification__HookScope", +}); + +export type V2HookCompletedNotification__HookSource = + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "plugin" + | "cloudRequirements" + | "cloudManagedConfig" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; +export const V2HookCompletedNotification__HookSource = Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "plugin", + "cloudRequirements", + "cloudManagedConfig", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", +]).annotate({ identifier: "V2HookCompletedNotification__HookSource" }); + +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.", + identifier: "V2HookCompletedNotification__AbsolutePathBuf", +}); export type V2HookCompletedNotification__HookRunStatus = | "running" @@ -4414,16 +5852,7 @@ export const V2HookCompletedNotification__HookRunStatus = Schema.Literals([ "failed", "blocked", "stopped", -]); - -export type V2HookCompletedNotification__HookScope = "thread" | "turn"; -export const V2HookCompletedNotification__HookScope = Schema.Literals(["thread", "turn"]); - -export type V2HooksListResponse__AbsolutePathBuf = string; -export const V2HooksListResponse__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.", -}); +]).annotate({ identifier: "V2HookCompletedNotification__HookRunStatus" }); export type V2HooksListResponse__HookErrorInfo = { readonly message: string; @@ -4432,7 +5861,7 @@ export type V2HooksListResponse__HookErrorInfo = { export const V2HooksListResponse__HookErrorInfo = Schema.Struct({ message: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2HooksListResponse__HookErrorInfo" }); export type V2HooksListResponse__HookEventName = | "preToolUse" @@ -4445,7 +5874,8 @@ export type V2HooksListResponse__HookEventName = | "userPromptSubmit" | "subagentStart" | "subagentStop" - | "stop"; + | "stop" + | "interrupt"; export const V2HooksListResponse__HookEventName = Schema.Literals([ "preToolUse", "permissionRequest", @@ -4458,10 +5888,8 @@ export const V2HooksListResponse__HookEventName = Schema.Literals([ "subagentStart", "subagentStop", "stop", -]); - -export type V2HooksListResponse__HookHandlerType = "command" | "prompt" | "agent"; -export const V2HooksListResponse__HookHandlerType = Schema.Literals(["command", "prompt", "agent"]); + "interrupt", +]).annotate({ identifier: "V2HooksListResponse__HookEventName" }); export type V2HooksListResponse__HookSource = | "system" @@ -4487,7 +5915,14 @@ export const V2HooksListResponse__HookSource = Schema.Literals([ "legacyManagedConfigFile", "legacyManagedConfigMdm", "unknown", -]); +]).annotate({ identifier: "V2HooksListResponse__HookSource" }); + +export type V2HooksListResponse__AbsolutePathBuf = string; +export const V2HooksListResponse__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.", + identifier: "V2HooksListResponse__AbsolutePathBuf", +}); export type V2HooksListResponse__HookTrustStatus = "managed" | "untrusted" | "trusted" | "modified"; export const V2HooksListResponse__HookTrustStatus = Schema.Literals([ @@ -4495,13 +5930,21 @@ export const V2HooksListResponse__HookTrustStatus = Schema.Literals([ "untrusted", "trusted", "modified", -]); +]).annotate({ identifier: "V2HooksListResponse__HookTrustStatus" }); -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__HookOutputEntryKind = + | "warning" + | "stop" + | "feedback" + | "context" + | "error"; +export const V2HookStartedNotification__HookOutputEntryKind = Schema.Literals([ + "warning", + "stop", + "feedback", + "context", + "error", +]).annotate({ identifier: "V2HookStartedNotification__HookOutputEntryKind" }); export type V2HookStartedNotification__HookEventName = | "preToolUse" @@ -4514,7 +5957,8 @@ export type V2HookStartedNotification__HookEventName = | "userPromptSubmit" | "subagentStart" | "subagentStop" - | "stop"; + | "stop" + | "interrupt"; export const V2HookStartedNotification__HookEventName = Schema.Literals([ "preToolUse", "permissionRequest", @@ -4527,31 +5971,60 @@ export const V2HookStartedNotification__HookEventName = Schema.Literals([ "subagentStart", "subagentStop", "stop", -]); + "interrupt", +]).annotate({ identifier: "V2HookStartedNotification__HookEventName" }); export type V2HookStartedNotification__HookExecutionMode = "sync" | "async"; -export const V2HookStartedNotification__HookExecutionMode = Schema.Literals(["sync", "async"]); +export const V2HookStartedNotification__HookExecutionMode = Schema.Literals([ + "sync", + "async", +]).annotate({ identifier: "V2HookStartedNotification__HookExecutionMode" }); -export type V2HookStartedNotification__HookHandlerType = "command" | "prompt" | "agent"; +export type V2HookStartedNotification__HookHandlerType = "command" | "mcpTool" | "prompt" | "agent"; export const V2HookStartedNotification__HookHandlerType = Schema.Literals([ "command", + "mcpTool", "prompt", "agent", -]); +]).annotate({ identifier: "V2HookStartedNotification__HookHandlerType" }); -export type V2HookStartedNotification__HookOutputEntryKind = - | "warning" - | "stop" - | "feedback" - | "context" - | "error"; -export const V2HookStartedNotification__HookOutputEntryKind = Schema.Literals([ - "warning", - "stop", - "feedback", - "context", - "error", -]); +export type V2HookStartedNotification__HookScope = "thread" | "turn"; +export const V2HookStartedNotification__HookScope = Schema.Literals(["thread", "turn"]).annotate({ + identifier: "V2HookStartedNotification__HookScope", +}); + +export type V2HookStartedNotification__HookSource = + | "system" + | "user" + | "project" + | "mdm" + | "sessionFlags" + | "plugin" + | "cloudRequirements" + | "cloudManagedConfig" + | "legacyManagedConfigFile" + | "legacyManagedConfigMdm" + | "unknown"; +export const V2HookStartedNotification__HookSource = Schema.Literals([ + "system", + "user", + "project", + "mdm", + "sessionFlags", + "plugin", + "cloudRequirements", + "cloudManagedConfig", + "legacyManagedConfigFile", + "legacyManagedConfigMdm", + "unknown", +]).annotate({ identifier: "V2HookStartedNotification__HookSource" }); + +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.", + identifier: "V2HookStartedNotification__AbsolutePathBuf", +}); export type V2HookStartedNotification__HookRunStatus = | "running" @@ -4565,34 +6038,111 @@ export const V2HookStartedNotification__HookRunStatus = Schema.Literals([ "failed", "blocked", "stopped", -]); +]).annotate({ identifier: "V2HookStartedNotification__HookRunStatus" }); -export type V2HookStartedNotification__HookScope = "thread" | "turn"; -export const V2HookStartedNotification__HookScope = Schema.Literals(["thread", "turn"]); +export type V2ItemCompletedNotification__ByteRange = { + readonly end: number; + readonly start: number; +}; +export const V2ItemCompletedNotification__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ItemCompletedNotification__ByteRange" }); -export type V2ItemCompletedNotification__AbsolutePathBuf = string; -export const V2ItemCompletedNotification__AbsolutePathBuf = Schema.String.annotate({ +export type V2ItemCompletedNotification__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ItemCompletedNotification__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2ItemCompletedNotification__ImageDetail" }); + +export type V2ItemCompletedNotification__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; +}; +export const V2ItemCompletedNotification__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ItemCompletedNotification__HookPromptFragment" }); + +export type V2ItemCompletedNotification__AgentMessageDelivery = "async"; +export const V2ItemCompletedNotification__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ItemCompletedNotification__AgentMessageDelivery", +}); + +export type V2ItemCompletedNotification__MemoryCitationEntry = { + readonly lineEnd: number; + readonly lineStart: number; + readonly note: string; + readonly path: string; +}; +export const V2ItemCompletedNotification__MemoryCitationEntry = Schema.Struct({ + lineEnd: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + lineStart: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + note: Schema.String, + path: Schema.String, +}).annotate({ identifier: "V2ItemCompletedNotification__MemoryCitationEntry" }); + +export type V2ItemCompletedNotification__MessagePhase = "commentary" | "final_answer"; +export const V2ItemCompletedNotification__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.", + '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.', + identifier: "V2ItemCompletedNotification__MessagePhase", }); -export type V2ItemCompletedNotification__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ItemCompletedNotification__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +export type V2ItemCompletedNotification__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ItemCompletedNotification__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ItemCompletedNotification__AsyncUserInputQuestion" }); + +export type V2ItemCompletedNotification__LegacyAppPathString = string; +export const V2ItemCompletedNotification__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ItemCompletedNotification__LegacyAppPathString", +}); + +export type V2ItemCompletedNotification__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ItemCompletedNotification__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ItemCompletedNotification__CommandExecutionSource" }); export type V2ItemCompletedNotification__CommandExecutionStatus = | "inProgress" @@ -4604,72 +6154,46 @@ export const V2ItemCompletedNotification__CommandExecutionStatus = Schema.Litera "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ItemCompletedNotification__CommandExecutionStatus" }); -export type V2ItemCompletedNotification__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ItemCompletedNotification__DynamicToolCallOutputContentItem = Schema.Union( +export type V2ItemCompletedNotification__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const V2ItemCompletedNotification__PatchChangeKind = Schema.Union( [ Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), + }).annotate({ title: "AddPatchChangeKind" }), Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + type: Schema.Literal("delete").annotate({ title: "DeletePatchChangeKindType" }), + }).annotate({ title: "DeletePatchChangeKind" }), Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + move_path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("update").annotate({ title: "UpdatePatchChangeKindType" }), + }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ItemCompletedNotification__PatchChangeKind" }); -export type V2ItemCompletedNotification__DynamicToolCallStatus = +export type V2ItemCompletedNotification__PatchApplyStatus = | "inProgress" | "completed" - | "failed"; -export const V2ItemCompletedNotification__DynamicToolCallStatus = Schema.Literals([ + | "failed" + | "declined"; +export const V2ItemCompletedNotification__PatchApplyStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); + "declined", +]).annotate({ identifier: "V2ItemCompletedNotification__PatchApplyStatus" }); -export type V2ItemCompletedNotification__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; -}; -export const V2ItemCompletedNotification__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, -}); - -export type V2ItemCompletedNotification__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2ItemCompletedNotification__ImageDetail = Schema.Literals([ - "auto", - "low", - "high", - "original", -]); - -export type V2ItemCompletedNotification__LegacyAppPathString = string; -export const V2ItemCompletedNotification__LegacyAppPathString = Schema.String; - -export type V2ItemCompletedNotification__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ItemCompletedNotification__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; export const V2ItemCompletedNotification__McpToolCallAppContext = Schema.Struct({ actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -4677,93 +6201,136 @@ export const V2ItemCompletedNotification__McpToolCallAppContext = Schema.Struct( connectorId: Schema.String, linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ItemCompletedNotification__McpToolCallAppContext" }); export type V2ItemCompletedNotification__McpToolCallError = { readonly message: string }; export const V2ItemCompletedNotification__McpToolCallError = Schema.Struct({ message: Schema.String, -}); +}).annotate({ identifier: "V2ItemCompletedNotification__McpToolCallError" }); + +export type V2ItemCompletedNotification__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ItemCompletedNotification__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ItemCompletedNotification__McpAppDisplayMode" }); export type V2ItemCompletedNotification__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; }; export const V2ItemCompletedNotification__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ItemCompletedNotification__McpToolCallResult" }); export type V2ItemCompletedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; export const V2ItemCompletedNotification__McpToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); - -export type V2ItemCompletedNotification__MemoryCitationEntry = { - readonly lineEnd: number; - readonly lineStart: number; - readonly note: string; - readonly path: string; -}; -export const V2ItemCompletedNotification__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, -}); +]).annotate({ identifier: "V2ItemCompletedNotification__McpToolCallStatus" }); -export type V2ItemCompletedNotification__MessagePhase = "commentary" | "final_answer"; -export const V2ItemCompletedNotification__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 V2ItemCompletedNotification__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ItemCompletedNotification__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ItemCompletedNotification__DynamicToolCallOutputContentItem" }); -export type V2ItemCompletedNotification__PatchApplyStatus = +export type V2ItemCompletedNotification__DynamicToolCallStatus = | "inProgress" | "completed" - | "failed" - | "declined"; -export const V2ItemCompletedNotification__PatchApplyStatus = Schema.Literals([ + | "failed"; +export const V2ItemCompletedNotification__DynamicToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", - "declined", -]); +]).annotate({ identifier: "V2ItemCompletedNotification__DynamicToolCallStatus" }); -export type V2ItemCompletedNotification__PatchChangeKind = - | { readonly type: "add" } - | { readonly type: "delete" } - | { readonly move_path?: string | null; readonly type: "update" }; -export const V2ItemCompletedNotification__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 V2ItemCompletedNotification__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ItemCompletedNotification__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ItemCompletedNotification__CollabAgentStatus" }); export type V2ItemCompletedNotification__ReasoningEffort = string; export const V2ItemCompletedNotification__ReasoningEffort = Schema.String.annotate({ description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ItemCompletedNotification__ReasoningEffort", + }), +); + +export type V2ItemCompletedNotification__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const V2ItemCompletedNotification__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "interrupted", +]).annotate({ identifier: "V2ItemCompletedNotification__CollabAgentToolCallStatus" }); + +export type V2ItemCompletedNotification__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ItemCompletedNotification__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ItemCompletedNotification__CollabAgentTool" }); export type V2ItemCompletedNotification__SubAgentActivityKind = | "started" @@ -4775,32 +6342,7 @@ export const V2ItemCompletedNotification__SubAgentActivityKind = Schema.Literals "interacted", "interrupted", "completed", -]); - -export type V2ItemCompletedNotification__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const V2ItemCompletedNotification__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, - ]), - ), -}); +]).annotate({ identifier: "V2ItemCompletedNotification__SubAgentActivityKind" }); export type V2ItemCompletedNotification__WebSearchAction = | { @@ -4832,33 +6374,109 @@ export const V2ItemCompletedNotification__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ItemCompletedNotification__WebSearchAction" }); + +export type V2ItemCompletedNotification__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ItemCompletedNotification__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ItemCompletedNotification__ImageGenerationFailure" }); + +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.", + identifier: "V2ItemCompletedNotification__AbsolutePathBuf", +}); + +export type V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString = string; +export const V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString = + Schema.String.annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString", + }); + +export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource = + | "shell" + | "unifiedExec"; +export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource = + Schema.Literals(["shell", "unifiedExec"]).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource", + }); 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.", + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf", + }); + +export type V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol = + | "http" + | "https" + | "socks5Tcp" + | "socks5Udp"; +export const V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol = + Schema.Literals(["http", "https", "socks5Tcp", "socks5Udp"]).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol", + }); + +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode = + | "read" + | "write" + | "deny"; +export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode = + Schema.Literals(["read", "write", "deny"]).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode", }); export type V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions = { readonly enabled?: boolean | null; }; export const V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions = - Schema.Struct({ enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])) }); + Schema.Struct({ + enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + }).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions", + }); export type V2ItemGuardianApprovalReviewCompletedNotification__AutoReviewDecisionSource = "agent"; export const V2ItemGuardianApprovalReviewCompletedNotification__AutoReviewDecisionSource = Schema.Literal("agent").annotate({ description: "[UNSTABLE] Source that produced a terminal approval auto-review decision.", + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__AutoReviewDecisionSource", }); -export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode = - | "read" - | "write" - | "deny"; -export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode = - Schema.Literals(["read", "write", "deny"]); +export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel = + | "low" + | "medium" + | "high" + | "critical"; +export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel = Schema.Literals( + ["low", "medium", "high", "critical"], +).annotate({ + description: "[UNSTABLE] Risk level assigned by approval auto-review.", + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel", +}); export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus = | "inProgress" @@ -4869,23 +6487,9 @@ export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalR export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus = Schema.Literals(["inProgress", "approved", "denied", "timedOut", "aborted"]).annotate({ description: "[UNSTABLE] Lifecycle state for an approval auto-review.", + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus", }); -export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource = - | "shell" - | "unifiedExec"; -export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource = - Schema.Literals(["shell", "unifiedExec"]); - -export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel = - | "low" - | "medium" - | "high" - | "critical"; -export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel = Schema.Literals( - ["low", "medium", "high", "critical"], -).annotate({ description: "[UNSTABLE] Risk level assigned by approval auto-review." }); - export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization = | "unknown" | "low" @@ -4894,55 +6498,59 @@ export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAutho export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization = Schema.Literals(["unknown", "low", "medium", "high"]).annotate({ description: "[UNSTABLE] Authorization level assigned by approval auto-review.", + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization", }); -export type V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString = string; -export const V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString = Schema.String; +export type V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString = string; +export const V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString = + Schema.String.annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString", + }); -export type V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol = - | "http" - | "https" - | "socks5Tcp" - | "socks5Udp"; -export const V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol = - Schema.Literals(["http", "https", "socks5Tcp", "socks5Udp"]); +export type V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource = + | "shell" + | "unifiedExec"; +export const V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource = + Schema.Literals(["shell", "unifiedExec"]).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource", + }); 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.", + identifier: "V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf", }); -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__NetworkApprovalProtocol = + | "http" + | "https" + | "socks5Tcp" + | "socks5Udp"; +export const V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol = + Schema.Literals(["http", "https", "socks5Tcp", "socks5Udp"]).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol", + }); export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode = | "read" | "write" | "deny"; export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode = - Schema.Literals(["read", "write", "deny"]); - -export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus = - | "inProgress" - | "approved" - | "denied" - | "timedOut" - | "aborted"; -export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus = - Schema.Literals(["inProgress", "approved", "denied", "timedOut", "aborted"]).annotate({ - description: "[UNSTABLE] Lifecycle state for an approval auto-review.", + Schema.Literals(["read", "write", "deny"]).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode", }); -export type V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource = - | "shell" - | "unifiedExec"; -export const V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource = - Schema.Literals(["shell", "unifiedExec"]); +export type V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions = { + readonly enabled?: boolean | null; +}; +export const V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions = + Schema.Struct({ + enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + }).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions", + }); export type V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel = | "low" @@ -4954,7 +6562,22 @@ export const V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel "medium", "high", "critical", -]).annotate({ description: "[UNSTABLE] Risk level assigned by approval auto-review." }); +]).annotate({ + description: "[UNSTABLE] Risk level assigned by approval auto-review.", + identifier: "V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel", +}); + +export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus = + | "inProgress" + | "approved" + | "denied" + | "timedOut" + | "aborted"; +export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus = + Schema.Literals(["inProgress", "approved", "denied", "timedOut", "aborted"]).annotate({ + description: "[UNSTABLE] Lifecycle state for an approval auto-review.", + identifier: "V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus", + }); export type V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization = | "unknown" @@ -4964,42 +6587,109 @@ export type V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthori export const V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization = Schema.Literals(["unknown", "low", "medium", "high"]).annotate({ description: "[UNSTABLE] Authorization level assigned by approval auto-review.", + identifier: "V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization", }); -export type V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString = string; -export const V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString = Schema.String; +export type V2ItemStartedNotification__ByteRange = { readonly end: number; readonly start: number }; +export const V2ItemStartedNotification__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ItemStartedNotification__ByteRange" }); -export type V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol = - | "http" - | "https" - | "socks5Tcp" - | "socks5Udp"; -export const V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol = - Schema.Literals(["http", "https", "socks5Tcp", "socks5Udp"]); +export type V2ItemStartedNotification__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ItemStartedNotification__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2ItemStartedNotification__ImageDetail" }); -export type V2ItemStartedNotification__AbsolutePathBuf = string; -export const V2ItemStartedNotification__AbsolutePathBuf = Schema.String.annotate({ +export type V2ItemStartedNotification__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; +}; +export const V2ItemStartedNotification__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ItemStartedNotification__HookPromptFragment" }); + +export type V2ItemStartedNotification__AgentMessageDelivery = "async"; +export const V2ItemStartedNotification__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ItemStartedNotification__AgentMessageDelivery", +}); + +export type V2ItemStartedNotification__MemoryCitationEntry = { + readonly lineEnd: number; + readonly lineStart: number; + readonly note: string; + readonly path: string; +}; +export const V2ItemStartedNotification__MemoryCitationEntry = Schema.Struct({ + lineEnd: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + lineStart: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + note: Schema.String, + path: Schema.String, +}).annotate({ identifier: "V2ItemStartedNotification__MemoryCitationEntry" }); + +export type V2ItemStartedNotification__MessagePhase = "commentary" | "final_answer"; +export const V2ItemStartedNotification__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.", + '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.', + identifier: "V2ItemStartedNotification__MessagePhase", }); -export type V2ItemStartedNotification__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ItemStartedNotification__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +export type V2ItemStartedNotification__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ItemStartedNotification__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ItemStartedNotification__AsyncUserInputQuestion" }); + +export type V2ItemStartedNotification__LegacyAppPathString = string; +export const V2ItemStartedNotification__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ItemStartedNotification__LegacyAppPathString", +}); + +export type V2ItemStartedNotification__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ItemStartedNotification__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ItemStartedNotification__CommandExecutionSource" }); export type V2ItemStartedNotification__CommandExecutionStatus = | "inProgress" @@ -5011,65 +6701,39 @@ export const V2ItemStartedNotification__CommandExecutionStatus = Schema.Literals "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ItemStartedNotification__CommandExecutionStatus" }); -export type V2ItemStartedNotification__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ItemStartedNotification__DynamicToolCallOutputContentItem = Schema.Union( +export type V2ItemStartedNotification__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const V2ItemStartedNotification__PatchChangeKind = Schema.Union( [ Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), + }).annotate({ title: "AddPatchChangeKind" }), Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + type: Schema.Literal("delete").annotate({ title: "DeletePatchChangeKindType" }), + }).annotate({ title: "DeletePatchChangeKind" }), Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + move_path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("update").annotate({ title: "UpdatePatchChangeKindType" }), + }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ItemStartedNotification__PatchChangeKind" }); -export type V2ItemStartedNotification__DynamicToolCallStatus = +export type V2ItemStartedNotification__PatchApplyStatus = | "inProgress" | "completed" - | "failed"; -export const V2ItemStartedNotification__DynamicToolCallStatus = Schema.Literals([ + | "failed" + | "declined"; +export const V2ItemStartedNotification__PatchApplyStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); - -export type V2ItemStartedNotification__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; -}; -export const V2ItemStartedNotification__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, -}); - -export type V2ItemStartedNotification__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2ItemStartedNotification__ImageDetail = Schema.Literals([ - "auto", - "low", - "high", - "original", -]); - -export type V2ItemStartedNotification__LegacyAppPathString = string; -export const V2ItemStartedNotification__LegacyAppPathString = Schema.String; + "declined", +]).annotate({ identifier: "V2ItemStartedNotification__PatchApplyStatus" }); export type V2ItemStartedNotification__McpToolCallAppContext = { readonly actionName?: string | null; @@ -5084,93 +6748,136 @@ export const V2ItemStartedNotification__McpToolCallAppContext = Schema.Struct({ connectorId: Schema.String, linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ItemStartedNotification__McpToolCallAppContext" }); export type V2ItemStartedNotification__McpToolCallError = { readonly message: string }; export const V2ItemStartedNotification__McpToolCallError = Schema.Struct({ message: Schema.String, -}); +}).annotate({ identifier: "V2ItemStartedNotification__McpToolCallError" }); + +export type V2ItemStartedNotification__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ItemStartedNotification__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ItemStartedNotification__McpAppDisplayMode" }); export type V2ItemStartedNotification__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; }; export const V2ItemStartedNotification__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ItemStartedNotification__McpToolCallResult" }); export type V2ItemStartedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; export const V2ItemStartedNotification__McpToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); - -export type V2ItemStartedNotification__MemoryCitationEntry = { - readonly lineEnd: number; - readonly lineStart: number; - readonly note: string; - readonly path: string; -}; -export const V2ItemStartedNotification__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, -}); +]).annotate({ identifier: "V2ItemStartedNotification__McpToolCallStatus" }); -export type V2ItemStartedNotification__MessagePhase = "commentary" | "final_answer"; -export const V2ItemStartedNotification__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 V2ItemStartedNotification__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ItemStartedNotification__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ItemStartedNotification__DynamicToolCallOutputContentItem" }); -export type V2ItemStartedNotification__PatchApplyStatus = +export type V2ItemStartedNotification__DynamicToolCallStatus = | "inProgress" | "completed" - | "failed" - | "declined"; -export const V2ItemStartedNotification__PatchApplyStatus = Schema.Literals([ + | "failed"; +export const V2ItemStartedNotification__DynamicToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", - "declined", -]); +]).annotate({ identifier: "V2ItemStartedNotification__DynamicToolCallStatus" }); -export type V2ItemStartedNotification__PatchChangeKind = - | { readonly type: "add" } - | { readonly type: "delete" } - | { readonly move_path?: string | null; readonly type: "update" }; -export const V2ItemStartedNotification__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 V2ItemStartedNotification__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ItemStartedNotification__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ItemStartedNotification__CollabAgentStatus" }); export type V2ItemStartedNotification__ReasoningEffort = string; export const V2ItemStartedNotification__ReasoningEffort = Schema.String.annotate({ description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ItemStartedNotification__ReasoningEffort", + }), +); + +export type V2ItemStartedNotification__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const V2ItemStartedNotification__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "interrupted", +]).annotate({ identifier: "V2ItemStartedNotification__CollabAgentToolCallStatus" }); + +export type V2ItemStartedNotification__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ItemStartedNotification__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ItemStartedNotification__CollabAgentTool" }); export type V2ItemStartedNotification__SubAgentActivityKind = | "started" @@ -5182,32 +6889,7 @@ export const V2ItemStartedNotification__SubAgentActivityKind = Schema.Literals([ "interacted", "interrupted", "completed", -]); - -export type V2ItemStartedNotification__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const V2ItemStartedNotification__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, - ]), - ), -}); +]).annotate({ identifier: "V2ItemStartedNotification__SubAgentActivityKind" }); export type V2ItemStartedNotification__WebSearchAction = | { @@ -5239,48 +6921,85 @@ export const V2ItemStartedNotification__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ItemStartedNotification__WebSearchAction" }); + +export type V2ItemStartedNotification__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ItemStartedNotification__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ItemStartedNotification__ImageGenerationFailure" }); + +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.", + identifier: "V2ItemStartedNotification__AbsolutePathBuf", +}); export type V2ListMcpServerStatusParams__McpServerStatusDetail = "full" | "toolsAndAuthOnly"; export const V2ListMcpServerStatusParams__McpServerStatusDetail = Schema.Literals([ "full", "toolsAndAuthOnly", -]); +]).annotate({ identifier: "V2ListMcpServerStatusParams__McpServerStatusDetail" }); export type V2ListMcpServerStatusResponse__McpAuthStatus = + | "unknown" | "unsupported" | "notLoggedIn" | "bearerToken" | "oAuth"; export const V2ListMcpServerStatusResponse__McpAuthStatus = Schema.Literals([ + "unknown", "unsupported", "notLoggedIn", "bearerToken", "oAuth", -]); +]).annotate({ identifier: "V2ListMcpServerStatusResponse__McpAuthStatus" }); -export type V2ListMcpServerStatusResponse__McpServerInfo = { +export type V2ListMcpServerStatusResponse__ResourceTemplate = { + readonly annotations?: Schema.Json; readonly description?: string | null; - readonly icons?: ReadonlyArray | null; + readonly mimeType?: string | null; readonly name: string; readonly title?: string | null; - readonly version: string; - readonly websiteUrl?: string | null; + readonly uriTemplate: string; }; -export const V2ListMcpServerStatusResponse__McpServerInfo = Schema.Struct({ +export const V2ListMcpServerStatusResponse__ResourceTemplate = Schema.Struct({ + annotations: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - icons: Schema.optionalKey(Schema.Union([Schema.Array(Schema.Unknown), Schema.Null])), + mimeType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), name: Schema.String, title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - version: Schema.String, - websiteUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}).annotate({ description: "Presentation metadata advertised by an initialized MCP server." }); + uriTemplate: Schema.String, +}).annotate({ + description: "A template description for resources available on the server.", + identifier: "V2ListMcpServerStatusResponse__ResourceTemplate", +}); export type V2ListMcpServerStatusResponse__Resource = { - readonly _meta?: unknown; - readonly annotations?: unknown; + readonly _meta?: Schema.Json; + readonly annotations?: Schema.Json; readonly description?: string | null; - readonly icons?: ReadonlyArray | null; + readonly icons?: ReadonlyArray | null; readonly mimeType?: string | null; readonly name: string; readonly size?: number | null; @@ -5288,76 +7007,112 @@ export type V2ListMcpServerStatusResponse__Resource = { readonly uri: string; }; export const V2ListMcpServerStatusResponse__Resource = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - annotations: Schema.optionalKey(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + annotations: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - icons: Schema.optionalKey(Schema.Union([Schema.Array(Schema.Unknown), Schema.Null])), + icons: Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), Schema.Null]), + ), mimeType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), name: Schema.String, size: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), uri: Schema.String, -}).annotate({ description: "A known resource that the server is capable of reading." }); +}).annotate({ + description: "A known resource that the server is capable of reading.", + identifier: "V2ListMcpServerStatusResponse__Resource", +}); -export type V2ListMcpServerStatusResponse__ResourceTemplate = { - readonly annotations?: unknown; +export type V2ListMcpServerStatusResponse__McpServerConnectionStatus = + | "notStarted" + | "starting" + | "connected" + | "authenticationRequired" + | "failed" + | "cancelled" + | "disabled"; +export const V2ListMcpServerStatusResponse__McpServerConnectionStatus = Schema.Literals([ + "notStarted", + "starting", + "connected", + "authenticationRequired", + "failed", + "cancelled", + "disabled", +]).annotate({ identifier: "V2ListMcpServerStatusResponse__McpServerConnectionStatus" }); + +export type V2ListMcpServerStatusResponse__McpServerInfo = { readonly description?: string | null; - readonly mimeType?: string | null; + readonly icons?: ReadonlyArray | null; readonly name: string; readonly title?: string | null; - readonly uriTemplate: string; + readonly version: string; + readonly websiteUrl?: string | null; }; -export const V2ListMcpServerStatusResponse__ResourceTemplate = Schema.Struct({ - annotations: Schema.optionalKey(Schema.Unknown), +export const V2ListMcpServerStatusResponse__McpServerInfo = Schema.Struct({ description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - mimeType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icons: Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), Schema.Null]), + ), name: Schema.String, title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - uriTemplate: Schema.String, -}).annotate({ description: "A template description for resources available on the server." }); + version: Schema.String, + websiteUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Presentation metadata advertised by an initialized MCP server.", + identifier: "V2ListMcpServerStatusResponse__McpServerInfo", +}); export type V2ListMcpServerStatusResponse__Tool = { - readonly _meta?: unknown; - readonly annotations?: unknown; + readonly _meta?: Schema.Json; + readonly annotations?: Schema.Json; readonly description?: string | null; - readonly icons?: ReadonlyArray | null; - readonly inputSchema: unknown; + readonly icons?: ReadonlyArray | null; + readonly inputSchema: Schema.Json; readonly name: string; - readonly outputSchema?: unknown; + readonly outputSchema?: Schema.Json; readonly title?: string | null; }; export const V2ListMcpServerStatusResponse__Tool = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - annotations: Schema.optionalKey(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + annotations: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - icons: Schema.optionalKey(Schema.Union([Schema.Array(Schema.Unknown), Schema.Null])), - inputSchema: Schema.Unknown, + icons: Schema.optionalKey( + Schema.Union([Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), Schema.Null]), + ), + inputSchema: Schema.Json.annotate({ expected: "JSON value" }), name: Schema.String, - outputSchema: Schema.optionalKey(Schema.Unknown), + outputSchema: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}).annotate({ description: "Definition for a tool the client can call." }); +}).annotate({ + description: "Definition for a tool the client can call.", + identifier: "V2ListMcpServerStatusResponse__Tool", +}); export type V2LoginAccountParams__LoginAppBrand = "codex" | "chatgpt"; -export const V2LoginAccountParams__LoginAppBrand = Schema.Literals(["codex", "chatgpt"]); +export const V2LoginAccountParams__LoginAppBrand = Schema.Literals(["codex", "chatgpt"]).annotate({ + identifier: "V2LoginAccountParams__LoginAppBrand", +}); 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.", + identifier: "V2MarketplaceAddResponse__AbsolutePathBuf", }); 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 V2MarketplaceUpgradeResponse__AbsolutePathBuf = string; -export const V2MarketplaceUpgradeResponse__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.", + identifier: "V2MarketplaceRemoveResponse__AbsolutePathBuf", }); export type V2MarketplaceUpgradeResponse__MarketplaceUpgradeErrorInfo = { @@ -5367,41 +7122,67 @@ export type V2MarketplaceUpgradeResponse__MarketplaceUpgradeErrorInfo = { export const V2MarketplaceUpgradeResponse__MarketplaceUpgradeErrorInfo = Schema.Struct({ marketplaceName: Schema.String, message: Schema.String, +}).annotate({ identifier: "V2MarketplaceUpgradeResponse__MarketplaceUpgradeErrorInfo" }); + +export type V2MarketplaceUpgradeResponse__AbsolutePathBuf = string; +export const V2MarketplaceUpgradeResponse__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.", + identifier: "V2MarketplaceUpgradeResponse__AbsolutePathBuf", }); export type V2McpResourceReadResponse__ResourceContent = | { - readonly _meta?: unknown; + readonly _meta?: Schema.Json; readonly mimeType?: string | null; readonly text: string; readonly uri: string; } | { - readonly _meta?: unknown; + readonly _meta?: Schema.Json; readonly blob: string; readonly mimeType?: string | null; readonly uri: string; }; export const V2McpResourceReadResponse__ResourceContent = Schema.Union([ Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), mimeType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), text: Schema.String, uri: Schema.String.annotate({ description: "The URI of this resource." }), }), Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), blob: Schema.String, mimeType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), uri: Schema.String.annotate({ description: "The URI of this resource." }), }), -]).annotate({ description: "Contents returned when reading a resource from an MCP server." }); +]).annotate({ + description: "Contents returned when reading a resource from an MCP server.", + identifier: "V2McpResourceReadResponse__ResourceContent", +}); + +export type V2McpServerEventStreamNotification__McpServerEventNotification = { + readonly method: string; + readonly params: Schema.Json; +}; +export const V2McpServerEventStreamNotification__McpServerEventNotification = Schema.Struct({ + method: Schema.String, + params: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ identifier: "V2McpServerEventStreamNotification__McpServerEventNotification" }); + +export type V2McpServerOauthLoginParams__McpServerOauthClientRegistration = "auto" | "cimd" | "dcr"; +export const V2McpServerOauthLoginParams__McpServerOauthClientRegistration = Schema.Literals([ + "auto", + "cimd", + "dcr", +]).annotate({ identifier: "V2McpServerOauthLoginParams__McpServerOauthClientRegistration" }); export type V2McpServerStatusUpdatedNotification__McpServerStartupFailureReason = "reauthenticationRequired"; export const V2McpServerStatusUpdatedNotification__McpServerStartupFailureReason = Schema.Literal( "reauthenticationRequired", -); +).annotate({ identifier: "V2McpServerStatusUpdatedNotification__McpServerStartupFailureReason" }); export type V2McpServerStatusUpdatedNotification__McpServerStartupState = | "starting" @@ -5413,17 +7194,56 @@ export const V2McpServerStatusUpdatedNotification__McpServerStartupState = Schem "ready", "failed", "cancelled", -]); +]).annotate({ identifier: "V2McpServerStatusUpdatedNotification__McpServerStartupState" }); + +export type V2ModelListResponse__ModelAvailabilityNux = { readonly message: string }; +export const V2ModelListResponse__ModelAvailabilityNux = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ModelListResponse__ModelAvailabilityNux" }); + +export type V2ModelListResponse__CyberAccessProgram = "standard" | "daybreakBlue" | "daybreakRed"; +export const V2ModelListResponse__CyberAccessProgram = Schema.Literals([ + "standard", + "daybreakBlue", + "daybreakRed", +]).annotate({ + description: + "Requested cyber treatment for a ChatGPT-authenticated Codex turn. Authorization and model-tier restrictions remain server-owned.", + identifier: "V2ModelListResponse__CyberAccessProgram", +}); + +export type V2ModelListResponse__ReasoningEffort = string; +export const V2ModelListResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ModelListResponse__ReasoningEffort", + }), +); export type V2ModelListResponse__InputModality = "text" | "image" | "audio"; -export const V2ModelListResponse__InputModality = Schema.Literals([ - "text", - "image", - "audio", -]).annotate({ description: "Canonical user-input modality tags advertised by a model." }); +export const V2ModelListResponse__InputModality = Schema.Union( + [ + Schema.Literal("text").annotate({ description: "Plain text turns and tool payloads." }), + Schema.Literal("image").annotate({ description: "Image attachments included in user turns." }), + Schema.Literal("audio").annotate({ description: "Audio attachments included in user turns." }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Canonical user-input modality tags advertised by a model.", + identifier: "V2ModelListResponse__InputModality", +}); -export type V2ModelListResponse__ModelAvailabilityNux = { readonly message: string }; -export const V2ModelListResponse__ModelAvailabilityNux = Schema.Struct({ message: Schema.String }); +export type V2ModelListResponse__MultiAgentVersion = "disabled" | "v1" | "v2"; +export const V2ModelListResponse__MultiAgentVersion = Schema.Literals([ + "disabled", + "v1", + "v2", +]).annotate({ + description: "Multi-agent runtime supported by a model.", + identifier: "V2ModelListResponse__MultiAgentVersion", +}); export type V2ModelListResponse__ModelServiceTier = { readonly description: string; @@ -5434,33 +7254,79 @@ export const V2ModelListResponse__ModelServiceTier = Schema.Struct({ description: Schema.String, id: Schema.String, name: Schema.String, -}); +}).annotate({ identifier: "V2ModelListResponse__ModelServiceTier" }); export type V2ModelListResponse__ModelUpgradeInfo = { readonly migrationMarkdown?: string | null; readonly model: string; readonly modelLink?: string | null; + readonly retirementAt?: number | null; readonly upgradeCopy?: string | null; }; export const V2ModelListResponse__ModelUpgradeInfo = Schema.Struct({ migrationMarkdown: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), model: Schema.String, modelLink: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + retirementAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Informational Unix timestamp for this upgrade's scheduled retirement, if known.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), upgradeCopy: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ModelListResponse__ReasoningEffort = string; -export const V2ModelListResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +}).annotate({ identifier: "V2ModelListResponse__ModelUpgradeInfo" }); export type V2ModelReroutedNotification__ModelRerouteReason = "highRiskCyberActivity"; -export const V2ModelReroutedNotification__ModelRerouteReason = - Schema.Literal("highRiskCyberActivity"); +export const V2ModelReroutedNotification__ModelRerouteReason = Schema.Literal( + "highRiskCyberActivity", +).annotate({ identifier: "V2ModelReroutedNotification__ModelRerouteReason" }); export type V2ModelVerificationNotification__ModelVerification = "trustedAccessForCyber"; -export const V2ModelVerificationNotification__ModelVerification = - Schema.Literal("trustedAccessForCyber"); +export const V2ModelVerificationNotification__ModelVerification = Schema.Literal( + "trustedAccessForCyber", +).annotate({ identifier: "V2ModelVerificationNotification__ModelVerification" }); + +export type V2NullableGetAccountRateLimitsParams__GetAccountRateLimitsParams = { + readonly excludeResetCreditDetails?: boolean; + readonly supportsLunaReserve?: boolean; +}; +export const V2NullableGetAccountRateLimitsParams__GetAccountRateLimitsParams = Schema.Struct({ + excludeResetCreditDetails: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "Skip the separate reset-credit detail lookup for background usage polls. The usage response still includes the available count; omitted/false preserves detailed reads.", + }), + ), + supportsLunaReserve: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "The client supports automatic Luna Reserve fallback. For eligible ChatGPT CLI users, allow the backend to record experiment exposure after ordinary usage is blocked.", + }), + ), +}).annotate({ + description: + "Usage-read capabilities of the requesting client, never inferred from its experiment arm.", + identifier: "V2NullableGetAccountRateLimitsParams__GetAccountRateLimitsParams", +}); + +export type V2NullableGetAccountTokenUsageParams__GetAccountTokenUsageParams = { + readonly threadId?: string | null; +}; +export const V2NullableGetAccountTokenUsageParams__GetAccountTokenUsageParams = Schema.Struct({ + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "When present, read estimated usage for this thread instead of account-wide token activity.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2NullableGetAccountTokenUsageParams__GetAccountTokenUsageParams" }); export type V2PermissionProfileListResponse__PermissionProfileSummary = { readonly allowed: boolean; @@ -5480,18 +7346,20 @@ export const V2PermissionProfileListResponse__PermissionProfileSummary = Schema. ]), ), id: Schema.String.annotate({ description: "Available permission profile identifier." }), -}); +}).annotate({ identifier: "V2PermissionProfileListResponse__PermissionProfileSummary" }); export type V2PluginInstalledParams__AbsolutePathBuf = string; export const V2PluginInstalledParams__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.", + identifier: "V2PluginInstalledParams__AbsolutePathBuf", }); export type V2PluginInstalledResponse__AbsolutePathBuf = string; export const V2PluginInstalledResponse__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.", + identifier: "V2PluginInstalledResponse__AbsolutePathBuf", }); export type V2PluginInstalledResponse__MarketplaceInterface = { @@ -5499,13 +7367,37 @@ export type V2PluginInstalledResponse__MarketplaceInterface = { }; export const V2PluginInstalledResponse__MarketplaceInterface = Schema.Struct({ displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2PluginInstalledResponse__MarketplaceInterface" }); export type V2PluginInstalledResponse__PluginAuthPolicy = "ON_INSTALL" | "ON_USE"; export const V2PluginInstalledResponse__PluginAuthPolicy = Schema.Literals([ "ON_INSTALL", "ON_USE", -]); +]).annotate({ identifier: "V2PluginInstalledResponse__PluginAuthPolicy" }); + +export type V2PluginInstalledResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; +export const V2PluginInstalledResponse__PluginAvailability = Schema.Union( + [ + Schema.Literal("DISABLED_BY_ADMIN"), + Schema.Literal("AVAILABLE").annotate({ + description: + 'Plugin-service currently sends `"ENABLED"` for available remote plugins. Codex app-server exposes `"AVAILABLE"` in its API; the alias keeps decoding compatible with that upstream response.', + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2PluginInstalledResponse__PluginAvailability" }); + +export type V2PluginInstalledResponse__PluginDisabledReason = + | "disabled_by_admin" + | "plan_not_eligible" + | "required_app_unavailable" + | "unknown"; +export const V2PluginInstalledResponse__PluginDisabledReason = Schema.Literals([ + "disabled_by_admin", + "plan_not_eligible", + "required_app_unavailable", + "unknown", +]).annotate({ identifier: "V2PluginInstalledResponse__PluginDisabledReason" }); export type V2PluginInstalledResponse__PluginInstallPolicy = | "NOT_AVAILABLE" @@ -5515,7 +7407,7 @@ export const V2PluginInstalledResponse__PluginInstallPolicy = Schema.Literals([ "NOT_AVAILABLE", "AVAILABLE", "INSTALLED_BY_DEFAULT", -]); +]).annotate({ identifier: "V2PluginInstalledResponse__PluginInstallPolicy" }); export type V2PluginInstalledResponse__PluginInstallPolicySource = | "WORKSPACE_SETTING" @@ -5523,7 +7415,7 @@ export type V2PluginInstalledResponse__PluginInstallPolicySource = export const V2PluginInstalledResponse__PluginInstallPolicySource = Schema.Literals([ "WORKSPACE_SETTING", "IMPLICIT_CANONICAL_APP", -]); +]).annotate({ identifier: "V2PluginInstalledResponse__PluginInstallPolicySource" }); export type V2PluginInstalledResponse__PluginShareDiscoverability = | "LISTED" @@ -5533,26 +7425,27 @@ export const V2PluginInstalledResponse__PluginShareDiscoverability = Schema.Lite "LISTED", "UNLISTED", "PRIVATE", -]); - -export type V2PluginInstalledResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; -export const V2PluginInstalledResponse__PluginSharePrincipalRole = Schema.Literals([ - "reader", - "editor", - "owner", -]); +]).annotate({ identifier: "V2PluginInstalledResponse__PluginShareDiscoverability" }); export type V2PluginInstalledResponse__PluginSharePrincipalType = "user" | "group" | "workspace"; export const V2PluginInstalledResponse__PluginSharePrincipalType = Schema.Literals([ "user", "group", "workspace", -]); +]).annotate({ identifier: "V2PluginInstalledResponse__PluginSharePrincipalType" }); + +export type V2PluginInstalledResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; +export const V2PluginInstalledResponse__PluginSharePrincipalRole = Schema.Literals([ + "reader", + "editor", + "owner", +]).annotate({ identifier: "V2PluginInstalledResponse__PluginSharePrincipalRole" }); export type V2PluginInstallParams__AbsolutePathBuf = string; export const V2PluginInstallParams__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.", + identifier: "V2PluginInstallParams__AbsolutePathBuf", }); export type V2PluginInstallResponse__AppSummary = { @@ -5568,15 +7461,22 @@ export const V2PluginInstallResponse__AppSummary = Schema.Struct({ id: Schema.String, installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), name: Schema.String, -}).annotate({ description: "EXPERIMENTAL - app metadata summary for plugin responses." }); +}).annotate({ + description: "EXPERIMENTAL - app metadata summary for plugin responses.", + identifier: "V2PluginInstallResponse__AppSummary", +}); export type V2PluginInstallResponse__PluginAuthPolicy = "ON_INSTALL" | "ON_USE"; -export const V2PluginInstallResponse__PluginAuthPolicy = Schema.Literals(["ON_INSTALL", "ON_USE"]); +export const V2PluginInstallResponse__PluginAuthPolicy = Schema.Literals([ + "ON_INSTALL", + "ON_USE", +]).annotate({ identifier: "V2PluginInstallResponse__PluginAuthPolicy" }); export type V2PluginListParams__AbsolutePathBuf = string; export const V2PluginListParams__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.", + identifier: "V2PluginListParams__AbsolutePathBuf", }); export type V2PluginListParams__PluginListMarketplaceKind = @@ -5591,21 +7491,49 @@ export const V2PluginListParams__PluginListMarketplaceKind = Schema.Literals([ "workspace-directory", "shared-with-me", "created-by-me-remote", -]); +]).annotate({ identifier: "V2PluginListParams__PluginListMarketplaceKind" }); export type V2PluginListResponse__AbsolutePathBuf = string; export const V2PluginListResponse__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.", + identifier: "V2PluginListResponse__AbsolutePathBuf", }); export type V2PluginListResponse__MarketplaceInterface = { readonly displayName?: string | null }; export const V2PluginListResponse__MarketplaceInterface = Schema.Struct({ displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2PluginListResponse__MarketplaceInterface" }); export type V2PluginListResponse__PluginAuthPolicy = "ON_INSTALL" | "ON_USE"; -export const V2PluginListResponse__PluginAuthPolicy = Schema.Literals(["ON_INSTALL", "ON_USE"]); +export const V2PluginListResponse__PluginAuthPolicy = Schema.Literals([ + "ON_INSTALL", + "ON_USE", +]).annotate({ identifier: "V2PluginListResponse__PluginAuthPolicy" }); + +export type V2PluginListResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; +export const V2PluginListResponse__PluginAvailability = Schema.Union( + [ + Schema.Literal("DISABLED_BY_ADMIN"), + Schema.Literal("AVAILABLE").annotate({ + description: + 'Plugin-service currently sends `"ENABLED"` for available remote plugins. Codex app-server exposes `"AVAILABLE"` in its API; the alias keeps decoding compatible with that upstream response.', + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2PluginListResponse__PluginAvailability" }); + +export type V2PluginListResponse__PluginDisabledReason = + | "disabled_by_admin" + | "plan_not_eligible" + | "required_app_unavailable" + | "unknown"; +export const V2PluginListResponse__PluginDisabledReason = Schema.Literals([ + "disabled_by_admin", + "plan_not_eligible", + "required_app_unavailable", + "unknown", +]).annotate({ identifier: "V2PluginListResponse__PluginDisabledReason" }); export type V2PluginListResponse__PluginInstallPolicy = | "NOT_AVAILABLE" @@ -5615,7 +7543,7 @@ export const V2PluginListResponse__PluginInstallPolicy = Schema.Literals([ "NOT_AVAILABLE", "AVAILABLE", "INSTALLED_BY_DEFAULT", -]); +]).annotate({ identifier: "V2PluginListResponse__PluginInstallPolicy" }); export type V2PluginListResponse__PluginInstallPolicySource = | "WORKSPACE_SETTING" @@ -5623,40 +7551,43 @@ export type V2PluginListResponse__PluginInstallPolicySource = export const V2PluginListResponse__PluginInstallPolicySource = Schema.Literals([ "WORKSPACE_SETTING", "IMPLICIT_CANONICAL_APP", -]); +]).annotate({ identifier: "V2PluginListResponse__PluginInstallPolicySource" }); export type V2PluginListResponse__PluginShareDiscoverability = "LISTED" | "UNLISTED" | "PRIVATE"; export const V2PluginListResponse__PluginShareDiscoverability = Schema.Literals([ "LISTED", "UNLISTED", "PRIVATE", -]); - -export type V2PluginListResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; -export const V2PluginListResponse__PluginSharePrincipalRole = Schema.Literals([ - "reader", - "editor", - "owner", -]); +]).annotate({ identifier: "V2PluginListResponse__PluginShareDiscoverability" }); export type V2PluginListResponse__PluginSharePrincipalType = "user" | "group" | "workspace"; export const V2PluginListResponse__PluginSharePrincipalType = Schema.Literals([ "user", "group", "workspace", -]); +]).annotate({ identifier: "V2PluginListResponse__PluginSharePrincipalType" }); + +export type V2PluginListResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; +export const V2PluginListResponse__PluginSharePrincipalRole = Schema.Literals([ + "reader", + "editor", + "owner", +]).annotate({ identifier: "V2PluginListResponse__PluginSharePrincipalRole" }); export type V2PluginReadParams__AbsolutePathBuf = string; export const V2PluginReadParams__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.", + identifier: "V2PluginReadParams__AbsolutePathBuf", }); -export type V2PluginReadResponse__AbsolutePathBuf = string; -export const V2PluginReadResponse__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 V2PluginReadResponse__AppTemplateUnavailableReason = + | "NOT_CONFIGURED_FOR_WORKSPACE" + | "NO_ACTIVE_WORKSPACE"; +export const V2PluginReadResponse__AppTemplateUnavailableReason = Schema.Literals([ + "NOT_CONFIGURED_FOR_WORKSPACE", + "NO_ACTIVE_WORKSPACE", +]).annotate({ identifier: "V2PluginReadResponse__AppTemplateUnavailableReason" }); export type V2PluginReadResponse__AppSummary = { readonly category?: string | null; @@ -5671,15 +7602,10 @@ export const V2PluginReadResponse__AppSummary = Schema.Struct({ id: Schema.String, installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), name: Schema.String, -}).annotate({ description: "EXPERIMENTAL - app metadata summary for plugin responses." }); - -export type V2PluginReadResponse__AppTemplateUnavailableReason = - | "NOT_CONFIGURED_FOR_WORKSPACE" - | "NO_ACTIVE_WORKSPACE"; -export const V2PluginReadResponse__AppTemplateUnavailableReason = Schema.Literals([ - "NOT_CONFIGURED_FOR_WORKSPACE", - "NO_ACTIVE_WORKSPACE", -]); +}).annotate({ + description: "EXPERIMENTAL - app metadata summary for plugin responses.", + identifier: "V2PluginReadResponse__AppSummary", +}); export type V2PluginReadResponse__HookEventName = | "preToolUse" @@ -5692,7 +7618,8 @@ export type V2PluginReadResponse__HookEventName = | "userPromptSubmit" | "subagentStart" | "subagentStop" - | "stop"; + | "stop" + | "interrupt"; export const V2PluginReadResponse__HookEventName = Schema.Literals([ "preToolUse", "permissionRequest", @@ -5705,10 +7632,63 @@ export const V2PluginReadResponse__HookEventName = Schema.Literals([ "subagentStart", "subagentStop", "stop", -]); + "interrupt", +]).annotate({ identifier: "V2PluginReadResponse__HookEventName" }); + +export type V2PluginReadResponse__AbsolutePathBuf = string; +export const V2PluginReadResponse__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.", + identifier: "V2PluginReadResponse__AbsolutePathBuf", +}); + +export type V2PluginReadResponse__ScheduledTaskWeekday = + | "MO" + | "TU" + | "WE" + | "TH" + | "FR" + | "SA" + | "SU"; +export const V2PluginReadResponse__ScheduledTaskWeekday = Schema.Literals([ + "MO", + "TU", + "WE", + "TH", + "FR", + "SA", + "SU", +]).annotate({ identifier: "V2PluginReadResponse__ScheduledTaskWeekday" }); export type V2PluginReadResponse__PluginAuthPolicy = "ON_INSTALL" | "ON_USE"; -export const V2PluginReadResponse__PluginAuthPolicy = Schema.Literals(["ON_INSTALL", "ON_USE"]); +export const V2PluginReadResponse__PluginAuthPolicy = Schema.Literals([ + "ON_INSTALL", + "ON_USE", +]).annotate({ identifier: "V2PluginReadResponse__PluginAuthPolicy" }); + +export type V2PluginReadResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; +export const V2PluginReadResponse__PluginAvailability = Schema.Union( + [ + Schema.Literal("DISABLED_BY_ADMIN"), + Schema.Literal("AVAILABLE").annotate({ + description: + 'Plugin-service currently sends `"ENABLED"` for available remote plugins. Codex app-server exposes `"AVAILABLE"` in its API; the alias keeps decoding compatible with that upstream response.', + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2PluginReadResponse__PluginAvailability" }); + +export type V2PluginReadResponse__PluginDisabledReason = + | "disabled_by_admin" + | "plan_not_eligible" + | "required_app_unavailable" + | "unknown"; +export const V2PluginReadResponse__PluginDisabledReason = Schema.Literals([ + "disabled_by_admin", + "plan_not_eligible", + "required_app_unavailable", + "unknown", +]).annotate({ identifier: "V2PluginReadResponse__PluginDisabledReason" }); export type V2PluginReadResponse__PluginInstallPolicy = | "NOT_AVAILABLE" @@ -5718,7 +7698,7 @@ export const V2PluginReadResponse__PluginInstallPolicy = Schema.Literals([ "NOT_AVAILABLE", "AVAILABLE", "INSTALLED_BY_DEFAULT", -]); +]).annotate({ identifier: "V2PluginReadResponse__PluginInstallPolicy" }); export type V2PluginReadResponse__PluginInstallPolicySource = | "WORKSPACE_SETTING" @@ -5726,64 +7706,96 @@ export type V2PluginReadResponse__PluginInstallPolicySource = export const V2PluginReadResponse__PluginInstallPolicySource = Schema.Literals([ "WORKSPACE_SETTING", "IMPLICIT_CANONICAL_APP", -]); +]).annotate({ identifier: "V2PluginReadResponse__PluginInstallPolicySource" }); export type V2PluginReadResponse__PluginShareDiscoverability = "LISTED" | "UNLISTED" | "PRIVATE"; export const V2PluginReadResponse__PluginShareDiscoverability = Schema.Literals([ "LISTED", "UNLISTED", "PRIVATE", -]); - -export type V2PluginReadResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; -export const V2PluginReadResponse__PluginSharePrincipalRole = Schema.Literals([ - "reader", - "editor", - "owner", -]); +]).annotate({ identifier: "V2PluginReadResponse__PluginShareDiscoverability" }); export type V2PluginReadResponse__PluginSharePrincipalType = "user" | "group" | "workspace"; export const V2PluginReadResponse__PluginSharePrincipalType = Schema.Literals([ "user", "group", "workspace", -]); +]).annotate({ identifier: "V2PluginReadResponse__PluginSharePrincipalType" }); -export type V2PluginReadResponse__ScheduledTaskWeekday = - | "MO" - | "TU" - | "WE" - | "TH" - | "FR" - | "SA" - | "SU"; -export const V2PluginReadResponse__ScheduledTaskWeekday = Schema.Literals([ - "MO", - "TU", - "WE", - "TH", - "FR", - "SA", - "SU", -]); +export type V2PluginReadResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; +export const V2PluginReadResponse__PluginSharePrincipalRole = Schema.Literals([ + "reader", + "editor", + "owner", +]).annotate({ identifier: "V2PluginReadResponse__PluginSharePrincipalRole" }); + +export type V2PluginReconcileResponse__PluginReconcileChangedPlugin = { + readonly hasApps: boolean; + readonly hasHooks: boolean; + readonly hasMcps: boolean; + readonly hasSkills: boolean; + readonly id: string; +}; +export const V2PluginReconcileResponse__PluginReconcileChangedPlugin = Schema.Struct({ + hasApps: Schema.Boolean, + hasHooks: Schema.Boolean, + hasMcps: Schema.Boolean, + hasSkills: Schema.Boolean.annotate({ + description: + "Whether either bundle declares skill roots; not a validated inventory of enabled skills.", + }), + id: Schema.String.annotate({ + description: "Local plugin ID (`name@marketplace`), matching `PluginSummary.id`.", + }), +}).annotate({ + description: + "Runtime categories affected by this change, not just capabilities currently present. Flags describe declarations before runtime policy filtering. Updates OR the old and new bundle flags; enablement changes and cached reinstalls use the cached bundle; removals retain the old bundle's flags.", + identifier: "V2PluginReconcileResponse__PluginReconcileChangedPlugin", +}); export type V2PluginShareCheckoutResponse__AbsolutePathBuf = string; export const V2PluginShareCheckoutResponse__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.", + identifier: "V2PluginShareCheckoutResponse__AbsolutePathBuf", }); export type V2PluginShareListResponse__AbsolutePathBuf = string; export const V2PluginShareListResponse__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.", + identifier: "V2PluginShareListResponse__AbsolutePathBuf", }); export type V2PluginShareListResponse__PluginAuthPolicy = "ON_INSTALL" | "ON_USE"; export const V2PluginShareListResponse__PluginAuthPolicy = Schema.Literals([ "ON_INSTALL", "ON_USE", -]); +]).annotate({ identifier: "V2PluginShareListResponse__PluginAuthPolicy" }); + +export type V2PluginShareListResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; +export const V2PluginShareListResponse__PluginAvailability = Schema.Union( + [ + Schema.Literal("DISABLED_BY_ADMIN"), + Schema.Literal("AVAILABLE").annotate({ + description: + 'Plugin-service currently sends `"ENABLED"` for available remote plugins. Codex app-server exposes `"AVAILABLE"` in its API; the alias keeps decoding compatible with that upstream response.', + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2PluginShareListResponse__PluginAvailability" }); + +export type V2PluginShareListResponse__PluginDisabledReason = + | "disabled_by_admin" + | "plan_not_eligible" + | "required_app_unavailable" + | "unknown"; +export const V2PluginShareListResponse__PluginDisabledReason = Schema.Literals([ + "disabled_by_admin", + "plan_not_eligible", + "required_app_unavailable", + "unknown", +]).annotate({ identifier: "V2PluginShareListResponse__PluginDisabledReason" }); export type V2PluginShareListResponse__PluginInstallPolicy = | "NOT_AVAILABLE" @@ -5793,7 +7805,7 @@ export const V2PluginShareListResponse__PluginInstallPolicy = Schema.Literals([ "NOT_AVAILABLE", "AVAILABLE", "INSTALLED_BY_DEFAULT", -]); +]).annotate({ identifier: "V2PluginShareListResponse__PluginInstallPolicy" }); export type V2PluginShareListResponse__PluginInstallPolicySource = | "WORKSPACE_SETTING" @@ -5801,7 +7813,7 @@ export type V2PluginShareListResponse__PluginInstallPolicySource = export const V2PluginShareListResponse__PluginInstallPolicySource = Schema.Literals([ "WORKSPACE_SETTING", "IMPLICIT_CANONICAL_APP", -]); +]).annotate({ identifier: "V2PluginShareListResponse__PluginInstallPolicySource" }); export type V2PluginShareListResponse__PluginShareDiscoverability = | "LISTED" @@ -5811,44 +7823,58 @@ export const V2PluginShareListResponse__PluginShareDiscoverability = Schema.Lite "LISTED", "UNLISTED", "PRIVATE", -]); - -export type V2PluginShareListResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; -export const V2PluginShareListResponse__PluginSharePrincipalRole = Schema.Literals([ - "reader", - "editor", - "owner", -]); +]).annotate({ identifier: "V2PluginShareListResponse__PluginShareDiscoverability" }); export type V2PluginShareListResponse__PluginSharePrincipalType = "user" | "group" | "workspace"; export const V2PluginShareListResponse__PluginSharePrincipalType = Schema.Literals([ "user", "group", "workspace", -]); +]).annotate({ identifier: "V2PluginShareListResponse__PluginSharePrincipalType" }); -export type V2PluginShareSaveParams__AbsolutePathBuf = string; -export const V2PluginShareSaveParams__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 V2PluginShareListResponse__PluginSharePrincipalRole = "reader" | "editor" | "owner"; +export const V2PluginShareListResponse__PluginSharePrincipalRole = Schema.Literals([ + "reader", + "editor", + "owner", +]).annotate({ identifier: "V2PluginShareListResponse__PluginSharePrincipalRole" }); export type V2PluginShareSaveParams__PluginShareDiscoverability = "LISTED" | "UNLISTED" | "PRIVATE"; export const V2PluginShareSaveParams__PluginShareDiscoverability = Schema.Literals([ "LISTED", "UNLISTED", "PRIVATE", -]); +]).annotate({ identifier: "V2PluginShareSaveParams__PluginShareDiscoverability" }); + +export type V2PluginShareSaveParams__AbsolutePathBuf = string; +export const V2PluginShareSaveParams__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.", + identifier: "V2PluginShareSaveParams__AbsolutePathBuf", +}); export type V2PluginShareSaveParams__PluginSharePrincipalType = "user" | "group" | "workspace"; export const V2PluginShareSaveParams__PluginSharePrincipalType = Schema.Literals([ "user", "group", "workspace", -]); +]).annotate({ identifier: "V2PluginShareSaveParams__PluginSharePrincipalType" }); export type V2PluginShareSaveParams__PluginShareTargetRole = "reader" | "editor"; -export const V2PluginShareSaveParams__PluginShareTargetRole = Schema.Literals(["reader", "editor"]); +export const V2PluginShareSaveParams__PluginShareTargetRole = Schema.Literals([ + "reader", + "editor", +]).annotate({ identifier: "V2PluginShareSaveParams__PluginShareTargetRole" }); + +export type V2PluginShareUpdateTargetsParams__PluginShareUpdateDiscoverability = + | "UNLISTED" + | "PRIVATE" + | "LISTED"; +export const V2PluginShareUpdateTargetsParams__PluginShareUpdateDiscoverability = Schema.Literals([ + "UNLISTED", + "PRIVATE", + "LISTED", +]).annotate({ identifier: "V2PluginShareUpdateTargetsParams__PluginShareUpdateDiscoverability" }); export type V2PluginShareUpdateTargetsParams__PluginSharePrincipalType = | "user" @@ -5858,23 +7884,13 @@ export const V2PluginShareUpdateTargetsParams__PluginSharePrincipalType = Schema "user", "group", "workspace", -]); +]).annotate({ identifier: "V2PluginShareUpdateTargetsParams__PluginSharePrincipalType" }); export type V2PluginShareUpdateTargetsParams__PluginShareTargetRole = "reader" | "editor"; export const V2PluginShareUpdateTargetsParams__PluginShareTargetRole = Schema.Literals([ "reader", "editor", -]); - -export type V2PluginShareUpdateTargetsParams__PluginShareUpdateDiscoverability = - | "UNLISTED" - | "PRIVATE" - | "LISTED"; -export const V2PluginShareUpdateTargetsParams__PluginShareUpdateDiscoverability = Schema.Literals([ - "UNLISTED", - "PRIVATE", - "LISTED", -]); +]).annotate({ identifier: "V2PluginShareUpdateTargetsParams__PluginShareTargetRole" }); export type V2PluginShareUpdateTargetsResponse__PluginShareDiscoverability = | "LISTED" @@ -5884,7 +7900,17 @@ export const V2PluginShareUpdateTargetsResponse__PluginShareDiscoverability = Sc "LISTED", "UNLISTED", "PRIVATE", -]); +]).annotate({ identifier: "V2PluginShareUpdateTargetsResponse__PluginShareDiscoverability" }); + +export type V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType = + | "user" + | "group" + | "workspace"; +export const V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType = Schema.Literals([ + "user", + "group", + "workspace", +]).annotate({ identifier: "V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType" }); export type V2PluginShareUpdateTargetsResponse__PluginSharePrincipalRole = | "reader" @@ -5894,17 +7920,28 @@ export const V2PluginShareUpdateTargetsResponse__PluginSharePrincipalRole = Sche "reader", "editor", "owner", -]); +]).annotate({ identifier: "V2PluginShareUpdateTargetsResponse__PluginSharePrincipalRole" }); -export type V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType = - | "user" - | "group" - | "workspace"; -export const V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType = Schema.Literals([ - "user", - "group", - "workspace", -]); +export type V2ProcessOutputDeltaNotification__ProcessOutputStream = "stdout" | "stderr"; +export const V2ProcessOutputDeltaNotification__ProcessOutputStream = Schema.Union( + [ + Schema.Literal("stdout").annotate({ + description: "stdout stream. PTY mode multiplexes terminal output here.", + }), + Schema.Literal("stderr").annotate({ description: "stderr stream." }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Stream label for `process/outputDelta` notifications.", + identifier: "V2ProcessOutputDeltaNotification__ProcessOutputStream", +}); + +export type V2ProjectChangedNotification__ProjectChangeType = "created" | "updated" | "deleted"; +export const V2ProjectChangedNotification__ProjectChangeType = Schema.Literals([ + "created", + "updated", + "deleted", +]).annotate({ identifier: "V2ProjectChangedNotification__ProjectChangeType" }); export type V2RawResponseCompletedNotification__TokenUsageBreakdown = { readonly cacheWriteInputTokens?: number; @@ -5916,36 +7953,39 @@ export type V2RawResponseCompletedNotification__TokenUsageBreakdown = { }; export const V2RawResponseCompletedNotification__TokenUsageBreakdown = Schema.Struct({ cacheWriteInputTokens: Schema.optionalKey( - Schema.Number.annotate({ default: 0, format: "int64" }).check(Schema.isInt()), + Schema.Number.annotate({ default: 0, format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + ), + cachedInputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + inputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + outputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + reasoningOutputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + totalTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), ), - cachedInputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - inputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - outputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - reasoningOutputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - totalTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), +}).annotate({ identifier: "V2RawResponseCompletedNotification__TokenUsageBreakdown" }); + +export type V2RawResponseCompletedNotification__ResponseUsageMetadata = { + readonly amount?: string | null; + readonly metadata?: Schema.Json; +}; +export const V2RawResponseCompletedNotification__ResponseUsageMetadata = Schema.Struct({ + amount: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + metadata: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ + description: "Usage metadata reported for one upstream response.", + identifier: "V2RawResponseCompletedNotification__ResponseUsageMetadata", }); -export type V2RawResponseItemCompletedNotification__AgentMessageInputContent = - | { readonly text: string; readonly type: "input_text" } - | { readonly encrypted_content: string; readonly type: "encrypted_content" }; -export const V2RawResponseItemCompletedNotification__AgentMessageInputContent = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("input_text").annotate({ - title: "InputTextAgentMessageInputContentType", - }), - }).annotate({ title: "InputTextAgentMessageInputContent" }), - Schema.Struct({ - encrypted_content: Schema.String, - type: Schema.Literal("encrypted_content").annotate({ - title: "EncryptedContentAgentMessageInputContentType", - }), - }).annotate({ title: "EncryptedContentAgentMessageInputContent" }), - ], - { mode: "oneOf" }, -); - export type V2RawResponseItemCompletedNotification__ImageDetail = | "auto" | "low" @@ -5956,7 +7996,7 @@ export const V2RawResponseItemCompletedNotification__ImageDetail = Schema.Litera "low", "high", "original", -]); +]).annotate({ identifier: "V2RawResponseItemCompletedNotification__ImageDetail" }); export type V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough = { readonly turn_id?: string | null; @@ -5967,58 +8007,48 @@ export const V2RawResponseItemCompletedNotification__InternalChatMessageMetadata }).annotate({ description: "Internal Responses API passthrough metadata copied into underlying chat messages.\n\nResponses API strongly types this payload. Do not modify it without first getting API approval and making the corresponding Responses API change.", + identifier: "V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough", }); -export type V2RawResponseItemCompletedNotification__LocalShellAction = { - readonly command: ReadonlyArray; - readonly env?: { readonly [x: string]: string } | null; - readonly timeout_ms?: number | null; - readonly type: "exec"; - readonly user?: string | null; - readonly working_directory?: string | null; -}; -export const V2RawResponseItemCompletedNotification__LocalShellAction = Schema.Union( +export type V2RawResponseItemCompletedNotification__MessagePhase = "commentary" | "final_answer"; +export const V2RawResponseItemCompletedNotification__MessagePhase = Schema.Union( [ - Schema.Struct({ - command: Schema.Array(Schema.String), - env: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - timeout_ms: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - type: Schema.Literal("exec").annotate({ title: "ExecLocalShellActionType" }), - user: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - working_directory: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }).annotate({ title: "ExecLocalShellAction" }), + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), ], { mode: "oneOf" }, -); - -export type V2RawResponseItemCompletedNotification__LocalShellStatus = - | "completed" - | "in_progress" - | "incomplete"; -export const V2RawResponseItemCompletedNotification__LocalShellStatus = Schema.Literals([ - "completed", - "in_progress", - "incomplete", -]); - -export type V2RawResponseItemCompletedNotification__MessagePhase = "commentary" | "final_answer"; -export const V2RawResponseItemCompletedNotification__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +).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.', + identifier: "V2RawResponseItemCompletedNotification__MessagePhase", }); +export type V2RawResponseItemCompletedNotification__AgentMessageInputContent = + | { readonly text: string; readonly type: "input_text" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2RawResponseItemCompletedNotification__AgentMessageInputContent = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextAgentMessageInputContentType", + }), + }).annotate({ title: "InputTextAgentMessageInputContent" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentAgentMessageInputContentType", + }), + }).annotate({ title: "EncryptedContentAgentMessageInputContent" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2RawResponseItemCompletedNotification__AgentMessageInputContent" }); + export type V2RawResponseItemCompletedNotification__ReasoningItemContent = | { readonly text: string; readonly type: "reasoning_text" } | { readonly text: string; readonly type: "text" }; @@ -6036,7 +8066,7 @@ export const V2RawResponseItemCompletedNotification__ReasoningItemContent = Sche }).annotate({ title: "TextReasoningItemContent" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2RawResponseItemCompletedNotification__ReasoningItemContent" }); export type V2RawResponseItemCompletedNotification__ReasoningItemReasoningSummary = { readonly text: string; @@ -6052,7 +8082,52 @@ export const V2RawResponseItemCompletedNotification__ReasoningItemReasoningSumma }).annotate({ title: "SummaryTextReasoningItemReasoningSummary" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2RawResponseItemCompletedNotification__ReasoningItemReasoningSummary" }); + +export type V2RawResponseItemCompletedNotification__LocalShellAction = { + readonly command: ReadonlyArray; + readonly env?: { readonly [x: string]: string } | null; + readonly timeout_ms?: number | null; + readonly type: "exec"; + readonly user?: string | null; + readonly working_directory?: string | null; +}; +export const V2RawResponseItemCompletedNotification__LocalShellAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.Array(Schema.String), + env: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + timeout_ms: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("exec").annotate({ title: "ExecLocalShellActionType" }), + user: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + working_directory: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "ExecLocalShellAction" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2RawResponseItemCompletedNotification__LocalShellAction" }); + +export type V2RawResponseItemCompletedNotification__LocalShellStatus = + | "completed" + | "in_progress" + | "incomplete"; +export const V2RawResponseItemCompletedNotification__LocalShellStatus = Schema.Literals([ + "completed", + "in_progress", + "incomplete", +]).annotate({ identifier: "V2RawResponseItemCompletedNotification__LocalShellStatus" }); export type V2RawResponseItemCompletedNotification__ResponsesApiWebSearchAction = | { @@ -6092,6 +8167,16 @@ export const V2RawResponseItemCompletedNotification__ResponsesApiWebSearchAction }).annotate({ title: "OtherResponsesApiWebSearchAction" }), ], { mode: "oneOf" }, +).annotate({ identifier: "V2RawResponseItemCompletedNotification__ResponsesApiWebSearchAction" }); + +export type V2RawResponseItemCompletedNotification__ReasoningEffort = string; +export const V2RawResponseItemCompletedNotification__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2RawResponseItemCompletedNotification__ReasoningEffort", + }), ); export type V2RemoteControlStatusChangedNotification__RemoteControlConnectionStatus = @@ -6100,10 +8185,14 @@ export type V2RemoteControlStatusChangedNotification__RemoteControlConnectionSta | "connected" | "errored"; export const V2RemoteControlStatusChangedNotification__RemoteControlConnectionStatus = - Schema.Literals(["disabled", "connecting", "connected", "errored"]); + Schema.Literals(["disabled", "connecting", "connected", "errored"]).annotate({ + identifier: "V2RemoteControlStatusChangedNotification__RemoteControlConnectionStatus", + }); export type V2ReviewStartParams__ReviewDelivery = "inline" | "detached"; -export const V2ReviewStartParams__ReviewDelivery = Schema.Literals(["inline", "detached"]); +export const V2ReviewStartParams__ReviewDelivery = Schema.Literals(["inline", "detached"]).annotate( + { identifier: "V2ReviewStartParams__ReviewDelivery" }, +); export type V2ReviewStartParams__ReviewTarget = | { readonly type: "uncommittedChanges" } @@ -6151,87 +8240,32 @@ 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" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ReviewStartResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); - -export type V2ReviewStartResponse__CommandExecutionStatus = - | "inProgress" - | "completed" - | "failed" - | "declined"; -export const V2ReviewStartResponse__CommandExecutionStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "declined", -]); +).annotate({ identifier: "V2ReviewStartParams__ReviewTarget" }); -export type V2ReviewStartResponse__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ReviewStartResponse__DynamicToolCallOutputContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), - ], - { mode: "oneOf" }, -); +export type V2ReviewStartResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ReviewStartResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ReviewStartResponse__NonSteerableTurnKind" }); -export type V2ReviewStartResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ReviewStartResponse__DynamicToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ReviewStartResponse__MisalignmentSteer = { readonly message: string }; +export const V2ReviewStartResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ReviewStartResponse__MisalignmentSteer" }); -export type V2ReviewStartResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; -}; -export const V2ReviewStartResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, -}); +export type V2ReviewStartResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ReviewStartResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ReviewStartResponse__ByteRange" }); export type V2ReviewStartResponse__ImageDetail = "auto" | "low" | "high" | "original"; export const V2ReviewStartResponse__ImageDetail = Schema.Literals([ @@ -6239,47 +8273,22 @@ export const V2ReviewStartResponse__ImageDetail = Schema.Literals([ "low", "high", "original", -]); - -export type V2ReviewStartResponse__LegacyAppPathString = string; -export const V2ReviewStartResponse__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2ReviewStartResponse__ImageDetail" }); -export type V2ReviewStartResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ReviewStartResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ReviewStartResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ReviewStartResponse__McpToolCallError = { readonly message: string }; -export const V2ReviewStartResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); +export const V2ReviewStartResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ReviewStartResponse__HookPromptFragment" }); -export type V2ReviewStartResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2ReviewStartResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2ReviewStartResponse__AgentMessageDelivery = "async"; +export const V2ReviewStartResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ReviewStartResponse__AgentMessageDelivery", }); -export type V2ReviewStartResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ReviewStartResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - export type V2ReviewStartResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; @@ -6288,38 +8297,74 @@ export type V2ReviewStartResponse__MemoryCitationEntry = { }; export const V2ReviewStartResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ReviewStartResponse__MemoryCitationEntry" }); export type V2ReviewStartResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ReviewStartResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export const V2ReviewStartResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ReviewStartResponse__MessagePhase", }); -export type V2ReviewStartResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ReviewStartResponse__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type V2ReviewStartResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ReviewStartResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ReviewStartResponse__AsyncUserInputQuestion" }); -export type V2ReviewStartResponse__PatchApplyStatus = +export type V2ReviewStartResponse__LegacyAppPathString = string; +export const V2ReviewStartResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ReviewStartResponse__LegacyAppPathString", +}); + +export type V2ReviewStartResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ReviewStartResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ReviewStartResponse__CommandExecutionSource" }); + +export type V2ReviewStartResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ReviewStartResponse__PatchApplyStatus = Schema.Literals([ +export const V2ReviewStartResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ReviewStartResponse__CommandExecutionStatus" }); export type V2ReviewStartResponse__PatchChangeKind = | { readonly type: "add" } @@ -6339,12 +8384,160 @@ export const V2ReviewStartResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ReviewStartResponse__PatchChangeKind" }); + +export type V2ReviewStartResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ReviewStartResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]).annotate({ identifier: "V2ReviewStartResponse__PatchApplyStatus" }); + +export type V2ReviewStartResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; +}; +export const V2ReviewStartResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ReviewStartResponse__McpToolCallAppContext" }); + +export type V2ReviewStartResponse__McpToolCallError = { readonly message: string }; +export const V2ReviewStartResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ReviewStartResponse__McpToolCallError" }); + +export type V2ReviewStartResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ReviewStartResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ReviewStartResponse__McpAppDisplayMode" }); + +export type V2ReviewStartResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ReviewStartResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ReviewStartResponse__McpToolCallResult" }); + +export type V2ReviewStartResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ReviewStartResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ReviewStartResponse__McpToolCallStatus" }); + +export type V2ReviewStartResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ReviewStartResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ReviewStartResponse__DynamicToolCallOutputContentItem" }); + +export type V2ReviewStartResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ReviewStartResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ReviewStartResponse__DynamicToolCallStatus" }); + +export type V2ReviewStartResponse__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ReviewStartResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ReviewStartResponse__CollabAgentStatus" }); export type V2ReviewStartResponse__ReasoningEffort = string; export const V2ReviewStartResponse__ReasoningEffort = Schema.String.annotate({ description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ReviewStartResponse__ReasoningEffort", + }), +); + +export type V2ReviewStartResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const V2ReviewStartResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "interrupted", +]).annotate({ identifier: "V2ReviewStartResponse__CollabAgentToolCallStatus" }); + +export type V2ReviewStartResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ReviewStartResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ReviewStartResponse__CollabAgentTool" }); export type V2ReviewStartResponse__SubAgentActivityKind = | "started" @@ -6356,44 +8549,7 @@ export const V2ReviewStartResponse__SubAgentActivityKind = Schema.Literals([ "interacted", "interrupted", "completed", -]); - -export type V2ReviewStartResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const V2ReviewStartResponse__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 V2ReviewStartResponse__TurnStatus = - | "completed" - | "interrupted" - | "failed" - | "inProgress"; -export const V2ReviewStartResponse__TurnStatus = Schema.Literals([ - "completed", - "interrupted", - "failed", - "inProgress", -]); +]).annotate({ identifier: "V2ReviewStartResponse__SubAgentActivityKind" }); export type V2ReviewStartResponse__WebSearchAction = | { @@ -6425,13 +8581,74 @@ export const V2ReviewStartResponse__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ReviewStartResponse__WebSearchAction" }); + +export type V2ReviewStartResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ReviewStartResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ReviewStartResponse__ImageGenerationFailure" }); + +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.", + identifier: "V2ReviewStartResponse__AbsolutePathBuf", +}); + +export type V2ReviewStartResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ReviewStartResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ReviewStartResponse__TurnItemsView" }); + +export type V2ReviewStartResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ReviewStartResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2ReviewStartResponse__TurnStatus" }); export type V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType = "credits" | "usage_limit"; export const V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType = Schema.Literals([ "credits", "usage_limit", -]); +]).annotate({ identifier: "V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType" }); export type V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus = | "sent" @@ -6439,30 +8656,28 @@ export type V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus = export const V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus = Schema.Literals([ "sent", "cooldown_active", -]); +]).annotate({ identifier: "V2SendAddCreditsNudgeEmailResponse__AddCreditsNudgeEmailStatus" }); export type V2ServerRequestResolvedNotification__RequestId = string | number; export const V2ServerRequestResolvedNotification__RequestId = Schema.Union([ Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -]); + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +]).annotate({ identifier: "V2ServerRequestResolvedNotification__RequestId" }); export type V2SkillsConfigWriteParams__AbsolutePathBuf = string; export const V2SkillsConfigWriteParams__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.", + identifier: "V2SkillsConfigWriteParams__AbsolutePathBuf", }); export type V2SkillsExtraRootsSetParams__AbsolutePathBuf = string; export const V2SkillsExtraRootsSetParams__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__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.", + identifier: "V2SkillsExtraRootsSetParams__AbsolutePathBuf", }); export type V2SkillsListResponse__SkillErrorInfo = { @@ -6472,15 +8687,7 @@ export type V2SkillsListResponse__SkillErrorInfo = { export const V2SkillsListResponse__SkillErrorInfo = Schema.Struct({ message: Schema.String, path: Schema.String, -}); - -export type V2SkillsListResponse__SkillScope = "user" | "repo" | "system" | "admin"; -export const V2SkillsListResponse__SkillScope = Schema.Literals([ - "user", - "repo", - "system", - "admin", -]); +}).annotate({ identifier: "V2SkillsListResponse__SkillErrorInfo" }); export type V2SkillsListResponse__SkillToolDependency = { readonly command?: string | null; @@ -6497,25 +8704,90 @@ export const V2SkillsListResponse__SkillToolDependency = Schema.Struct({ type: Schema.String, url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), value: Schema.String, -}); +}).annotate({ identifier: "V2SkillsListResponse__SkillToolDependency" }); -export type V2ThreadForkParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const V2ThreadForkParams__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ +export type V2SkillsListResponse__AbsolutePathBuf = string; +export const V2SkillsListResponse__AbsolutePathBuf = Schema.String.annotate({ 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + "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.", + identifier: "V2SkillsListResponse__AbsolutePathBuf", }); -export type V2ThreadForkParams__AskForApproval = - | "untrusted" - | "on-request" - | "never" - | { - readonly granular: { - readonly mcp_elicitations: boolean; +export type V2SkillsListResponse__SkillScope = "user" | "repo" | "system" | "admin"; +export const V2SkillsListResponse__SkillScope = Schema.Literals([ + "user", + "repo", + "system", + "admin", +]).annotate({ identifier: "V2SkillsListResponse__SkillScope" }); + +export type V2ThreadAttachmentAddResponse__ThreadAttachment = { + readonly attachmentType: string; + readonly createdAt: number; + readonly id: string; + readonly identityKey: string; + readonly payload: Schema.Json; +}; +export const V2ThreadAttachmentAddResponse__ThreadAttachment = Schema.Struct({ + attachmentType: Schema.String, + createdAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + id: Schema.String, + identityKey: Schema.String, + payload: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ + description: "An independently persisted attachment associated with a thread.", + identifier: "V2ThreadAttachmentAddResponse__ThreadAttachment", +}); + +export type V2ThreadAttachmentAddResponse__ThreadAttachmentAddOutcome = "created" | "existing"; +export const V2ThreadAttachmentAddResponse__ThreadAttachmentAddOutcome = Schema.Literals([ + "created", + "existing", +]).annotate({ + description: "Result of attempting to associate an attachment with a thread.", + identifier: "V2ThreadAttachmentAddResponse__ThreadAttachmentAddOutcome", +}); + +export type V2ThreadAttachmentListResponse__ThreadAttachment = { + readonly attachmentType: string; + readonly createdAt: number; + readonly id: string; + readonly identityKey: string; + readonly payload: Schema.Json; +}; +export const V2ThreadAttachmentListResponse__ThreadAttachment = Schema.Struct({ + attachmentType: Schema.String, + createdAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + id: Schema.String, + identityKey: Schema.String, + payload: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ + description: "An independently persisted attachment associated with a thread.", + identifier: "V2ThreadAttachmentListResponse__ThreadAttachment", +}); + +export type V2ThreadAttachmentUpdatedNotification__ThreadAttachmentOperation = + | "created" + | "deleted"; +export const V2ThreadAttachmentUpdatedNotification__ThreadAttachmentOperation = Schema.Literals([ + "created", + "deleted", +]).annotate({ + description: "The persisted attachment change represented by a notification.", + identifier: "V2ThreadAttachmentUpdatedNotification__ThreadAttachmentOperation", +}); + +export type V2ThreadForkParams__AskForApproval = + | "untrusted" + | "on-request" + | "never" + | { + readonly granular: { + readonly mcp_elicitations: boolean; readonly request_permissions?: boolean; readonly rules: boolean; readonly sandbox_approval: boolean; @@ -6536,7 +8808,18 @@ export const V2ThreadForkParams__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadForkParams__AskForApproval" }); + +export type V2ThreadForkParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const V2ThreadForkParams__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ThreadForkParams__ApprovalsReviewer", +}); export type V2ThreadForkParams__SandboxMode = | "read-only" @@ -6546,20 +8829,13 @@ export const V2ThreadForkParams__SandboxMode = Schema.Literals([ "read-only", "workspace-write", "danger-full-access", -]); +]).annotate({ identifier: "V2ThreadForkParams__SandboxMode" }); export type V2ThreadForkParams__ThreadSource = string; -export const V2ThreadForkParams__ThreadSource = Schema.String; - -export type V2ThreadForkResponse__AbsolutePathBuf = string; -export const V2ThreadForkResponse__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 const V2ThreadForkParams__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadForkParams__ThreadSource", }); -export type V2ThreadForkResponse__AgentPath = string; -export const V2ThreadForkResponse__AgentPath = Schema.String; - export type V2ThreadForkResponse__AskForApproval = | "untrusted" | "on-request" @@ -6587,72 +8863,46 @@ export const V2ThreadForkResponse__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadForkResponse__AskForApproval" }); -export type V2ThreadForkResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadForkResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +export type V2ThreadForkResponse__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const V2ThreadForkResponse__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ThreadForkResponse__ApprovalsReviewer", +}); -export type V2ThreadForkResponse__CommandExecutionStatus = - | "inProgress" - | "completed" - | "failed" - | "declined"; -export const V2ThreadForkResponse__CommandExecutionStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "declined", -]); +export type V2ThreadForkResponse__AbsolutePathBuf = string; +export const V2ThreadForkResponse__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.", + identifier: "V2ThreadForkResponse__AbsolutePathBuf", +}); -export type V2ThreadForkResponse__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadForkResponse__DynamicToolCallOutputContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), - ], - { mode: "oneOf" }, +export type V2ThreadForkResponse__LegacyAppPathString = string; +export const V2ThreadForkResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadForkResponse__LegacyAppPathString", +}); + +export type V2ThreadForkResponse__ReasoningEffort = string; +export const V2ThreadForkResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadForkResponse__ReasoningEffort", + }), ); -export type V2ThreadForkResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadForkResponse__DynamicToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ThreadForkResponse__NetworkAccess = "restricted" | "enabled"; +export const V2ThreadForkResponse__NetworkAccess = Schema.Literals([ + "restricted", + "enabled", +]).annotate({ identifier: "V2ThreadForkResponse__NetworkAccess" }); export type V2ThreadForkResponse__GitInfo = { readonly branch?: string | null; @@ -6663,64 +8913,94 @@ export const V2ThreadForkResponse__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ThreadForkResponse__GitInfo" }); -export type V2ThreadForkResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadForkResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadForkResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadForkResponse__ThreadHistoryMode" }); + +export type V2ThreadForkResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2ThreadForkResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadForkResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadForkResponse__ThreadSectionAppearance", +}); + +export type V2ThreadForkResponse__AgentPath = string; +export const V2ThreadForkResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadForkResponse__AgentPath", +}); + +export type V2ThreadForkResponse__ThreadId = string; +export const V2ThreadForkResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadForkResponse__ThreadId", +}); + +export type V2ThreadForkResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; +export const V2ThreadForkResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadForkResponse__ThreadActiveFlag" }); + +export type V2ThreadForkResponse__ThreadSource = string; +export const V2ThreadForkResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadForkResponse__ThreadSource", }); +export type V2ThreadForkResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadForkResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadForkResponse__NonSteerableTurnKind" }); + +export type V2ThreadForkResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadForkResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadForkResponse__MisalignmentSteer" }); + +export type V2ThreadForkResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadForkResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadForkResponse__ByteRange" }); + export type V2ThreadForkResponse__ImageDetail = "auto" | "low" | "high" | "original"; export const V2ThreadForkResponse__ImageDetail = Schema.Literals([ "auto", "low", "high", "original", -]); +]).annotate({ identifier: "V2ThreadForkResponse__ImageDetail" }); -export type V2ThreadForkResponse__LegacyAppPathString = string; -export const V2ThreadForkResponse__LegacyAppPathString = Schema.String; - -export type V2ThreadForkResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ThreadForkResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadForkResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadForkResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadForkResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); +export const V2ThreadForkResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadForkResponse__HookPromptFragment" }); -export type V2ThreadForkResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2ThreadForkResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2ThreadForkResponse__AgentMessageDelivery = "async"; +export const V2ThreadForkResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadForkResponse__AgentMessageDelivery", }); -export type V2ThreadForkResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadForkResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - export type V2ThreadForkResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; @@ -6729,38 +9009,69 @@ export type V2ThreadForkResponse__MemoryCitationEntry = { }; export const V2ThreadForkResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadForkResponse__MemoryCitationEntry" }); export type V2ThreadForkResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadForkResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export const V2ThreadForkResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadForkResponse__MessagePhase", }); -export type V2ThreadForkResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadForkResponse__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type V2ThreadForkResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadForkResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadForkResponse__AsyncUserInputQuestion" }); -export type V2ThreadForkResponse__PatchApplyStatus = +export type V2ThreadForkResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadForkResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadForkResponse__CommandExecutionSource" }); + +export type V2ThreadForkResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadForkResponse__PatchApplyStatus = Schema.Literals([ +export const V2ThreadForkResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadForkResponse__CommandExecutionStatus" }); export type V2ThreadForkResponse__PatchChangeKind = | { readonly type: "add" } @@ -6780,73 +9091,162 @@ export const V2ThreadForkResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2ThreadForkResponse__ReasoningEffort = string; -export const V2ThreadForkResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadForkResponse__PatchChangeKind" }); -export type V2ThreadForkResponse__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadForkResponse__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadForkResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadForkResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadForkResponse__PatchApplyStatus" }); -export type V2ThreadForkResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadForkResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2ThreadForkResponse__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 const V2ThreadForkResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadForkResponse__McpToolCallAppContext" }); -export type V2ThreadForkResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; -export const V2ThreadForkResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export type V2ThreadForkResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadForkResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadForkResponse__McpToolCallError" }); -export type V2ThreadForkResponse__ThreadId = string; -export const V2ThreadForkResponse__ThreadId = Schema.String; +export type V2ThreadForkResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadForkResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadForkResponse__McpAppDisplayMode" }); -export type V2ThreadForkResponse__ThreadSource = string; -export const V2ThreadForkResponse__ThreadSource = Schema.String; +export type V2ThreadForkResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadForkResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadForkResponse__McpToolCallResult" }); -export type V2ThreadForkResponse__TurnStatus = - | "completed" +export type V2ThreadForkResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadForkResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadForkResponse__McpToolCallStatus" }); + +export type V2ThreadForkResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadForkResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadForkResponse__DynamicToolCallOutputContentItem" }); + +export type V2ThreadForkResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadForkResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadForkResponse__DynamicToolCallStatus" }); + +export type V2ThreadForkResponse__CollabAgentStatus = + | "pendingInit" + | "running" | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadForkResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadForkResponse__CollabAgentStatus" }); + +export type V2ThreadForkResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" | "failed" - | "inProgress"; -export const V2ThreadForkResponse__TurnStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadForkResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadForkResponse__CollabAgentToolCallStatus" }); + +export type V2ThreadForkResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadForkResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadForkResponse__CollabAgentTool" }); + +export type V2ThreadForkResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadForkResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadForkResponse__SubAgentActivityKind" }); export type V2ThreadForkResponse__WebSearchAction = | { @@ -6878,7 +9278,61 @@ export const V2ThreadForkResponse__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadForkResponse__WebSearchAction" }); + +export type V2ThreadForkResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadForkResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadForkResponse__ImageGenerationFailure" }); + +export type V2ThreadForkResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadForkResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadForkResponse__TurnItemsView" }); + +export type V2ThreadForkResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadForkResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2ThreadForkResponse__TurnStatus" }); export type V2ThreadGoalGetResponse__ThreadGoalStatus = | "active" @@ -6894,7 +9348,7 @@ export const V2ThreadGoalGetResponse__ThreadGoalStatus = Schema.Literals([ "usageLimited", "budgetLimited", "complete", -]); +]).annotate({ identifier: "V2ThreadGoalGetResponse__ThreadGoalStatus" }); export type V2ThreadGoalSetParams__ThreadGoalStatus = | "active" @@ -6910,7 +9364,7 @@ export const V2ThreadGoalSetParams__ThreadGoalStatus = Schema.Literals([ "usageLimited", "budgetLimited", "complete", -]); +]).annotate({ identifier: "V2ThreadGoalSetParams__ThreadGoalStatus" }); export type V2ThreadGoalSetResponse__ThreadGoalStatus = | "active" @@ -6926,7 +9380,7 @@ export const V2ThreadGoalSetResponse__ThreadGoalStatus = Schema.Literals([ "usageLimited", "budgetLimited", "complete", -]); +]).annotate({ identifier: "V2ThreadGoalSetResponse__ThreadGoalStatus" }); export type V2ThreadGoalUpdatedNotification__ThreadGoalStatus = | "active" @@ -6942,92 +9396,207 @@ export const V2ThreadGoalUpdatedNotification__ThreadGoalStatus = Schema.Literals "usageLimited", "budgetLimited", "complete", -]); +]).annotate({ identifier: "V2ThreadGoalUpdatedNotification__ThreadGoalStatus" }); -export type V2ThreadListParams__SortDirection = "asc" | "desc"; -export const V2ThreadListParams__SortDirection = Schema.Literals(["asc", "desc"]); +export type V2ThreadItemsListParams__SortDirection = "asc" | "desc"; +export const V2ThreadItemsListParams__SortDirection = Schema.Literals(["asc", "desc"]).annotate({ + identifier: "V2ThreadItemsListParams__SortDirection", +}); -export type V2ThreadListParams__ThreadListCwdFilter = string | ReadonlyArray; -export const V2ThreadListParams__ThreadListCwdFilter = Schema.Union([ - Schema.String, - Schema.Array(Schema.String), -]); +export type V2ThreadItemsListResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadItemsListResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadItemsListResponse__ByteRange" }); -export type V2ThreadListParams__ThreadSortKey = "created_at" | "updated_at" | "recency_at"; -export const V2ThreadListParams__ThreadSortKey = Schema.Literals([ - "created_at", - "updated_at", - "recency_at", -]); +export type V2ThreadItemsListResponse__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ThreadItemsListResponse__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2ThreadItemsListResponse__ImageDetail" }); -export type V2ThreadListParams__ThreadSourceKind = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "subAgent" - | "subAgentReview" - | "subAgentCompact" - | "subAgentThreadSpawn" - | "subAgentOther" - | "unknown"; -export const V2ThreadListParams__ThreadSourceKind = Schema.Literals([ - "cli", - "vscode", - "exec", - "appServer", - "subAgent", - "subAgentReview", - "subAgentCompact", - "subAgentThreadSpawn", - "subAgentOther", - "unknown", -]); +export type V2ThreadItemsListResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; +}; +export const V2ThreadItemsListResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadItemsListResponse__HookPromptFragment" }); -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 V2ThreadItemsListResponse__AgentMessageDelivery = "async"; +export const V2ThreadItemsListResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadItemsListResponse__AgentMessageDelivery", }); -export type V2ThreadListResponse__AgentPath = string; -export const V2ThreadListResponse__AgentPath = Schema.String; +export type V2ThreadItemsListResponse__MemoryCitationEntry = { + readonly lineEnd: number; + readonly lineStart: number; + readonly note: string; + readonly path: string; +}; +export const V2ThreadItemsListResponse__MemoryCitationEntry = Schema.Struct({ + lineEnd: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + lineStart: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + note: Schema.String, + path: Schema.String, +}).annotate({ identifier: "V2ThreadItemsListResponse__MemoryCitationEntry" }); -export type V2ThreadListResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" +export type V2ThreadItemsListResponse__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadItemsListResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadItemsListResponse__MessagePhase", +}); + +export type V2ThreadItemsListResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadItemsListResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadItemsListResponse__AsyncUserInputQuestion" }); + +export type V2ThreadItemsListResponse__LegacyAppPathString = string; +export const V2ThreadItemsListResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadItemsListResponse__LegacyAppPathString", +}); + +export type V2ThreadItemsListResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadItemsListResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadItemsListResponse__CommandExecutionSource" }); + +export type V2ThreadItemsListResponse__CommandExecutionStatus = + | "inProgress" | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadListResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", + | "failed" + | "declined"; +export const V2ThreadItemsListResponse__CommandExecutionStatus = Schema.Literals([ + "inProgress", "completed", - "errored", - "shutdown", - "notFound", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadItemsListResponse__CommandExecutionStatus" }); -export type V2ThreadListResponse__CommandExecutionStatus = +export type V2ThreadItemsListResponse__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const V2ThreadItemsListResponse__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" }, +).annotate({ identifier: "V2ThreadItemsListResponse__PatchChangeKind" }); + +export type V2ThreadItemsListResponse__PatchApplyStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadListResponse__CommandExecutionStatus = Schema.Literals([ +export const V2ThreadItemsListResponse__PatchApplyStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadItemsListResponse__PatchApplyStatus" }); -export type V2ThreadListResponse__DynamicToolCallOutputContentItem = +export type V2ThreadItemsListResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; +}; +export const V2ThreadItemsListResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadItemsListResponse__McpToolCallAppContext" }); + +export type V2ThreadItemsListResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadItemsListResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadItemsListResponse__McpToolCallError" }); + +export type V2ThreadItemsListResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadItemsListResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadItemsListResponse__McpAppDisplayMode" }); + +export type V2ThreadItemsListResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadItemsListResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadItemsListResponse__McpToolCallResult" }); + +export type V2ThreadItemsListResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadItemsListResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadItemsListResponse__McpToolCallStatus" }); + +export type V2ThreadItemsListResponse__DynamicToolCallOutputContentItem = | { readonly text: string; readonly type: "inputText" } | { readonly imageUrl: string; readonly type: "inputImage" } | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadListResponse__DynamicToolCallOutputContentItem = Schema.Union( +export const V2ThreadItemsListResponse__DynamicToolCallOutputContentItem = Schema.Union( [ Schema.Struct({ text: Schema.String, @@ -7049,14 +9618,209 @@ export const V2ThreadListResponse__DynamicToolCallOutputContentItem = Schema.Uni }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadItemsListResponse__DynamicToolCallOutputContentItem" }); + +export type V2ThreadItemsListResponse__DynamicToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2ThreadItemsListResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadItemsListResponse__DynamicToolCallStatus" }); + +export type V2ThreadItemsListResponse__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadItemsListResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadItemsListResponse__CollabAgentStatus" }); + +export type V2ThreadItemsListResponse__ReasoningEffort = string; +export const V2ThreadItemsListResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadItemsListResponse__ReasoningEffort", + }), ); -export type V2ThreadListResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadListResponse__DynamicToolCallStatus = Schema.Literals([ +export type V2ThreadItemsListResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const V2ThreadItemsListResponse__CollabAgentToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadItemsListResponse__CollabAgentToolCallStatus" }); + +export type V2ThreadItemsListResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadItemsListResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadItemsListResponse__CollabAgentTool" }); + +export type V2ThreadItemsListResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadItemsListResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadItemsListResponse__SubAgentActivityKind" }); + +export type V2ThreadItemsListResponse__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 V2ThreadItemsListResponse__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: "SearchWebSearchActionType" }), + }).annotate({ title: "SearchWebSearchAction" }), + Schema.Struct({ + 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" }, +).annotate({ identifier: "V2ThreadItemsListResponse__WebSearchAction" }); + +export type V2ThreadItemsListResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadItemsListResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadItemsListResponse__ImageGenerationFailure" }); + +export type V2ThreadItemsListResponse__AbsolutePathBuf = string; +export const V2ThreadItemsListResponse__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.", + identifier: "V2ThreadItemsListResponse__AbsolutePathBuf", +}); + +export type V2ThreadListParams__ThreadListCwdFilter = string | ReadonlyArray; +export const V2ThreadListParams__ThreadListCwdFilter = Schema.Union([ + Schema.String, + Schema.Array(Schema.String), +]).annotate({ identifier: "V2ThreadListParams__ThreadListCwdFilter" }); + +export type V2ThreadListParams__SortDirection = "asc" | "desc"; +export const V2ThreadListParams__SortDirection = Schema.Literals(["asc", "desc"]).annotate({ + identifier: "V2ThreadListParams__SortDirection", +}); + +export type V2ThreadListParams__ThreadSortKey = + | "created_at" + | "updated_at" + | "recency_at" + | "section_position"; +export const V2ThreadListParams__ThreadSortKey = Schema.Literals([ + "created_at", + "updated_at", + "recency_at", + "section_position", +]).annotate({ identifier: "V2ThreadListParams__ThreadSortKey" }); + +export type V2ThreadListParams__ThreadSourceKind = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "subAgent" + | "subAgentReview" + | "subAgentCompact" + | "subAgentThreadSpawn" + | "subAgentOther" + | "unknown"; +export const V2ThreadListParams__ThreadSourceKind = Schema.Literals([ + "cli", + "vscode", + "exec", + "appServer", + "subAgent", + "subAgentReview", + "subAgentCompact", + "subAgentThreadSpawn", + "subAgentOther", + "unknown", +]).annotate({ identifier: "V2ThreadListParams__ThreadSourceKind" }); + +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.", + identifier: "V2ThreadListResponse__AbsolutePathBuf", +}); export type V2ThreadListResponse__GitInfo = { readonly branch?: string | null; @@ -7067,64 +9831,104 @@ export const V2ThreadListResponse__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ThreadListResponse__GitInfo" }); -export type V2ThreadListResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadListResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadListResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadListResponse__ThreadHistoryMode" }); + +export type V2ThreadListResponse__ReasoningEffort = string; +export const V2ThreadListResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadListResponse__ReasoningEffort", + }), +); + +export type V2ThreadListResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2ThreadListResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadListResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadListResponse__ThreadSectionAppearance", +}); + +export type V2ThreadListResponse__AgentPath = string; +export const V2ThreadListResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadListResponse__AgentPath", +}); + +export type V2ThreadListResponse__ThreadId = string; +export const V2ThreadListResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadListResponse__ThreadId", +}); + +export type V2ThreadListResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; +export const V2ThreadListResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadListResponse__ThreadActiveFlag" }); + +export type V2ThreadListResponse__ThreadSource = string; +export const V2ThreadListResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadListResponse__ThreadSource", }); +export type V2ThreadListResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadListResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadListResponse__NonSteerableTurnKind" }); + +export type V2ThreadListResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadListResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadListResponse__MisalignmentSteer" }); + +export type V2ThreadListResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadListResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadListResponse__ByteRange" }); + export type V2ThreadListResponse__ImageDetail = "auto" | "low" | "high" | "original"; export const V2ThreadListResponse__ImageDetail = Schema.Literals([ "auto", "low", "high", "original", -]); - -export type V2ThreadListResponse__LegacyAppPathString = string; -export const V2ThreadListResponse__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2ThreadListResponse__ImageDetail" }); -export type V2ThreadListResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ThreadListResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadListResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadListResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadListResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); +export const V2ThreadListResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadListResponse__HookPromptFragment" }); -export type V2ThreadListResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2ThreadListResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2ThreadListResponse__AgentMessageDelivery = "async"; +export const V2ThreadListResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadListResponse__AgentMessageDelivery", }); -export type V2ThreadListResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadListResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - export type V2ThreadListResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; @@ -7133,38 +9937,74 @@ export type V2ThreadListResponse__MemoryCitationEntry = { }; export const V2ThreadListResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadListResponse__MemoryCitationEntry" }); export type V2ThreadListResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadListResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export const V2ThreadListResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadListResponse__MessagePhase", }); -export type V2ThreadListResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadListResponse__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type V2ThreadListResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadListResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadListResponse__AsyncUserInputQuestion" }); -export type V2ThreadListResponse__PatchApplyStatus = +export type V2ThreadListResponse__LegacyAppPathString = string; +export const V2ThreadListResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadListResponse__LegacyAppPathString", +}); + +export type V2ThreadListResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadListResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadListResponse__CommandExecutionSource" }); + +export type V2ThreadListResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadListResponse__PatchApplyStatus = Schema.Literals([ +export const V2ThreadListResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadListResponse__CommandExecutionStatus" }); export type V2ThreadListResponse__PatchChangeKind = | { readonly type: "add" } @@ -7184,73 +10024,162 @@ export const V2ThreadListResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2ThreadListResponse__ReasoningEffort = string; -export const V2ThreadListResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadListResponse__PatchChangeKind" }); -export type V2ThreadListResponse__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadListResponse__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadListResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadListResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadListResponse__PatchApplyStatus" }); -export type V2ThreadListResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadListResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2ThreadListResponse__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 const V2ThreadListResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadListResponse__McpToolCallAppContext" }); -export type V2ThreadListResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; -export const V2ThreadListResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export type V2ThreadListResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadListResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadListResponse__McpToolCallError" }); -export type V2ThreadListResponse__ThreadId = string; -export const V2ThreadListResponse__ThreadId = Schema.String; +export type V2ThreadListResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadListResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadListResponse__McpAppDisplayMode" }); -export type V2ThreadListResponse__ThreadSource = string; -export const V2ThreadListResponse__ThreadSource = Schema.String; +export type V2ThreadListResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadListResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadListResponse__McpToolCallResult" }); -export type V2ThreadListResponse__TurnStatus = - | "completed" +export type V2ThreadListResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadListResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadListResponse__McpToolCallStatus" }); + +export type V2ThreadListResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadListResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadListResponse__DynamicToolCallOutputContentItem" }); + +export type V2ThreadListResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadListResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadListResponse__DynamicToolCallStatus" }); + +export type V2ThreadListResponse__CollabAgentStatus = + | "pendingInit" + | "running" | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadListResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadListResponse__CollabAgentStatus" }); + +export type V2ThreadListResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" | "failed" - | "inProgress"; -export const V2ThreadListResponse__TurnStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadListResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadListResponse__CollabAgentToolCallStatus" }); + +export type V2ThreadListResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadListResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadListResponse__CollabAgentTool" }); + +export type V2ThreadListResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadListResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadListResponse__SubAgentActivityKind" }); export type V2ThreadListResponse__WebSearchAction = | { @@ -7282,9 +10211,63 @@ export const V2ThreadListResponse__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadListResponse__WebSearchAction" }); -export type V2ThreadMetadataUpdateParams__ThreadMetadataGitInfoUpdateParams = { +export type V2ThreadListResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadListResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadListResponse__ImageGenerationFailure" }); + +export type V2ThreadListResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadListResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadListResponse__TurnItemsView" }); + +export type V2ThreadListResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadListResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2ThreadListResponse__TurnStatus" }); + +export type V2ThreadMetadataUpdateParams__ThreadMetadataGitInfoUpdateParams = { readonly branch?: string | null; readonly originUrl?: string | null; readonly sha?: string | null; @@ -7317,85 +10300,15 @@ export const V2ThreadMetadataUpdateParams__ThreadMetadataGitInfoUpdateParams = S Schema.Null, ]), ), -}); +}).annotate({ identifier: "V2ThreadMetadataUpdateParams__ThreadMetadataGitInfoUpdateParams" }); 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.", + identifier: "V2ThreadMetadataUpdateResponse__AbsolutePathBuf", }); -export type V2ThreadMetadataUpdateResponse__AgentPath = string; -export const V2ThreadMetadataUpdateResponse__AgentPath = Schema.String; - -export type V2ThreadMetadataUpdateResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadMetadataUpdateResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); - -export type V2ThreadMetadataUpdateResponse__CommandExecutionStatus = - | "inProgress" - | "completed" - | "failed" - | "declined"; -export const V2ThreadMetadataUpdateResponse__CommandExecutionStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "declined", -]); - -export type V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadMetadataUpdateResponse__DynamicToolCallStatus = - | "inProgress" - | "completed" - | "failed"; -export const V2ThreadMetadataUpdateResponse__DynamicToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - export type V2ThreadMetadataUpdateResponse__GitInfo = { readonly branch?: string | null; readonly originUrl?: string | null; @@ -7405,68 +10318,108 @@ export const V2ThreadMetadataUpdateResponse__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__GitInfo" }); -export type V2ThreadMetadataUpdateResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadMetadataUpdateResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadMetadataUpdateResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__ThreadHistoryMode" }); + +export type V2ThreadMetadataUpdateResponse__ReasoningEffort = string; +export const V2ThreadMetadataUpdateResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadMetadataUpdateResponse__ReasoningEffort", + }), +); + +export type V2ThreadMetadataUpdateResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2ThreadMetadataUpdateResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadMetadataUpdateResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadMetadataUpdateResponse__ThreadSectionAppearance", +}); + +export type V2ThreadMetadataUpdateResponse__AgentPath = string; +export const V2ThreadMetadataUpdateResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadMetadataUpdateResponse__AgentPath", +}); + +export type V2ThreadMetadataUpdateResponse__ThreadId = string; +export const V2ThreadMetadataUpdateResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadMetadataUpdateResponse__ThreadId", +}); + +export type V2ThreadMetadataUpdateResponse__ThreadActiveFlag = + | "waitingOnApproval" + | "waitingOnUserInput"; +export const V2ThreadMetadataUpdateResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__ThreadActiveFlag" }); + +export type V2ThreadMetadataUpdateResponse__ThreadSource = string; +export const V2ThreadMetadataUpdateResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadMetadataUpdateResponse__ThreadSource", }); +export type V2ThreadMetadataUpdateResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadMetadataUpdateResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__NonSteerableTurnKind" }); + +export type V2ThreadMetadataUpdateResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadMetadataUpdateResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__MisalignmentSteer" }); + +export type V2ThreadMetadataUpdateResponse__ByteRange = { + readonly end: number; + readonly start: number; +}; +export const V2ThreadMetadataUpdateResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__ByteRange" }); + export type V2ThreadMetadataUpdateResponse__ImageDetail = "auto" | "low" | "high" | "original"; export const V2ThreadMetadataUpdateResponse__ImageDetail = Schema.Literals([ "auto", "low", "high", "original", -]); - -export type V2ThreadMetadataUpdateResponse__LegacyAppPathString = string; -export const V2ThreadMetadataUpdateResponse__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__ImageDetail" }); -export type V2ThreadMetadataUpdateResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; -}; -export const V2ThreadMetadataUpdateResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadMetadataUpdateResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadMetadataUpdateResponse__McpToolCallError = Schema.Struct({ - message: Schema.String, -}); - -export type V2ThreadMetadataUpdateResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; +export type V2ThreadMetadataUpdateResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadMetadataUpdateResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); +export const V2ThreadMetadataUpdateResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__HookPromptFragment" }); -export type V2ThreadMetadataUpdateResponse__McpToolCallStatus = - | "inProgress" - | "completed" - | "failed"; -export const V2ThreadMetadataUpdateResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ThreadMetadataUpdateResponse__AgentMessageDelivery = "async"; +export const V2ThreadMetadataUpdateResponse__AgentMessageDelivery = Schema.Literal( + "async", +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__AgentMessageDelivery" }); export type V2ThreadMetadataUpdateResponse__MemoryCitationEntry = { readonly lineEnd: number; @@ -7476,41 +10429,74 @@ export type V2ThreadMetadataUpdateResponse__MemoryCitationEntry = { }; export const V2ThreadMetadataUpdateResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__MemoryCitationEntry" }); export type V2ThreadMetadataUpdateResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadMetadataUpdateResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export const V2ThreadMetadataUpdateResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadMetadataUpdateResponse__MessagePhase", }); -export type V2ThreadMetadataUpdateResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadMetadataUpdateResponse__NonSteerableTurnKind = Schema.Literals([ - "review", - "compact", -]); +export type V2ThreadMetadataUpdateResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadMetadataUpdateResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__AsyncUserInputQuestion" }); -export type V2ThreadMetadataUpdateResponse__PatchApplyStatus = +export type V2ThreadMetadataUpdateResponse__LegacyAppPathString = string; +export const V2ThreadMetadataUpdateResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadMetadataUpdateResponse__LegacyAppPathString", +}); + +export type V2ThreadMetadataUpdateResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadMetadataUpdateResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__CommandExecutionSource" }); + +export type V2ThreadMetadataUpdateResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadMetadataUpdateResponse__PatchApplyStatus = Schema.Literals([ +export const V2ThreadMetadataUpdateResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__CommandExecutionStatus" }); export type V2ThreadMetadataUpdateResponse__PatchChangeKind = | { readonly type: "add" } @@ -7530,75 +10516,168 @@ export const V2ThreadMetadataUpdateResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2ThreadMetadataUpdateResponse__ReasoningEffort = string; -export const V2ThreadMetadataUpdateResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__PatchChangeKind" }); -export type V2ThreadMetadataUpdateResponse__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadMetadataUpdateResponse__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadMetadataUpdateResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadMetadataUpdateResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__PatchApplyStatus" }); -export type V2ThreadMetadataUpdateResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadMetadataUpdateResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2ThreadMetadataUpdateResponse__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 const V2ThreadMetadataUpdateResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__McpToolCallAppContext" }); -export type V2ThreadMetadataUpdateResponse__ThreadActiveFlag = - | "waitingOnApproval" - | "waitingOnUserInput"; -export const V2ThreadMetadataUpdateResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export type V2ThreadMetadataUpdateResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadMetadataUpdateResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__McpToolCallError" }); -export type V2ThreadMetadataUpdateResponse__ThreadId = string; -export const V2ThreadMetadataUpdateResponse__ThreadId = Schema.String; +export type V2ThreadMetadataUpdateResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadMetadataUpdateResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__McpAppDisplayMode" }); -export type V2ThreadMetadataUpdateResponse__ThreadSource = string; -export const V2ThreadMetadataUpdateResponse__ThreadSource = Schema.String; +export type V2ThreadMetadataUpdateResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadMetadataUpdateResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__McpToolCallResult" }); -export type V2ThreadMetadataUpdateResponse__TurnStatus = +export type V2ThreadMetadataUpdateResponse__McpToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2ThreadMetadataUpdateResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__McpToolCallStatus" }); + +export type V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem" }); + +export type V2ThreadMetadataUpdateResponse__DynamicToolCallStatus = + | "inProgress" | "completed" + | "failed"; +export const V2ThreadMetadataUpdateResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__DynamicToolCallStatus" }); + +export type V2ThreadMetadataUpdateResponse__CollabAgentStatus = + | "pendingInit" + | "running" | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadMetadataUpdateResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__CollabAgentStatus" }); + +export type V2ThreadMetadataUpdateResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" | "failed" - | "inProgress"; -export const V2ThreadMetadataUpdateResponse__TurnStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadMetadataUpdateResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__CollabAgentToolCallStatus" }); + +export type V2ThreadMetadataUpdateResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadMetadataUpdateResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__CollabAgentTool" }); + +export type V2ThreadMetadataUpdateResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadMetadataUpdateResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__SubAgentActivityKind" }); export type V2ThreadMetadataUpdateResponse__WebSearchAction = | { @@ -7630,81 +10709,68 @@ export const V2ThreadMetadataUpdateResponse__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { 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; +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__WebSearchAction" }); -export type V2ThreadReadResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadReadResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); - -export type V2ThreadReadResponse__CommandExecutionStatus = - | "inProgress" - | "completed" - | "failed" - | "declined"; -export const V2ThreadReadResponse__CommandExecutionStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "declined", -]); - -export type V2ThreadReadResponse__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadReadResponse__DynamicToolCallOutputContentItem = Schema.Union( +export type V2ThreadMetadataUpdateResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadMetadataUpdateResponse__ImageGenerationFailure = Schema.Union( [ Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__ImageGenerationFailure" }); -export type V2ThreadReadResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadReadResponse__DynamicToolCallStatus = Schema.Literals([ - "inProgress", +export type V2ThreadMetadataUpdateResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadMetadataUpdateResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__TurnItemsView" }); + +export type V2ThreadMetadataUpdateResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadMetadataUpdateResponse__TurnStatus = Schema.Literals([ "completed", + "interrupted", "failed", -]); + "inProgress", +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__TurnStatus" }); + +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.", + identifier: "V2ThreadReadResponse__AbsolutePathBuf", +}); export type V2ThreadReadResponse__GitInfo = { readonly branch?: string | null; @@ -7715,16 +10781,81 @@ export const V2ThreadReadResponse__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadReadResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; -}; -export const V2ThreadReadResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, -}); +}).annotate({ identifier: "V2ThreadReadResponse__GitInfo" }); + +export type V2ThreadReadResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadReadResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadReadResponse__ThreadHistoryMode" }); + +export type V2ThreadReadResponse__ReasoningEffort = string; +export const V2ThreadReadResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadReadResponse__ReasoningEffort", + }), +); + +export type V2ThreadReadResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; +}; +export const V2ThreadReadResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadReadResponse__ThreadSectionAppearance", +}); + +export type V2ThreadReadResponse__AgentPath = string; +export const V2ThreadReadResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadReadResponse__AgentPath", +}); + +export type V2ThreadReadResponse__ThreadId = string; +export const V2ThreadReadResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadReadResponse__ThreadId", +}); + +export type V2ThreadReadResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; +export const V2ThreadReadResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadReadResponse__ThreadActiveFlag" }); + +export type V2ThreadReadResponse__ThreadSource = string; +export const V2ThreadReadResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadReadResponse__ThreadSource", +}); + +export type V2ThreadReadResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadReadResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadReadResponse__NonSteerableTurnKind" }); + +export type V2ThreadReadResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadReadResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadReadResponse__MisalignmentSteer" }); + +export type V2ThreadReadResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadReadResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadReadResponse__ByteRange" }); export type V2ThreadReadResponse__ImageDetail = "auto" | "low" | "high" | "original"; export const V2ThreadReadResponse__ImageDetail = Schema.Literals([ @@ -7732,47 +10863,22 @@ export const V2ThreadReadResponse__ImageDetail = Schema.Literals([ "low", "high", "original", -]); - -export type V2ThreadReadResponse__LegacyAppPathString = string; -export const V2ThreadReadResponse__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2ThreadReadResponse__ImageDetail" }); -export type V2ThreadReadResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ThreadReadResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadReadResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadReadResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadReadResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); +export const V2ThreadReadResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadReadResponse__HookPromptFragment" }); -export type V2ThreadReadResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2ThreadReadResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2ThreadReadResponse__AgentMessageDelivery = "async"; +export const V2ThreadReadResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadReadResponse__AgentMessageDelivery", }); -export type V2ThreadReadResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadReadResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - export type V2ThreadReadResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; @@ -7781,38 +10887,74 @@ export type V2ThreadReadResponse__MemoryCitationEntry = { }; export const V2ThreadReadResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadReadResponse__MemoryCitationEntry" }); export type V2ThreadReadResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadReadResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export const V2ThreadReadResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadReadResponse__MessagePhase", }); -export type V2ThreadReadResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadReadResponse__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type V2ThreadReadResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadReadResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadReadResponse__AsyncUserInputQuestion" }); -export type V2ThreadReadResponse__PatchApplyStatus = +export type V2ThreadReadResponse__LegacyAppPathString = string; +export const V2ThreadReadResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadReadResponse__LegacyAppPathString", +}); + +export type V2ThreadReadResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadReadResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadReadResponse__CommandExecutionSource" }); + +export type V2ThreadReadResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadReadResponse__PatchApplyStatus = Schema.Literals([ +export const V2ThreadReadResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadReadResponse__CommandExecutionStatus" }); export type V2ThreadReadResponse__PatchChangeKind = | { readonly type: "add" } @@ -7832,73 +10974,162 @@ export const V2ThreadReadResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2ThreadReadResponse__ReasoningEffort = string; -export const V2ThreadReadResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadReadResponse__PatchChangeKind" }); -export type V2ThreadReadResponse__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadReadResponse__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadReadResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadReadResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadReadResponse__PatchApplyStatus" }); -export type V2ThreadReadResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadReadResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2ThreadReadResponse__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 const V2ThreadReadResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadReadResponse__McpToolCallAppContext" }); -export type V2ThreadReadResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; -export const V2ThreadReadResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export type V2ThreadReadResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadReadResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadReadResponse__McpToolCallError" }); -export type V2ThreadReadResponse__ThreadId = string; -export const V2ThreadReadResponse__ThreadId = Schema.String; +export type V2ThreadReadResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadReadResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadReadResponse__McpAppDisplayMode" }); -export type V2ThreadReadResponse__ThreadSource = string; -export const V2ThreadReadResponse__ThreadSource = Schema.String; +export type V2ThreadReadResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadReadResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadReadResponse__McpToolCallResult" }); -export type V2ThreadReadResponse__TurnStatus = - | "completed" +export type V2ThreadReadResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadReadResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadReadResponse__McpToolCallStatus" }); + +export type V2ThreadReadResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadReadResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadReadResponse__DynamicToolCallOutputContentItem" }); + +export type V2ThreadReadResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadReadResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadReadResponse__DynamicToolCallStatus" }); + +export type V2ThreadReadResponse__CollabAgentStatus = + | "pendingInit" + | "running" | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadReadResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadReadResponse__CollabAgentStatus" }); + +export type V2ThreadReadResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" | "failed" - | "inProgress"; -export const V2ThreadReadResponse__TurnStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadReadResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadReadResponse__CollabAgentToolCallStatus" }); + +export type V2ThreadReadResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadReadResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadReadResponse__CollabAgentTool" }); + +export type V2ThreadReadResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadReadResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadReadResponse__SubAgentActivityKind" }); export type V2ThreadReadResponse__WebSearchAction = | { @@ -7930,7 +11161,165 @@ export const V2ThreadReadResponse__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadReadResponse__WebSearchAction" }); + +export type V2ThreadReadResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadReadResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadReadResponse__ImageGenerationFailure" }); + +export type V2ThreadReadResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadReadResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadReadResponse__TurnItemsView" }); + +export type V2ThreadReadResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadReadResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2ThreadReadResponse__TurnStatus" }); + +export type V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeTranscriptRole = + | "user" + | "assistant"; +export const V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeTranscriptRole = + Schema.Literals(["user", "assistant"]).annotate({ + identifier: "V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeTranscriptRole", + }); + +export type V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeBemItemPresentation = + | { readonly type: "wholeItem" } + | { readonly type: "inlineMarkdown" } + | { readonly index: number; readonly type: "inlineVisualization" }; +export const V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeBemItemPresentation = + Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("wholeItem").annotate({ + title: "WholeItemThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "WholeItemThreadRealtimeBemItemPresentation" }), + Schema.Struct({ + type: Schema.Literal("inlineMarkdown").annotate({ + title: "InlineMarkdownThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "InlineMarkdownThreadRealtimeBemItemPresentation" }), + Schema.Struct({ + index: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + type: Schema.Literal("inlineVisualization").annotate({ + title: "InlineVisualizationThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "InlineVisualizationThreadRealtimeBemItemPresentation" }), + ], + { mode: "oneOf" }, + ).annotate({ + description: "EXPERIMENTAL - how an existing agent item appears in a realtime conversation.", + identifier: "V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeBemItemPresentation", + }); + +export type V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeSessionOutcome = + | "ended" + | "failed"; +export const V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeSessionOutcome = + Schema.Literals(["ended", "failed"]).annotate({ + identifier: "V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeSessionOutcome", + }); + +export type V2ThreadRealtimeItemStartedNotification__ThreadRealtimeTranscriptRole = + | "user" + | "assistant"; +export const V2ThreadRealtimeItemStartedNotification__ThreadRealtimeTranscriptRole = + Schema.Literals(["user", "assistant"]).annotate({ + identifier: "V2ThreadRealtimeItemStartedNotification__ThreadRealtimeTranscriptRole", + }); + +export type V2ThreadRealtimeItemStartedNotification__ThreadRealtimeBemItemPresentation = + | { readonly type: "wholeItem" } + | { readonly type: "inlineMarkdown" } + | { readonly index: number; readonly type: "inlineVisualization" }; +export const V2ThreadRealtimeItemStartedNotification__ThreadRealtimeBemItemPresentation = + Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("wholeItem").annotate({ + title: "WholeItemThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "WholeItemThreadRealtimeBemItemPresentation" }), + Schema.Struct({ + type: Schema.Literal("inlineMarkdown").annotate({ + title: "InlineMarkdownThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "InlineMarkdownThreadRealtimeBemItemPresentation" }), + Schema.Struct({ + index: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + type: Schema.Literal("inlineVisualization").annotate({ + title: "InlineVisualizationThreadRealtimeBemItemPresentationType", + }), + }).annotate({ title: "InlineVisualizationThreadRealtimeBemItemPresentation" }), + ], + { mode: "oneOf" }, + ).annotate({ + description: "EXPERIMENTAL - how an existing agent item appears in a realtime conversation.", + identifier: "V2ThreadRealtimeItemStartedNotification__ThreadRealtimeBemItemPresentation", + }); + +export type V2ThreadRealtimeItemStartedNotification__ThreadRealtimeSessionOutcome = + | "ended" + | "failed"; +export const V2ThreadRealtimeItemStartedNotification__ThreadRealtimeSessionOutcome = + Schema.Literals(["ended", "failed"]).annotate({ + identifier: "V2ThreadRealtimeItemStartedNotification__ThreadRealtimeSessionOutcome", + }); export type V2ThreadRealtimeOutputAudioDeltaNotification__ThreadRealtimeAudioChunk = { readonly data: string; @@ -7944,59 +11333,43 @@ export const V2ThreadRealtimeOutputAudioDeltaNotification__ThreadRealtimeAudioCh data: Schema.String, itemId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), numChannels: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), sampleRate: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), samplesPerChannel: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), }, -).annotate({ description: "EXPERIMENTAL - thread realtime audio chunk." }); +).annotate({ + description: "EXPERIMENTAL - thread realtime audio chunk.", + identifier: "V2ThreadRealtimeOutputAudioDeltaNotification__ThreadRealtimeAudioChunk", +}); export type V2ThreadRealtimeStartedNotification__RealtimeConversationVersion = "v1" | "v2" | "v3"; export const V2ThreadRealtimeStartedNotification__RealtimeConversationVersion = Schema.Literals([ "v1", "v2", "v3", -]); - -export type V2ThreadResumeParams__AgentMessageInputContent = - | { readonly text: string; readonly type: "input_text" } - | { readonly encrypted_content: string; readonly type: "encrypted_content" }; -export const V2ThreadResumeParams__AgentMessageInputContent = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("input_text").annotate({ - title: "InputTextAgentMessageInputContentType", - }), - }).annotate({ title: "InputTextAgentMessageInputContent" }), - Schema.Struct({ - encrypted_content: Schema.String, - type: Schema.Literal("encrypted_content").annotate({ - title: "EncryptedContentAgentMessageInputContentType", - }), - }).annotate({ title: "EncryptedContentAgentMessageInputContent" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadResumeParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const V2ThreadResumeParams__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); +]).annotate({ identifier: "V2ThreadRealtimeStartedNotification__RealtimeConversationVersion" }); export type V2ThreadResumeParams__AskForApproval = | "untrusted" @@ -8025,6 +11398,47 @@ export const V2ThreadResumeParams__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeParams__AskForApproval" }); + +export type V2ThreadResumeParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const V2ThreadResumeParams__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ThreadResumeParams__ApprovalsReviewer", +}); + +export type V2ThreadResumeParams__Personality = "none" | "friendly" | "pragmatic"; +export const V2ThreadResumeParams__Personality = Schema.Literals([ + "none", + "friendly", + "pragmatic", +]).annotate({ + description: "Deprecated: `friendly` and `pragmatic` no longer select a style.", + identifier: "V2ThreadResumeParams__Personality", +}); + +export type V2ThreadResumeParams__SandboxMode = + | "read-only" + | "workspace-write" + | "danger-full-access"; +export const V2ThreadResumeParams__SandboxMode = Schema.Literals([ + "read-only", + "workspace-write", + "danger-full-access", +]).annotate({ identifier: "V2ThreadResumeParams__SandboxMode" }); + +export type V2ThreadResumeParams__ReasoningEffort = string; +export const V2ThreadResumeParams__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadResumeParams__ReasoningEffort", + }), ); export type V2ThreadResumeParams__ImageDetail = "auto" | "low" | "high" | "original"; @@ -8033,7 +11447,7 @@ export const V2ThreadResumeParams__ImageDetail = Schema.Literals([ "low", "high", "original", -]); +]).annotate({ identifier: "V2ThreadResumeParams__ImageDetail" }); export type V2ThreadResumeParams__InternalChatMessageMetadataPassthrough = { readonly turn_id?: string | null; @@ -8043,57 +11457,47 @@ export const V2ThreadResumeParams__InternalChatMessageMetadataPassthrough = Sche }).annotate({ description: "Internal Responses API passthrough metadata copied into underlying chat messages.\n\nResponses API strongly types this payload. Do not modify it without first getting API approval and making the corresponding Responses API change.", + identifier: "V2ThreadResumeParams__InternalChatMessageMetadataPassthrough", }); -export type V2ThreadResumeParams__LocalShellAction = { - readonly command: ReadonlyArray; - readonly env?: { readonly [x: string]: string } | null; - readonly timeout_ms?: number | null; - readonly type: "exec"; - readonly user?: string | null; - readonly working_directory?: string | null; -}; -export const V2ThreadResumeParams__LocalShellAction = Schema.Union( +export type V2ThreadResumeParams__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadResumeParams__MessagePhase = Schema.Union( [ - Schema.Struct({ - command: Schema.Array(Schema.String), - env: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - timeout_ms: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - type: Schema.Literal("exec").annotate({ title: "ExecLocalShellActionType" }), - user: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - working_directory: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }).annotate({ title: "ExecLocalShellAction" }), + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), ], { mode: "oneOf" }, -); - -export type V2ThreadResumeParams__LocalShellStatus = "completed" | "in_progress" | "incomplete"; -export const V2ThreadResumeParams__LocalShellStatus = Schema.Literals([ - "completed", - "in_progress", - "incomplete", -]); - -export type V2ThreadResumeParams__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadResumeParams__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +).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.', + identifier: "V2ThreadResumeParams__MessagePhase", }); -export type V2ThreadResumeParams__Personality = "none" | "friendly" | "pragmatic"; -export const V2ThreadResumeParams__Personality = Schema.Literals(["none", "friendly", "pragmatic"]); +export type V2ThreadResumeParams__AgentMessageInputContent = + | { readonly text: string; readonly type: "input_text" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadResumeParams__AgentMessageInputContent = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextAgentMessageInputContentType", + }), + }).annotate({ title: "InputTextAgentMessageInputContent" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentAgentMessageInputContentType", + }), + }).annotate({ title: "EncryptedContentAgentMessageInputContent" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeParams__AgentMessageInputContent" }); export type V2ThreadResumeParams__ReasoningItemContent = | { readonly text: string; readonly type: "reasoning_text" } @@ -8112,7 +11516,7 @@ export const V2ThreadResumeParams__ReasoningItemContent = Schema.Union( }).annotate({ title: "TextReasoningItemContent" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadResumeParams__ReasoningItemContent" }); export type V2ThreadResumeParams__ReasoningItemReasoningSummary = { readonly text: string; @@ -8128,31 +11532,73 @@ export const V2ThreadResumeParams__ReasoningItemReasoningSummary = Schema.Union( }).annotate({ title: "SummaryTextReasoningItemReasoningSummary" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadResumeParams__ReasoningItemReasoningSummary" }); -export type V2ThreadResumeParams__ResponsesApiWebSearchAction = - | { - readonly queries?: ReadonlyArray | null; - readonly query?: string | null; - readonly type: "search"; - } - | { readonly type: "open_page"; readonly url?: string | null } - | { - readonly pattern?: string | null; - readonly type: "find_in_page"; - readonly url?: string | null; - } - | { readonly type: "other" }; -export const V2ThreadResumeParams__ResponsesApiWebSearchAction = Schema.Union( +export type V2ThreadResumeParams__LocalShellAction = { + readonly command: ReadonlyArray; + readonly env?: { readonly [x: string]: string } | null; + readonly timeout_ms?: number | null; + readonly type: "exec"; + readonly user?: string | null; + readonly working_directory?: string | null; +}; +export const V2ThreadResumeParams__LocalShellAction = 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: "SearchResponsesApiWebSearchActionType" }), - }).annotate({ title: "SearchResponsesApiWebSearchAction" }), - Schema.Struct({ - type: Schema.Literal("open_page").annotate({ - title: "OpenPageResponsesApiWebSearchActionType", + command: Schema.Array(Schema.String), + env: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + timeout_ms: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("exec").annotate({ title: "ExecLocalShellActionType" }), + user: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + working_directory: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "ExecLocalShellAction" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeParams__LocalShellAction" }); + +export type V2ThreadResumeParams__LocalShellStatus = "completed" | "in_progress" | "incomplete"; +export const V2ThreadResumeParams__LocalShellStatus = Schema.Literals([ + "completed", + "in_progress", + "incomplete", +]).annotate({ identifier: "V2ThreadResumeParams__LocalShellStatus" }); + +export type V2ThreadResumeParams__ResponsesApiWebSearchAction = + | { + readonly queries?: ReadonlyArray | null; + readonly query?: string | null; + readonly type: "search"; + } + | { readonly type: "open_page"; readonly url?: string | null } + | { + readonly pattern?: string | null; + readonly type: "find_in_page"; + readonly url?: string | null; + } + | { readonly type: "other" }; +export const V2ThreadResumeParams__ResponsesApiWebSearchAction = 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: "SearchResponsesApiWebSearchActionType" }), + }).annotate({ title: "SearchResponsesApiWebSearchAction" }), + Schema.Struct({ + type: Schema.Literal("open_page").annotate({ + title: "OpenPageResponsesApiWebSearchActionType", }), url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "OpenPageResponsesApiWebSearchAction" }), @@ -8168,37 +11614,30 @@ export const V2ThreadResumeParams__ResponsesApiWebSearchAction = Schema.Union( }).annotate({ title: "OtherResponsesApiWebSearchAction" }), ], { mode: "oneOf" }, -); - -export type V2ThreadResumeParams__SandboxMode = - | "read-only" - | "workspace-write" - | "danger-full-access"; -export const V2ThreadResumeParams__SandboxMode = Schema.Literals([ - "read-only", - "workspace-write", - "danger-full-access", -]); - -export type V2ThreadResumeParams__SortDirection = "asc" | "desc"; -export const V2ThreadResumeParams__SortDirection = Schema.Literals(["asc", "desc"]); +).annotate({ identifier: "V2ThreadResumeParams__ResponsesApiWebSearchAction" }); export type V2ThreadResumeParams__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadResumeParams__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export const V2ThreadResumeParams__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeParams__TurnItemsView" }); -export type V2ThreadResumeResponse__AbsolutePathBuf = string; -export const V2ThreadResumeResponse__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 V2ThreadResumeParams__SortDirection = "asc" | "desc"; +export const V2ThreadResumeParams__SortDirection = Schema.Literals(["asc", "desc"]).annotate({ + identifier: "V2ThreadResumeParams__SortDirection", }); -export type V2ThreadResumeResponse__AgentPath = string; -export const V2ThreadResumeResponse__AgentPath = Schema.String; - export type V2ThreadResumeResponse__AskForApproval = | "untrusted" | "on-request" @@ -8226,72 +11665,55 @@ export const V2ThreadResumeResponse__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadResumeResponse__AskForApproval" }); -export type V2ThreadResumeResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadResumeResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +export type V2ThreadResumeResponse__ApprovalsReviewer = + | "user" + | "auto_review" + | "guardian_subagent"; +export const V2ThreadResumeResponse__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ThreadResumeResponse__ApprovalsReviewer", +}); -export type V2ThreadResumeResponse__CommandExecutionStatus = - | "inProgress" - | "completed" - | "failed" - | "declined"; -export const V2ThreadResumeResponse__CommandExecutionStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "declined", -]); +export type V2ThreadResumeResponse__ModeKind = "plan" | "default"; +export const V2ThreadResumeResponse__ModeKind = Schema.Literals(["plan", "default"]).annotate({ + description: "Initial collaboration mode to use when the TUI starts.", + identifier: "V2ThreadResumeResponse__ModeKind", +}); -export type V2ThreadResumeResponse__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadResumeResponse__DynamicToolCallOutputContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), - ], - { mode: "oneOf" }, +export type V2ThreadResumeResponse__ReasoningEffort = string; +export const V2ThreadResumeResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadResumeResponse__ReasoningEffort", + }), ); -export type V2ThreadResumeResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadResumeResponse__DynamicToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ThreadResumeResponse__AbsolutePathBuf = string; +export const V2ThreadResumeResponse__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.", + identifier: "V2ThreadResumeResponse__AbsolutePathBuf", +}); + +export type V2ThreadResumeResponse__LegacyAppPathString = string; +export const V2ThreadResumeResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadResumeResponse__LegacyAppPathString", +}); + +export type V2ThreadResumeResponse__NetworkAccess = "restricted" | "enabled"; +export const V2ThreadResumeResponse__NetworkAccess = Schema.Literals([ + "restricted", + "enabled", +]).annotate({ identifier: "V2ThreadResumeResponse__NetworkAccess" }); export type V2ThreadResumeResponse__GitInfo = { readonly branch?: string | null; @@ -8302,64 +11724,94 @@ export const V2ThreadResumeResponse__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ThreadResumeResponse__GitInfo" }); -export type V2ThreadResumeResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadResumeResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadResumeResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadResumeResponse__ThreadHistoryMode" }); + +export type V2ThreadResumeResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2ThreadResumeResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadResumeResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadResumeResponse__ThreadSectionAppearance", +}); + +export type V2ThreadResumeResponse__AgentPath = string; +export const V2ThreadResumeResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadResumeResponse__AgentPath", +}); + +export type V2ThreadResumeResponse__ThreadId = string; +export const V2ThreadResumeResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadResumeResponse__ThreadId", +}); + +export type V2ThreadResumeResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; +export const V2ThreadResumeResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadResumeResponse__ThreadActiveFlag" }); + +export type V2ThreadResumeResponse__ThreadSource = string; +export const V2ThreadResumeResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadResumeResponse__ThreadSource", }); +export type V2ThreadResumeResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadResumeResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadResumeResponse__NonSteerableTurnKind" }); + +export type V2ThreadResumeResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadResumeResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadResumeResponse__MisalignmentSteer" }); + +export type V2ThreadResumeResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadResumeResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadResumeResponse__ByteRange" }); + export type V2ThreadResumeResponse__ImageDetail = "auto" | "low" | "high" | "original"; export const V2ThreadResumeResponse__ImageDetail = Schema.Literals([ "auto", "low", "high", "original", -]); - -export type V2ThreadResumeResponse__LegacyAppPathString = string; -export const V2ThreadResumeResponse__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2ThreadResumeResponse__ImageDetail" }); -export type V2ThreadResumeResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ThreadResumeResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadResumeResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadResumeResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadResumeResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); +export const V2ThreadResumeResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadResumeResponse__HookPromptFragment" }); -export type V2ThreadResumeResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2ThreadResumeResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2ThreadResumeResponse__AgentMessageDelivery = "async"; +export const V2ThreadResumeResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadResumeResponse__AgentMessageDelivery", }); -export type V2ThreadResumeResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadResumeResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - export type V2ThreadResumeResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; @@ -8368,38 +11820,69 @@ export type V2ThreadResumeResponse__MemoryCitationEntry = { }; export const V2ThreadResumeResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadResumeResponse__MemoryCitationEntry" }); export type V2ThreadResumeResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadResumeResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export const V2ThreadResumeResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadResumeResponse__MessagePhase", }); -export type V2ThreadResumeResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadResumeResponse__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type V2ThreadResumeResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadResumeResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadResumeResponse__AsyncUserInputQuestion" }); -export type V2ThreadResumeResponse__PatchApplyStatus = +export type V2ThreadResumeResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadResumeResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadResumeResponse__CommandExecutionSource" }); + +export type V2ThreadResumeResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadResumeResponse__PatchApplyStatus = Schema.Literals([ +export const V2ThreadResumeResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadResumeResponse__CommandExecutionStatus" }); export type V2ThreadResumeResponse__PatchChangeKind = | { readonly type: "add" } @@ -8419,150 +11902,69 @@ export const V2ThreadResumeResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2ThreadResumeResponse__ReasoningEffort = string; -export const V2ThreadResumeResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); - -export type V2ThreadResumeResponse__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadResumeResponse__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", - "completed", -]); - -export type V2ThreadResumeResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const V2ThreadResumeResponse__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 V2ThreadResumeResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; -export const V2ThreadResumeResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); - -export type V2ThreadResumeResponse__ThreadId = string; -export const V2ThreadResumeResponse__ThreadId = Schema.String; - -export type V2ThreadResumeResponse__ThreadSource = string; -export const V2ThreadResumeResponse__ThreadSource = Schema.String; +).annotate({ identifier: "V2ThreadResumeResponse__PatchChangeKind" }); -export type V2ThreadResumeResponse__TurnStatus = +export type V2ThreadResumeResponse__PatchApplyStatus = + | "inProgress" | "completed" - | "interrupted" | "failed" - | "inProgress"; -export const V2ThreadResumeResponse__TurnStatus = Schema.Literals([ + | "declined"; +export const V2ThreadResumeResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "declined", +]).annotate({ identifier: "V2ThreadResumeResponse__PatchApplyStatus" }); -export type V2ThreadResumeResponse__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 V2ThreadResumeResponse__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: "SearchWebSearchActionType" }), - }).annotate({ title: "SearchWebSearchAction" }), - Schema.Struct({ - 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 V2ThreadResumeResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; +}; +export const V2ThreadResumeResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadResumeResponse__McpToolCallAppContext" }); -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 V2ThreadResumeResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadResumeResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadResumeResponse__McpToolCallError" }); -export type V2ThreadRollbackResponse__AgentPath = string; -export const V2ThreadRollbackResponse__AgentPath = Schema.String; +export type V2ThreadResumeResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadResumeResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadResumeResponse__McpAppDisplayMode" }); -export type V2ThreadRollbackResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadRollbackResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +export type V2ThreadResumeResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadResumeResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadResumeResponse__McpToolCallResult" }); -export type V2ThreadRollbackResponse__CommandExecutionStatus = - | "inProgress" - | "completed" - | "failed" - | "declined"; -export const V2ThreadRollbackResponse__CommandExecutionStatus = Schema.Literals([ +export type V2ThreadResumeResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadResumeResponse__McpToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", - "declined", -]); +]).annotate({ identifier: "V2ThreadResumeResponse__McpToolCallStatus" }); -export type V2ThreadRollbackResponse__DynamicToolCallOutputContentItem = +export type V2ThreadResumeResponse__DynamicToolCallOutputContentItem = | { readonly text: string; readonly type: "inputText" } | { readonly imageUrl: string; readonly type: "inputImage" } | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadRollbackResponse__DynamicToolCallOutputContentItem = Schema.Union( +export const V2ThreadResumeResponse__DynamicToolCallOutputContentItem = Schema.Union( [ Schema.Struct({ text: Schema.String, @@ -8584,213 +11986,80 @@ export const V2ThreadRollbackResponse__DynamicToolCallOutputContentItem = Schema }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadResumeResponse__DynamicToolCallOutputContentItem" }); -export type V2ThreadRollbackResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadRollbackResponse__DynamicToolCallStatus = Schema.Literals([ +export type V2ThreadResumeResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadResumeResponse__DynamicToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); - -export type V2ThreadRollbackResponse__GitInfo = { - readonly branch?: string | null; - readonly originUrl?: string | null; - readonly sha?: string | null; -}; -export const V2ThreadRollbackResponse__GitInfo = Schema.Struct({ - branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadRollbackResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; -}; -export const V2ThreadRollbackResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, -}); - -export type V2ThreadRollbackResponse__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2ThreadRollbackResponse__ImageDetail = Schema.Literals([ - "auto", - "low", - "high", - "original", -]); - -export type V2ThreadRollbackResponse__LegacyAppPathString = string; -export const V2ThreadRollbackResponse__LegacyAppPathString = Schema.String; - -export type V2ThreadRollbackResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; -}; -export const V2ThreadRollbackResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadRollbackResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadRollbackResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); - -export type V2ThreadRollbackResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2ThreadRollbackResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); +]).annotate({ identifier: "V2ThreadResumeResponse__DynamicToolCallStatus" }); -export type V2ThreadRollbackResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadRollbackResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", +export type V2ThreadResumeResponse__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadResumeResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", "completed", - "failed", -]); - -export type V2ThreadRollbackResponse__MemoryCitationEntry = { - readonly lineEnd: number; - readonly lineStart: number; - readonly note: string; - readonly path: string; -}; -export const V2ThreadRollbackResponse__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 V2ThreadRollbackResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadRollbackResponse__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 V2ThreadRollbackResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadRollbackResponse__NonSteerableTurnKind = Schema.Literals([ - "review", - "compact", -]); + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadResumeResponse__CollabAgentStatus" }); -export type V2ThreadRollbackResponse__PatchApplyStatus = +export type V2ThreadResumeResponse__CollabAgentToolCallStatus = | "inProgress" | "completed" | "failed" - | "declined"; -export const V2ThreadRollbackResponse__PatchApplyStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadResumeResponse__CollabAgentToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", - "declined", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadResumeResponse__CollabAgentToolCallStatus" }); -export type V2ThreadRollbackResponse__PatchChangeKind = - | { readonly type: "add" } - | { readonly type: "delete" } - | { readonly move_path?: string | null; readonly type: "update" }; -export const V2ThreadRollbackResponse__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 V2ThreadRollbackResponse__ReasoningEffort = string; -export const V2ThreadRollbackResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +export type V2ThreadResumeResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadResumeResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadResumeResponse__CollabAgentTool" }); -export type V2ThreadRollbackResponse__SubAgentActivityKind = +export type V2ThreadResumeResponse__SubAgentActivityKind = | "started" | "interacted" | "interrupted" | "completed"; -export const V2ThreadRollbackResponse__SubAgentActivityKind = Schema.Literals([ +export const V2ThreadResumeResponse__SubAgentActivityKind = Schema.Literals([ "started", "interacted", "interrupted", "completed", -]); - -export type V2ThreadRollbackResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const V2ThreadRollbackResponse__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 V2ThreadRollbackResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; -export const V2ThreadRollbackResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); - -export type V2ThreadRollbackResponse__ThreadId = string; -export const V2ThreadRollbackResponse__ThreadId = Schema.String; - -export type V2ThreadRollbackResponse__ThreadSource = string; -export const V2ThreadRollbackResponse__ThreadSource = Schema.String; - -export type V2ThreadRollbackResponse__TurnStatus = - | "completed" - | "interrupted" - | "failed" - | "inProgress"; -export const V2ThreadRollbackResponse__TurnStatus = Schema.Literals([ - "completed", - "interrupted", - "failed", - "inProgress", -]); +]).annotate({ identifier: "V2ThreadResumeResponse__SubAgentActivityKind" }); -export type V2ThreadRollbackResponse__WebSearchAction = +export type V2ThreadResumeResponse__WebSearchAction = | { readonly queries?: ReadonlyArray | null; readonly query?: string | null; @@ -8799,7 +12068,7 @@ export type V2ThreadRollbackResponse__WebSearchAction = | { readonly type: "openPage"; readonly url?: string | null } | { readonly pattern?: string | null; readonly type: "findInPage"; readonly url?: string | null } | { readonly type: "other" }; -export const V2ThreadRollbackResponse__WebSearchAction = Schema.Union( +export const V2ThreadResumeResponse__WebSearchAction = Schema.Union( [ Schema.Struct({ queries: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), @@ -8820,305 +12089,258 @@ export const V2ThreadRollbackResponse__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); - -export type V2ThreadSettingsUpdatedNotification__AbsolutePathBuf = string; -export const V2ThreadSettingsUpdatedNotification__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.", -}); +).annotate({ identifier: "V2ThreadResumeResponse__WebSearchAction" }); -export type V2ThreadSettingsUpdatedNotification__ActivePermissionProfile = { - readonly extends?: string | null; - readonly id: string; +export type V2ThreadResumeResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; }; -export const V2ThreadSettingsUpdatedNotification__ActivePermissionProfile = Schema.Struct({ - extends: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", - }), - Schema.Null, - ]), - ), - id: Schema.String.annotate({ - description: - "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", - }), -}); - -export type V2ThreadSettingsUpdatedNotification__ApprovalsReviewer = - | "user" - | "auto_review" - | "guardian_subagent"; -export const V2ThreadSettingsUpdatedNotification__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); - -export type V2ThreadSettingsUpdatedNotification__AskForApproval = - | "untrusted" - | "on-request" - | "never" - | { - readonly granular: { - readonly mcp_elicitations: boolean; - readonly request_permissions?: boolean; - readonly rules: boolean; - readonly sandbox_approval: boolean; - readonly skill_approval?: boolean; - }; - }; -export const V2ThreadSettingsUpdatedNotification__AskForApproval = Schema.Union( +export const V2ThreadResumeResponse__ImageGenerationFailure = Schema.Union( [ - Schema.Literals(["untrusted", "on-request", "never"]), Schema.Struct({ - granular: Schema.Struct({ - mcp_elicitations: Schema.Boolean, - request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - rules: Schema.Boolean, - sandbox_approval: Schema.Boolean, - skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - }).annotate({ title: "GranularAskForApproval" }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), ], { mode: "oneOf" }, -); - -export type V2ThreadSettingsUpdatedNotification__ModeKind = "plan" | "default"; -export const V2ThreadSettingsUpdatedNotification__ModeKind = Schema.Literals([ - "plan", - "default", -]).annotate({ description: "Initial collaboration mode to use when the TUI starts." }); - -export type V2ThreadSettingsUpdatedNotification__Personality = "none" | "friendly" | "pragmatic"; -export const V2ThreadSettingsUpdatedNotification__Personality = Schema.Literals([ - "none", - "friendly", - "pragmatic", -]); - -export type V2ThreadSettingsUpdatedNotification__ReasoningEffort = string; -export const V2ThreadSettingsUpdatedNotification__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadResumeResponse__ImageGenerationFailure" }); -export type V2ThreadSettingsUpdatedNotification__ReasoningSummary = - | "auto" - | "concise" - | "detailed" - | "none"; -export const V2ThreadSettingsUpdatedNotification__ReasoningSummary = Schema.Union( +export type V2ThreadResumeResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadResumeResponse__TurnItemsView = Schema.Union( [ - Schema.Literals(["auto", "concise", "detailed"]), - Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), ], { mode: "oneOf" }, -).annotate({ - description: - "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 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; - -export type V2ThreadStartedNotification__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadStartedNotification__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +).annotate({ identifier: "V2ThreadResumeResponse__TurnItemsView" }); -export type V2ThreadStartedNotification__CommandExecutionStatus = - | "inProgress" +export type V2ThreadResumeResponse__TurnStatus = | "completed" + | "interrupted" | "failed" - | "declined"; -export const V2ThreadStartedNotification__CommandExecutionStatus = Schema.Literals([ - "inProgress", + | "inProgress"; +export const V2ThreadResumeResponse__TurnStatus = Schema.Literals([ "completed", + "interrupted", "failed", - "declined", -]); - -export type V2ThreadStartedNotification__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadStartedNotification__DynamicToolCallOutputContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadStartedNotification__DynamicToolCallStatus = - | "inProgress" - | "completed" - | "failed"; -export const V2ThreadStartedNotification__DynamicToolCallStatus = Schema.Literals([ "inProgress", - "completed", - "failed", -]); +]).annotate({ identifier: "V2ThreadResumeResponse__TurnStatus" }); -export type V2ThreadStartedNotification__GitInfo = { +export type V2ThreadRevertResponse__AbsolutePathBuf = string; +export const V2ThreadRevertResponse__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.", + identifier: "V2ThreadRevertResponse__AbsolutePathBuf", +}); + +export type V2ThreadRevertResponse__GitInfo = { readonly branch?: string | null; readonly originUrl?: string | null; readonly sha?: string | null; }; -export const V2ThreadStartedNotification__GitInfo = Schema.Struct({ +export const V2ThreadRevertResponse__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ThreadRevertResponse__GitInfo" }); -export type V2ThreadStartedNotification__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadRevertResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadRevertResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadRevertResponse__ThreadHistoryMode" }); + +export type V2ThreadRevertResponse__ReasoningEffort = string; +export const V2ThreadRevertResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadRevertResponse__ReasoningEffort", + }), +); + +export type V2ThreadRevertResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2ThreadStartedNotification__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadRevertResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadRevertResponse__ThreadSectionAppearance", }); -export type V2ThreadStartedNotification__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2ThreadStartedNotification__ImageDetail = Schema.Literals([ - "auto", - "low", - "high", - "original", -]); +export type V2ThreadRevertResponse__AgentPath = string; +export const V2ThreadRevertResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadRevertResponse__AgentPath", +}); -export type V2ThreadStartedNotification__LegacyAppPathString = string; -export const V2ThreadStartedNotification__LegacyAppPathString = Schema.String; +export type V2ThreadRevertResponse__ThreadId = string; +export const V2ThreadRevertResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadRevertResponse__ThreadId", +}); -export type V2ThreadStartedNotification__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; -}; -export const V2ThreadStartedNotification__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +export type V2ThreadRevertResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; +export const V2ThreadRevertResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadRevertResponse__ThreadActiveFlag" }); + +export type V2ThreadRevertResponse__ThreadSource = string; +export const V2ThreadRevertResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadRevertResponse__ThreadSource", }); -export type V2ThreadStartedNotification__McpToolCallError = { readonly message: string }; -export const V2ThreadStartedNotification__McpToolCallError = Schema.Struct({ +export type V2ThreadRevertResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadRevertResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadRevertResponse__NonSteerableTurnKind" }); + +export type V2ThreadRevertResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadRevertResponse__MisalignmentSteer = Schema.Struct({ message: Schema.String, -}); +}).annotate({ identifier: "V2ThreadRevertResponse__MisalignmentSteer" }); -export type V2ThreadStartedNotification__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; +export type V2ThreadRevertResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadRevertResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadRevertResponse__ByteRange" }); + +export type V2ThreadRevertResponse__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ThreadRevertResponse__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2ThreadRevertResponse__ImageDetail" }); + +export type V2ThreadRevertResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadStartedNotification__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); +export const V2ThreadRevertResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadRevertResponse__HookPromptFragment" }); -export type V2ThreadStartedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadStartedNotification__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ThreadRevertResponse__AgentMessageDelivery = "async"; +export const V2ThreadRevertResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadRevertResponse__AgentMessageDelivery", +}); -export type V2ThreadStartedNotification__MemoryCitationEntry = { +export type V2ThreadRevertResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; readonly note: string; readonly path: string; }; -export const V2ThreadStartedNotification__MemoryCitationEntry = Schema.Struct({ +export const V2ThreadRevertResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadRevertResponse__MemoryCitationEntry" }); -export type V2ThreadStartedNotification__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadStartedNotification__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export type V2ThreadRevertResponse__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadRevertResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadRevertResponse__MessagePhase", }); -export type V2ThreadStartedNotification__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadStartedNotification__NonSteerableTurnKind = Schema.Literals([ - "review", - "compact", -]); +export type V2ThreadRevertResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadRevertResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadRevertResponse__AsyncUserInputQuestion" }); -export type V2ThreadStartedNotification__PatchApplyStatus = +export type V2ThreadRevertResponse__LegacyAppPathString = string; +export const V2ThreadRevertResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadRevertResponse__LegacyAppPathString", +}); + +export type V2ThreadRevertResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadRevertResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadRevertResponse__CommandExecutionSource" }); + +export type V2ThreadRevertResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadStartedNotification__PatchApplyStatus = Schema.Literals([ +export const V2ThreadRevertResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadRevertResponse__CommandExecutionStatus" }); -export type V2ThreadStartedNotification__PatchChangeKind = +export type V2ThreadRevertResponse__PatchChangeKind = | { readonly type: "add" } | { readonly type: "delete" } | { readonly move_path?: string | null; readonly type: "update" }; -export const V2ThreadStartedNotification__PatchChangeKind = Schema.Union( +export const V2ThreadRevertResponse__PatchChangeKind = Schema.Union( [ Schema.Struct({ type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), @@ -9132,77 +12354,164 @@ export const V2ThreadStartedNotification__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2ThreadStartedNotification__ReasoningEffort = string; -export const V2ThreadStartedNotification__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadRevertResponse__PatchChangeKind" }); -export type V2ThreadStartedNotification__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadStartedNotification__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadRevertResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadRevertResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadRevertResponse__PatchApplyStatus" }); -export type V2ThreadStartedNotification__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadRevertResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2ThreadStartedNotification__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 const V2ThreadRevertResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadRevertResponse__McpToolCallAppContext" }); -export type V2ThreadStartedNotification__ThreadActiveFlag = - | "waitingOnApproval" - | "waitingOnUserInput"; -export const V2ThreadStartedNotification__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export type V2ThreadRevertResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadRevertResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadRevertResponse__McpToolCallError" }); + +export type V2ThreadRevertResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadRevertResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadRevertResponse__McpAppDisplayMode" }); + +export type V2ThreadRevertResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadRevertResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadRevertResponse__McpToolCallResult" }); + +export type V2ThreadRevertResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadRevertResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadRevertResponse__McpToolCallStatus" }); -export type V2ThreadStartedNotification__ThreadId = string; -export const V2ThreadStartedNotification__ThreadId = Schema.String; +export type V2ThreadRevertResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadRevertResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadRevertResponse__DynamicToolCallOutputContentItem" }); -export type V2ThreadStartedNotification__ThreadSource = string; -export const V2ThreadStartedNotification__ThreadSource = Schema.String; +export type V2ThreadRevertResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadRevertResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadRevertResponse__DynamicToolCallStatus" }); -export type V2ThreadStartedNotification__TurnStatus = - | "completed" +export type V2ThreadRevertResponse__CollabAgentStatus = + | "pendingInit" + | "running" | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadRevertResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadRevertResponse__CollabAgentStatus" }); + +export type V2ThreadRevertResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" | "failed" - | "inProgress"; -export const V2ThreadStartedNotification__TurnStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadRevertResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadRevertResponse__CollabAgentToolCallStatus" }); -export type V2ThreadStartedNotification__WebSearchAction = +export type V2ThreadRevertResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadRevertResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadRevertResponse__CollabAgentTool" }); + +export type V2ThreadRevertResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadRevertResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadRevertResponse__SubAgentActivityKind" }); + +export type V2ThreadRevertResponse__WebSearchAction = | { readonly queries?: ReadonlyArray | null; readonly query?: string | null; @@ -9211,7 +12520,7 @@ export type V2ThreadStartedNotification__WebSearchAction = | { readonly type: "openPage"; readonly url?: string | null } | { readonly pattern?: string | null; readonly type: "findInPage"; readonly url?: string | null } | { readonly type: "other" }; -export const V2ThreadStartedNotification__WebSearchAction = Schema.Union( +export const V2ThreadRevertResponse__WebSearchAction = Schema.Union( [ Schema.Struct({ queries: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), @@ -9232,99 +12541,143 @@ export const V2ThreadStartedNotification__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); - -export type V2ThreadStartParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const V2ThreadStartParams__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); +).annotate({ identifier: "V2ThreadRevertResponse__WebSearchAction" }); -export type V2ThreadStartParams__AskForApproval = - | "untrusted" - | "on-request" - | "never" - | { - readonly granular: { - readonly mcp_elicitations: boolean; - readonly request_permissions?: boolean; - readonly rules: boolean; - readonly sandbox_approval: boolean; - readonly skill_approval?: boolean; - }; - }; -export const V2ThreadStartParams__AskForApproval = Schema.Union( +export type V2ThreadRevertResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadRevertResponse__ImageGenerationFailure = Schema.Union( [ - Schema.Literals(["untrusted", "on-request", "never"]), Schema.Struct({ - granular: Schema.Struct({ - mcp_elicitations: Schema.Boolean, - request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - rules: Schema.Boolean, - sandbox_approval: Schema.Boolean, - skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - }).annotate({ title: "GranularAskForApproval" }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadRevertResponse__ImageGenerationFailure" }); -export type V2ThreadStartParams__DynamicToolNamespaceTool = { - readonly deferLoading?: boolean; - readonly description: string; - readonly inputSchema: unknown; - readonly name: string; - readonly type: "function"; -}; -export const V2ThreadStartParams__DynamicToolNamespaceTool = Schema.Union( +export type V2ThreadRevertResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadRevertResponse__TurnItemsView = Schema.Union( [ - Schema.Struct({ - deferLoading: Schema.optionalKey(Schema.Boolean), - description: Schema.String, - inputSchema: Schema.Unknown, - name: Schema.String, - type: Schema.Literal("function").annotate({ title: "FunctionDynamicToolNamespaceToolType" }), - }).annotate({ title: "FunctionDynamicToolNamespaceTool" }), + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadRevertResponse__TurnItemsView" }); -export type V2ThreadStartParams__LegacyAppPathString = string; -export const V2ThreadStartParams__LegacyAppPathString = Schema.String; +export type V2ThreadRevertResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadRevertResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2ThreadRevertResponse__TurnStatus" }); -export type V2ThreadStartParams__Personality = "none" | "friendly" | "pragmatic"; -export const V2ThreadStartParams__Personality = Schema.Literals(["none", "friendly", "pragmatic"]); +export type V2ThreadSectionCreateParams__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; +}; +export const V2ThreadSectionCreateParams__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadSectionCreateParams__ThreadSectionAppearance", +}); -export type V2ThreadStartParams__SandboxMode = - | "read-only" - | "workspace-write" - | "danger-full-access"; -export const V2ThreadStartParams__SandboxMode = Schema.Literals([ - "read-only", - "workspace-write", - "danger-full-access", -]); +export type V2ThreadSectionCreateResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; +}; +export const V2ThreadSectionCreateResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadSectionCreateResponse__ThreadSectionAppearance", +}); -export type V2ThreadStartParams__ThreadSource = string; -export const V2ThreadStartParams__ThreadSource = Schema.String; +export type V2ThreadSectionListResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; +}; +export const V2ThreadSectionListResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadSectionListResponse__ThreadSectionAppearance", +}); -export type V2ThreadStartParams__ThreadStartSource = "startup" | "clear"; -export const V2ThreadStartParams__ThreadStartSource = Schema.Literals(["startup", "clear"]); +export type V2ThreadSectionUpdateParams__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; +}; +export const V2ThreadSectionUpdateParams__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadSectionUpdateParams__ThreadSectionAppearance", +}); -export type V2ThreadStartResponse__AbsolutePathBuf = string; -export const V2ThreadStartResponse__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 V2ThreadSectionUpdateResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; +}; +export const V2ThreadSectionUpdateResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadSectionUpdateResponse__ThreadSectionAppearance", }); -export type V2ThreadStartResponse__AgentPath = string; -export const V2ThreadStartResponse__AgentPath = Schema.String; +export type V2ThreadSettingsUpdatedNotification__ActivePermissionProfile = { + readonly extends?: string | null; + readonly id: string; +}; +export const V2ThreadSettingsUpdatedNotification__ActivePermissionProfile = Schema.Struct({ + extends: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", + }), + Schema.Null, + ]), + ), + id: Schema.String.annotate({ + description: + "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", + }), +}).annotate({ identifier: "V2ThreadSettingsUpdatedNotification__ActivePermissionProfile" }); -export type V2ThreadStartResponse__AskForApproval = +export type V2ThreadSettingsUpdatedNotification__AskForApproval = | "untrusted" | "on-request" | "never" @@ -9337,7 +12690,7 @@ export type V2ThreadStartResponse__AskForApproval = readonly skill_approval?: boolean; }; }; -export const V2ThreadStartResponse__AskForApproval = Schema.Union( +export const V2ThreadSettingsUpdatedNotification__AskForApproval = Schema.Union( [ Schema.Literals(["untrusted", "on-request", "never"]), Schema.Struct({ @@ -9351,186 +12704,282 @@ export const V2ThreadStartResponse__AskForApproval = Schema.Union( }).annotate({ title: "GranularAskForApproval" }), ], { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadSettingsUpdatedNotification__AskForApproval" }); + +export type V2ThreadSettingsUpdatedNotification__ApprovalsReviewer = + | "user" + | "auto_review" + | "guardian_subagent"; +export const V2ThreadSettingsUpdatedNotification__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ThreadSettingsUpdatedNotification__ApprovalsReviewer", +}); + +export type V2ThreadSettingsUpdatedNotification__ModeKind = "plan" | "default"; +export const V2ThreadSettingsUpdatedNotification__ModeKind = Schema.Literals([ + "plan", + "default", +]).annotate({ + description: "Initial collaboration mode to use when the TUI starts.", + identifier: "V2ThreadSettingsUpdatedNotification__ModeKind", +}); + +export type V2ThreadSettingsUpdatedNotification__ReasoningEffort = string; +export const V2ThreadSettingsUpdatedNotification__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadSettingsUpdatedNotification__ReasoningEffort", + }), ); -export type V2ThreadStartResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2ThreadStartResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +export type V2ThreadSettingsUpdatedNotification__AbsolutePathBuf = string; +export const V2ThreadSettingsUpdatedNotification__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.", + identifier: "V2ThreadSettingsUpdatedNotification__AbsolutePathBuf", +}); -export type V2ThreadStartResponse__CommandExecutionStatus = - | "inProgress" - | "completed" - | "failed" - | "declined"; -export const V2ThreadStartResponse__CommandExecutionStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "declined", -]); +export type V2ThreadSettingsUpdatedNotification__Personality = "none" | "friendly" | "pragmatic"; +export const V2ThreadSettingsUpdatedNotification__Personality = Schema.Literals([ + "none", + "friendly", + "pragmatic", +]).annotate({ + description: "Deprecated: `friendly` and `pragmatic` no longer select a style.", + identifier: "V2ThreadSettingsUpdatedNotification__Personality", +}); -export type V2ThreadStartResponse__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadStartResponse__DynamicToolCallOutputContentItem = Schema.Union( +export type V2ThreadSettingsUpdatedNotification__NetworkAccess = "restricted" | "enabled"; +export const V2ThreadSettingsUpdatedNotification__NetworkAccess = Schema.Literals([ + "restricted", + "enabled", +]).annotate({ identifier: "V2ThreadSettingsUpdatedNotification__NetworkAccess" }); + +export type V2ThreadSettingsUpdatedNotification__ReasoningSummary = + | "auto" + | "concise" + | "detailed" + | "none"; +export const V2ThreadSettingsUpdatedNotification__ReasoningSummary = Schema.Union( [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + Schema.Literals(["auto", "concise", "detailed"]), + Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), ], { mode: "oneOf" }, -); +).annotate({ + description: + "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", + identifier: "V2ThreadSettingsUpdatedNotification__ReasoningSummary", +}); -export type V2ThreadStartResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadStartResponse__DynamicToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +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.", + identifier: "V2ThreadStartedNotification__AbsolutePathBuf", +}); -export type V2ThreadStartResponse__GitInfo = { +export type V2ThreadStartedNotification__GitInfo = { readonly branch?: string | null; readonly originUrl?: string | null; readonly sha?: string | null; }; -export const V2ThreadStartResponse__GitInfo = Schema.Struct({ +export const V2ThreadStartedNotification__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ThreadStartedNotification__GitInfo" }); -export type V2ThreadStartResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadStartedNotification__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadStartedNotification__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadStartedNotification__ThreadHistoryMode" }); + +export type V2ThreadStartedNotification__ReasoningEffort = string; +export const V2ThreadStartedNotification__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadStartedNotification__ReasoningEffort", + }), +); + +export type V2ThreadStartedNotification__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2ThreadStartResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadStartedNotification__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadStartedNotification__ThreadSectionAppearance", }); -export type V2ThreadStartResponse__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2ThreadStartResponse__ImageDetail = Schema.Literals([ +export type V2ThreadStartedNotification__AgentPath = string; +export const V2ThreadStartedNotification__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadStartedNotification__AgentPath", +}); + +export type V2ThreadStartedNotification__ThreadId = string; +export const V2ThreadStartedNotification__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadStartedNotification__ThreadId", +}); + +export type V2ThreadStartedNotification__ThreadActiveFlag = + | "waitingOnApproval" + | "waitingOnUserInput"; +export const V2ThreadStartedNotification__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadStartedNotification__ThreadActiveFlag" }); + +export type V2ThreadStartedNotification__ThreadSource = string; +export const V2ThreadStartedNotification__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadStartedNotification__ThreadSource", +}); + +export type V2ThreadStartedNotification__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadStartedNotification__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadStartedNotification__NonSteerableTurnKind" }); + +export type V2ThreadStartedNotification__MisalignmentSteer = { readonly message: string }; +export const V2ThreadStartedNotification__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadStartedNotification__MisalignmentSteer" }); + +export type V2ThreadStartedNotification__ByteRange = { + readonly end: number; + readonly start: number; +}; +export const V2ThreadStartedNotification__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadStartedNotification__ByteRange" }); + +export type V2ThreadStartedNotification__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ThreadStartedNotification__ImageDetail = Schema.Literals([ "auto", "low", "high", "original", -]); - -export type V2ThreadStartResponse__LegacyAppPathString = string; -export const V2ThreadStartResponse__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2ThreadStartedNotification__ImageDetail" }); -export type V2ThreadStartResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ThreadStartedNotification__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadStartResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadStartResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadStartResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); +export const V2ThreadStartedNotification__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadStartedNotification__HookPromptFragment" }); -export type V2ThreadStartResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2ThreadStartResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2ThreadStartedNotification__AgentMessageDelivery = "async"; +export const V2ThreadStartedNotification__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadStartedNotification__AgentMessageDelivery", }); -export type V2ThreadStartResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadStartResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - -export type V2ThreadStartResponse__MemoryCitationEntry = { +export type V2ThreadStartedNotification__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; readonly note: string; readonly path: string; }; -export const V2ThreadStartResponse__MemoryCitationEntry = Schema.Struct({ +export const V2ThreadStartedNotification__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadStartedNotification__MemoryCitationEntry" }); -export type V2ThreadStartResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadStartResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export type V2ThreadStartedNotification__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadStartedNotification__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadStartedNotification__MessagePhase", }); -export type V2ThreadStartResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadStartResponse__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type V2ThreadStartedNotification__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadStartedNotification__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadStartedNotification__AsyncUserInputQuestion" }); -export type V2ThreadStartResponse__PatchApplyStatus = +export type V2ThreadStartedNotification__LegacyAppPathString = string; +export const V2ThreadStartedNotification__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadStartedNotification__LegacyAppPathString", +}); + +export type V2ThreadStartedNotification__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadStartedNotification__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadStartedNotification__CommandExecutionSource" }); + +export type V2ThreadStartedNotification__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadStartResponse__PatchApplyStatus = Schema.Literals([ +export const V2ThreadStartedNotification__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadStartedNotification__CommandExecutionStatus" }); -export type V2ThreadStartResponse__PatchChangeKind = +export type V2ThreadStartedNotification__PatchChangeKind = | { readonly type: "add" } | { readonly type: "delete" } | { readonly move_path?: string | null; readonly type: "update" }; -export const V2ThreadStartResponse__PatchChangeKind = Schema.Union( +export const V2ThreadStartedNotification__PatchChangeKind = Schema.Union( [ Schema.Struct({ type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), @@ -9544,151 +12993,111 @@ export const V2ThreadStartResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2ThreadStartResponse__ReasoningEffort = string; -export const V2ThreadStartResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadStartedNotification__PatchChangeKind" }); -export type V2ThreadStartResponse__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadStartResponse__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadStartedNotification__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadStartedNotification__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadStartedNotification__PatchApplyStatus" }); -export type V2ThreadStartResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadStartedNotification__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2ThreadStartResponse__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 const V2ThreadStartedNotification__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadStartedNotification__McpToolCallAppContext" }); -export type V2ThreadStartResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; -export const V2ThreadStartResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export type V2ThreadStartedNotification__McpToolCallError = { readonly message: string }; +export const V2ThreadStartedNotification__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadStartedNotification__McpToolCallError" }); -export type V2ThreadStartResponse__ThreadId = string; -export const V2ThreadStartResponse__ThreadId = Schema.String; +export type V2ThreadStartedNotification__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadStartedNotification__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadStartedNotification__McpAppDisplayMode" }); -export type V2ThreadStartResponse__ThreadSource = string; -export const V2ThreadStartResponse__ThreadSource = Schema.String; +export type V2ThreadStartedNotification__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadStartedNotification__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadStartedNotification__McpToolCallResult" }); -export type V2ThreadStartResponse__TurnStatus = - | "completed" - | "interrupted" - | "failed" - | "inProgress"; -export const V2ThreadStartResponse__TurnStatus = Schema.Literals([ +export type V2ThreadStartedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadStartedNotification__McpToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); +]).annotate({ identifier: "V2ThreadStartedNotification__McpToolCallStatus" }); -export type V2ThreadStartResponse__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 V2ThreadStartResponse__WebSearchAction = Schema.Union( +export type V2ThreadStartedNotification__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadStartedNotification__DynamicToolCallOutputContentItem = 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: "SearchWebSearchActionType" }), - }).annotate({ title: "SearchWebSearchAction" }), - Schema.Struct({ - type: Schema.Literal("openPage").annotate({ title: "OpenPageWebSearchActionType" }), - url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }).annotate({ title: "OpenPageWebSearchAction" }), + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), 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" }), + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), Schema.Struct({ - type: Schema.Literal("other").annotate({ title: "OtherWebSearchActionType" }), - }).annotate({ title: "OtherWebSearchAction" }), + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, -); - -export type V2ThreadStatusChangedNotification__ThreadActiveFlag = - | "waitingOnApproval" - | "waitingOnUserInput"; -export const V2ThreadStatusChangedNotification__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); - -export type V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown = { - readonly cacheWriteInputTokens?: number; - readonly cachedInputTokens: number; - readonly inputTokens: number; - readonly outputTokens: number; - readonly reasoningOutputTokens: number; - readonly totalTokens: number; -}; -export const V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown = Schema.Struct({ - cacheWriteInputTokens: Schema.optionalKey( - Schema.Number.annotate({ default: 0, format: "int64" }).check(Schema.isInt()), - ), - cachedInputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - inputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - outputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - reasoningOutputTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - totalTokens: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -}); - -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.", -}); +).annotate({ identifier: "V2ThreadStartedNotification__DynamicToolCallOutputContentItem" }); -export type V2ThreadUnarchiveResponse__AgentPath = string; -export const V2ThreadUnarchiveResponse__AgentPath = Schema.String; - -export type V2ThreadUnarchiveResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" +export type V2ThreadStartedNotification__DynamicToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2ThreadStartedNotification__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadStartedNotification__DynamicToolCallStatus" }); + +export type V2ThreadStartedNotification__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" | "completed" | "errored" | "shutdown" | "notFound"; -export const V2ThreadUnarchiveResponse__CollabAgentStatus = Schema.Literals([ +export const V2ThreadStartedNotification__CollabAgentStatus = Schema.Literals([ "pendingInit", "running", "interrupted", @@ -9696,176 +13105,504 @@ export const V2ThreadUnarchiveResponse__CollabAgentStatus = Schema.Literals([ "errored", "shutdown", "notFound", -]); +]).annotate({ identifier: "V2ThreadStartedNotification__CollabAgentStatus" }); -export type V2ThreadUnarchiveResponse__CommandExecutionStatus = +export type V2ThreadStartedNotification__CollabAgentToolCallStatus = | "inProgress" | "completed" | "failed" - | "declined"; -export const V2ThreadUnarchiveResponse__CommandExecutionStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadStartedNotification__CollabAgentToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", - "declined", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadStartedNotification__CollabAgentToolCallStatus" }); -export type V2ThreadUnarchiveResponse__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2ThreadUnarchiveResponse__DynamicToolCallOutputContentItem = Schema.Union( +export type V2ThreadStartedNotification__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadStartedNotification__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadStartedNotification__CollabAgentTool" }); + +export type V2ThreadStartedNotification__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadStartedNotification__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadStartedNotification__SubAgentActivityKind" }); + +export type V2ThreadStartedNotification__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 V2ThreadStartedNotification__WebSearchAction = Schema.Union( [ Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + 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: "SearchWebSearchActionType" }), + }).annotate({ title: "SearchWebSearchAction" }), Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + type: Schema.Literal("openPage").annotate({ title: "OpenPageWebSearchActionType" }), + url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "OpenPageWebSearchAction" }), Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", + 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" }, +).annotate({ identifier: "V2ThreadStartedNotification__WebSearchAction" }); + +export type V2ThreadStartedNotification__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadStartedNotification__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadStartedNotification__ImageGenerationFailure" }); -export type V2ThreadUnarchiveResponse__DynamicToolCallStatus = - | "inProgress" +export type V2ThreadStartedNotification__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadStartedNotification__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartedNotification__TurnItemsView" }); + +export type V2ThreadStartedNotification__TurnStatus = | "completed" - | "failed"; -export const V2ThreadUnarchiveResponse__DynamicToolCallStatus = Schema.Literals([ - "inProgress", + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadStartedNotification__TurnStatus = Schema.Literals([ "completed", + "interrupted", "failed", -]); + "inProgress", +]).annotate({ identifier: "V2ThreadStartedNotification__TurnStatus" }); -export type V2ThreadUnarchiveResponse__GitInfo = { +export type V2ThreadStartParams__AskForApproval = + | "untrusted" + | "on-request" + | "never" + | { + readonly granular: { + readonly mcp_elicitations: boolean; + readonly request_permissions?: boolean; + readonly rules: boolean; + readonly sandbox_approval: boolean; + readonly skill_approval?: boolean; + }; + }; +export const V2ThreadStartParams__AskForApproval = Schema.Union( + [ + Schema.Literals(["untrusted", "on-request", "never"]), + Schema.Struct({ + granular: Schema.Struct({ + mcp_elicitations: Schema.Boolean, + request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + rules: Schema.Boolean, + sandbox_approval: Schema.Boolean, + skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + }), + }).annotate({ title: "GranularAskForApproval" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartParams__AskForApproval" }); + +export type V2ThreadStartParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const V2ThreadStartParams__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ThreadStartParams__ApprovalsReviewer", +}); + +export type V2ThreadStartParams__Personality = "none" | "friendly" | "pragmatic"; +export const V2ThreadStartParams__Personality = Schema.Literals([ + "none", + "friendly", + "pragmatic", +]).annotate({ + description: "Deprecated: `friendly` and `pragmatic` no longer select a style.", + identifier: "V2ThreadStartParams__Personality", +}); + +export type V2ThreadStartParams__SandboxMode = + | "read-only" + | "workspace-write" + | "danger-full-access"; +export const V2ThreadStartParams__SandboxMode = Schema.Literals([ + "read-only", + "workspace-write", + "danger-full-access", +]).annotate({ identifier: "V2ThreadStartParams__SandboxMode" }); + +export type V2ThreadStartParams__ThreadStartSource = "startup" | "clear"; +export const V2ThreadStartParams__ThreadStartSource = Schema.Literals([ + "startup", + "clear", +]).annotate({ identifier: "V2ThreadStartParams__ThreadStartSource" }); + +export type V2ThreadStartParams__ThreadSource = string; +export const V2ThreadStartParams__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadStartParams__ThreadSource", +}); + +export type V2ThreadStartParams__DynamicToolNamespaceTool = { + readonly deferLoading?: boolean; + readonly description: string; + readonly inputSchema: Schema.Json; + readonly name: string; + readonly type: "function"; +}; +export const V2ThreadStartParams__DynamicToolNamespaceTool = Schema.Union( + [ + Schema.Struct({ + deferLoading: Schema.optionalKey(Schema.Boolean), + description: Schema.String, + inputSchema: Schema.Json.annotate({ expected: "JSON value" }), + name: Schema.String, + type: Schema.Literal("function").annotate({ title: "FunctionDynamicToolNamespaceToolType" }), + }).annotate({ title: "FunctionDynamicToolNamespaceTool" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartParams__DynamicToolNamespaceTool" }); + +export type V2ThreadStartParams__CapabilityRootLocation = { + readonly environmentId: string; + readonly path: string; + readonly type: "environment"; +}; +export const V2ThreadStartParams__CapabilityRootLocation = Schema.Union( + [ + Schema.Struct({ + environmentId: Schema.String, + path: Schema.String.annotate({ + description: "Absolute path for the root in the selected environment.", + }), + type: Schema.Literal("environment").annotate({ + title: "EnvironmentCapabilityRootLocationType", + }), + }).annotate({ + title: "EnvironmentCapabilityRootLocation", + description: "A path owned by an execution environment.", + }), + ], + { mode: "oneOf" }, +).annotate({ + description: "Location used to resolve a selected capability root.", + identifier: "V2ThreadStartParams__CapabilityRootLocation", +}); + +export type V2ThreadStartParams__LegacyAppPathString = string; +export const V2ThreadStartParams__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadStartParams__LegacyAppPathString", +}); + +export type V2ThreadStartResponse__AskForApproval = + | "untrusted" + | "on-request" + | "never" + | { + readonly granular: { + readonly mcp_elicitations: boolean; + readonly request_permissions?: boolean; + readonly rules: boolean; + readonly sandbox_approval: boolean; + readonly skill_approval?: boolean; + }; + }; +export const V2ThreadStartResponse__AskForApproval = Schema.Union( + [ + Schema.Literals(["untrusted", "on-request", "never"]), + Schema.Struct({ + granular: Schema.Struct({ + mcp_elicitations: Schema.Boolean, + request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + rules: Schema.Boolean, + sandbox_approval: Schema.Boolean, + skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + }), + }).annotate({ title: "GranularAskForApproval" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartResponse__AskForApproval" }); + +export type V2ThreadStartResponse__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const V2ThreadStartResponse__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).annotate({ + 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2ThreadStartResponse__ApprovalsReviewer", +}); + +export type V2ThreadStartResponse__AbsolutePathBuf = string; +export const V2ThreadStartResponse__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.", + identifier: "V2ThreadStartResponse__AbsolutePathBuf", +}); + +export type V2ThreadStartResponse__LegacyAppPathString = string; +export const V2ThreadStartResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadStartResponse__LegacyAppPathString", +}); + +export type V2ThreadStartResponse__ReasoningEffort = string; +export const V2ThreadStartResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadStartResponse__ReasoningEffort", + }), +); + +export type V2ThreadStartResponse__NetworkAccess = "restricted" | "enabled"; +export const V2ThreadStartResponse__NetworkAccess = Schema.Literals([ + "restricted", + "enabled", +]).annotate({ identifier: "V2ThreadStartResponse__NetworkAccess" }); + +export type V2ThreadStartResponse__GitInfo = { readonly branch?: string | null; readonly originUrl?: string | null; readonly sha?: string | null; }; -export const V2ThreadUnarchiveResponse__GitInfo = Schema.Struct({ +export const V2ThreadStartResponse__GitInfo = Schema.Struct({ branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +}).annotate({ identifier: "V2ThreadStartResponse__GitInfo" }); -export type V2ThreadUnarchiveResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadStartResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadStartResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadStartResponse__ThreadHistoryMode" }); + +export type V2ThreadStartResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2ThreadUnarchiveResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadStartResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadStartResponse__ThreadSectionAppearance", }); -export type V2ThreadUnarchiveResponse__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2ThreadUnarchiveResponse__ImageDetail = Schema.Literals([ - "auto", - "low", - "high", - "original", -]); +export type V2ThreadStartResponse__AgentPath = string; +export const V2ThreadStartResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadStartResponse__AgentPath", +}); -export type V2ThreadUnarchiveResponse__LegacyAppPathString = string; -export const V2ThreadUnarchiveResponse__LegacyAppPathString = Schema.String; +export type V2ThreadStartResponse__ThreadId = string; +export const V2ThreadStartResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadStartResponse__ThreadId", +}); -export type V2ThreadUnarchiveResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; -}; -export const V2ThreadUnarchiveResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +export type V2ThreadStartResponse__ThreadActiveFlag = "waitingOnApproval" | "waitingOnUserInput"; +export const V2ThreadStartResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadStartResponse__ThreadActiveFlag" }); + +export type V2ThreadStartResponse__ThreadSource = string; +export const V2ThreadStartResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadStartResponse__ThreadSource", }); -export type V2ThreadUnarchiveResponse__McpToolCallError = { readonly message: string }; -export const V2ThreadUnarchiveResponse__McpToolCallError = Schema.Struct({ +export type V2ThreadStartResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadStartResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadStartResponse__NonSteerableTurnKind" }); + +export type V2ThreadStartResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadStartResponse__MisalignmentSteer = Schema.Struct({ message: Schema.String, -}); +}).annotate({ identifier: "V2ThreadStartResponse__MisalignmentSteer" }); -export type V2ThreadUnarchiveResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; +export type V2ThreadStartResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadStartResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadStartResponse__ByteRange" }); + +export type V2ThreadStartResponse__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ThreadStartResponse__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2ThreadStartResponse__ImageDetail" }); + +export type V2ThreadStartResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2ThreadUnarchiveResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); +export const V2ThreadStartResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadStartResponse__HookPromptFragment" }); -export type V2ThreadUnarchiveResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2ThreadUnarchiveResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ThreadStartResponse__AgentMessageDelivery = "async"; +export const V2ThreadStartResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadStartResponse__AgentMessageDelivery", +}); -export type V2ThreadUnarchiveResponse__MemoryCitationEntry = { +export type V2ThreadStartResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; readonly note: string; readonly path: string; }; -export const V2ThreadUnarchiveResponse__MemoryCitationEntry = Schema.Struct({ +export const V2ThreadStartResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadStartResponse__MemoryCitationEntry" }); -export type V2ThreadUnarchiveResponse__MessagePhase = "commentary" | "final_answer"; -export const V2ThreadUnarchiveResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export type V2ThreadStartResponse__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadStartResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadStartResponse__MessagePhase", }); -export type V2ThreadUnarchiveResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2ThreadUnarchiveResponse__NonSteerableTurnKind = Schema.Literals([ - "review", - "compact", -]); +export type V2ThreadStartResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadStartResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadStartResponse__AsyncUserInputQuestion" }); -export type V2ThreadUnarchiveResponse__PatchApplyStatus = +export type V2ThreadStartResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadStartResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadStartResponse__CommandExecutionSource" }); + +export type V2ThreadStartResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2ThreadUnarchiveResponse__PatchApplyStatus = Schema.Literals([ +export const V2ThreadStartResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadStartResponse__CommandExecutionStatus" }); -export type V2ThreadUnarchiveResponse__PatchChangeKind = +export type V2ThreadStartResponse__PatchChangeKind = | { readonly type: "add" } | { readonly type: "delete" } | { readonly move_path?: string | null; readonly type: "update" }; -export const V2ThreadUnarchiveResponse__PatchChangeKind = Schema.Union( +export const V2ThreadStartResponse__PatchChangeKind = Schema.Union( [ Schema.Struct({ type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), @@ -9879,125 +13616,100 @@ export const V2ThreadUnarchiveResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadStartResponse__PatchChangeKind" }); -export type V2ThreadUnarchiveResponse__ReasoningEffort = string; -export const V2ThreadUnarchiveResponse__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); - -export type V2ThreadUnarchiveResponse__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2ThreadUnarchiveResponse__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadStartResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadStartResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadStartResponse__PatchApplyStatus" }); -export type V2ThreadUnarchiveResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadStartResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2ThreadUnarchiveResponse__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 const V2ThreadStartResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadStartResponse__McpToolCallAppContext" }); -export type V2ThreadUnarchiveResponse__ThreadActiveFlag = - | "waitingOnApproval" - | "waitingOnUserInput"; -export const V2ThreadUnarchiveResponse__ThreadActiveFlag = Schema.Literals([ - "waitingOnApproval", - "waitingOnUserInput", -]); +export type V2ThreadStartResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadStartResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadStartResponse__McpToolCallError" }); -export type V2ThreadUnarchiveResponse__ThreadId = string; -export const V2ThreadUnarchiveResponse__ThreadId = Schema.String; +export type V2ThreadStartResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadStartResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadStartResponse__McpAppDisplayMode" }); -export type V2ThreadUnarchiveResponse__ThreadSource = string; -export const V2ThreadUnarchiveResponse__ThreadSource = Schema.String; +export type V2ThreadStartResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadStartResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadStartResponse__McpToolCallResult" }); -export type V2ThreadUnarchiveResponse__TurnStatus = - | "completed" - | "interrupted" - | "failed" - | "inProgress"; -export const V2ThreadUnarchiveResponse__TurnStatus = Schema.Literals([ +export type V2ThreadStartResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadStartResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); +]).annotate({ identifier: "V2ThreadStartResponse__McpToolCallStatus" }); -export type V2ThreadUnarchiveResponse__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 V2ThreadUnarchiveResponse__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: "SearchWebSearchActionType" }), - }).annotate({ title: "SearchWebSearchAction" }), +export type V2ThreadStartResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadStartResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ Schema.Struct({ - type: Schema.Literal("openPage").annotate({ title: "OpenPageWebSearchActionType" }), - url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }).annotate({ title: "OpenPageWebSearchAction" }), + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), 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" }), + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), Schema.Struct({ - type: Schema.Literal("other").annotate({ title: "OtherWebSearchActionType" }), - }).annotate({ title: "OtherWebSearchAction" }), + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, -); - -export type V2ThreadUnsubscribeResponse__ThreadUnsubscribeStatus = - | "notLoaded" - | "notSubscribed" - | "unsubscribed"; -export const V2ThreadUnsubscribeResponse__ThreadUnsubscribeStatus = Schema.Literals([ - "notLoaded", - "notSubscribed", - "unsubscribed", -]); +).annotate({ identifier: "V2ThreadStartResponse__DynamicToolCallOutputContentItem" }); -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 V2ThreadStartResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadStartResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadStartResponse__DynamicToolCallStatus" }); -export type V2TurnCompletedNotification__CollabAgentStatus = +export type V2ThreadStartResponse__CollabAgentStatus = | "pendingInit" | "running" | "interrupted" @@ -10005,7 +13717,7 @@ export type V2TurnCompletedNotification__CollabAgentStatus = | "errored" | "shutdown" | "notFound"; -export const V2TurnCompletedNotification__CollabAgentStatus = Schema.Literals([ +export const V2ThreadStartResponse__CollabAgentStatus = Schema.Literals([ "pendingInit", "running", "interrupted", @@ -10013,165 +13725,330 @@ export const V2TurnCompletedNotification__CollabAgentStatus = Schema.Literals([ "errored", "shutdown", "notFound", -]); +]).annotate({ identifier: "V2ThreadStartResponse__CollabAgentStatus" }); -export type V2TurnCompletedNotification__CommandExecutionStatus = +export type V2ThreadStartResponse__CollabAgentToolCallStatus = | "inProgress" | "completed" | "failed" - | "declined"; -export const V2TurnCompletedNotification__CommandExecutionStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadStartResponse__CollabAgentToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", - "declined", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadStartResponse__CollabAgentToolCallStatus" }); -export type V2TurnCompletedNotification__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2TurnCompletedNotification__DynamicToolCallOutputContentItem = Schema.Union( +export type V2ThreadStartResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadStartResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadStartResponse__CollabAgentTool" }); + +export type V2ThreadStartResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadStartResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadStartResponse__SubAgentActivityKind" }); + +export type V2ThreadStartResponse__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 V2ThreadStartResponse__WebSearchAction = Schema.Union( [ Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + 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: "SearchWebSearchActionType" }), + }).annotate({ title: "SearchWebSearchAction" }), Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + type: Schema.Literal("openPage").annotate({ title: "OpenPageWebSearchActionType" }), + url: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ title: "OpenPageWebSearchAction" }), Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", + 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" }, +).annotate({ identifier: "V2ThreadStartResponse__WebSearchAction" }); + +export type V2ThreadStartResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadStartResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadStartResponse__ImageGenerationFailure" }); -export type V2TurnCompletedNotification__DynamicToolCallStatus = - | "inProgress" +export type V2ThreadStartResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadStartResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartResponse__TurnItemsView" }); + +export type V2ThreadStartResponse__TurnStatus = | "completed" - | "failed"; -export const V2TurnCompletedNotification__DynamicToolCallStatus = Schema.Literals([ - "inProgress", + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadStartResponse__TurnStatus = Schema.Literals([ "completed", + "interrupted", "failed", -]); + "inProgress", +]).annotate({ identifier: "V2ThreadStartResponse__TurnStatus" }); -export type V2TurnCompletedNotification__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadStatusChangedNotification__ThreadActiveFlag = + | "waitingOnApproval" + | "waitingOnUserInput"; +export const V2ThreadStatusChangedNotification__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadStatusChangedNotification__ThreadActiveFlag" }); + +export type V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown = { + readonly cacheWriteInputTokens?: number; + readonly cachedInputTokens: number; + readonly inputTokens: number; + readonly outputTokens: number; + readonly reasoningOutputTokens: number; + readonly totalTokens: number; }; -export const V2TurnCompletedNotification__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown = Schema.Struct({ + cacheWriteInputTokens: Schema.optionalKey( + Schema.Number.annotate({ default: 0, format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + ), + cachedInputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + inputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + outputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + reasoningOutputTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + totalTokens: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +}).annotate({ identifier: "V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown" }); + +export type V2ThreadTurnsListParams__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadTurnsListParams__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadTurnsListParams__TurnItemsView" }); + +export type V2ThreadTurnsListParams__SortDirection = "asc" | "desc"; +export const V2ThreadTurnsListParams__SortDirection = Schema.Literals(["asc", "desc"]).annotate({ + identifier: "V2ThreadTurnsListParams__SortDirection", }); -export type V2TurnCompletedNotification__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2TurnCompletedNotification__ImageDetail = Schema.Literals([ +export type V2ThreadTurnsListResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadTurnsListResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadTurnsListResponse__NonSteerableTurnKind" }); + +export type V2ThreadTurnsListResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadTurnsListResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadTurnsListResponse__MisalignmentSteer" }); + +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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadTurnsListResponse__ByteRange" }); + +export type V2ThreadTurnsListResponse__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ThreadTurnsListResponse__ImageDetail = Schema.Literals([ "auto", "low", "high", "original", -]); - -export type V2TurnCompletedNotification__LegacyAppPathString = string; -export const V2TurnCompletedNotification__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2ThreadTurnsListResponse__ImageDetail" }); -export type V2TurnCompletedNotification__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2ThreadTurnsListResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2TurnCompletedNotification__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2TurnCompletedNotification__McpToolCallError = { readonly message: string }; -export const V2TurnCompletedNotification__McpToolCallError = Schema.Struct({ - message: Schema.String, -}); +export const V2ThreadTurnsListResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadTurnsListResponse__HookPromptFragment" }); -export type V2TurnCompletedNotification__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2TurnCompletedNotification__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2ThreadTurnsListResponse__AgentMessageDelivery = "async"; +export const V2ThreadTurnsListResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadTurnsListResponse__AgentMessageDelivery", }); -export type V2TurnCompletedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2TurnCompletedNotification__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - -export type V2TurnCompletedNotification__MemoryCitationEntry = { +export type V2ThreadTurnsListResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; readonly note: string; readonly path: string; }; -export const V2TurnCompletedNotification__MemoryCitationEntry = Schema.Struct({ +export const V2ThreadTurnsListResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadTurnsListResponse__MemoryCitationEntry" }); -export type V2TurnCompletedNotification__MessagePhase = "commentary" | "final_answer"; -export const V2TurnCompletedNotification__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export type V2ThreadTurnsListResponse__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadTurnsListResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadTurnsListResponse__MessagePhase", }); -export type V2TurnCompletedNotification__NonSteerableTurnKind = "review" | "compact"; -export const V2TurnCompletedNotification__NonSteerableTurnKind = Schema.Literals([ - "review", - "compact", -]); +export type V2ThreadTurnsListResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadTurnsListResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadTurnsListResponse__AsyncUserInputQuestion" }); -export type V2TurnCompletedNotification__PatchApplyStatus = +export type V2ThreadTurnsListResponse__LegacyAppPathString = string; +export const V2ThreadTurnsListResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadTurnsListResponse__LegacyAppPathString", +}); + +export type V2ThreadTurnsListResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadTurnsListResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadTurnsListResponse__CommandExecutionSource" }); + +export type V2ThreadTurnsListResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2TurnCompletedNotification__PatchApplyStatus = Schema.Literals([ +export const V2ThreadTurnsListResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadTurnsListResponse__CommandExecutionStatus" }); -export type V2TurnCompletedNotification__PatchChangeKind = +export type V2ThreadTurnsListResponse__PatchChangeKind = | { readonly type: "add" } | { readonly type: "delete" } | { readonly move_path?: string | null; readonly type: "update" }; -export const V2TurnCompletedNotification__PatchChangeKind = Schema.Union( +export const V2ThreadTurnsListResponse__PatchChangeKind = Schema.Union( [ Schema.Struct({ type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), @@ -10185,63 +14062,177 @@ export const V2TurnCompletedNotification__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadTurnsListResponse__PatchChangeKind" }); -export type V2TurnCompletedNotification__ReasoningEffort = string; -export const V2TurnCompletedNotification__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +export type V2ThreadTurnsListResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadTurnsListResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]).annotate({ identifier: "V2ThreadTurnsListResponse__PatchApplyStatus" }); -export type V2TurnCompletedNotification__SubAgentActivityKind = - | "started" - | "interacted" +export type V2ThreadTurnsListResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; +}; +export const V2ThreadTurnsListResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadTurnsListResponse__McpToolCallAppContext" }); + +export type V2ThreadTurnsListResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadTurnsListResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadTurnsListResponse__McpToolCallError" }); + +export type V2ThreadTurnsListResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadTurnsListResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadTurnsListResponse__McpAppDisplayMode" }); + +export type V2ThreadTurnsListResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadTurnsListResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadTurnsListResponse__McpToolCallResult" }); + +export type V2ThreadTurnsListResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadTurnsListResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadTurnsListResponse__McpToolCallStatus" }); + +export type V2ThreadTurnsListResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadTurnsListResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadTurnsListResponse__DynamicToolCallOutputContentItem" }); + +export type V2ThreadTurnsListResponse__DynamicToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2ThreadTurnsListResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadTurnsListResponse__DynamicToolCallStatus" }); + +export type V2ThreadTurnsListResponse__CollabAgentStatus = + | "pendingInit" + | "running" | "interrupted" - | "completed"; -export const V2TurnCompletedNotification__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadTurnsListResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", "interrupted", "completed", -]); + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadTurnsListResponse__CollabAgentStatus" }); -export type V2TurnCompletedNotification__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const V2TurnCompletedNotification__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.", +export type V2ThreadTurnsListResponse__ReasoningEffort = string; +export const V2ThreadTurnsListResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadTurnsListResponse__ReasoningEffort", }), - placeholder: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Optional human-readable placeholder for the element, displayed in the UI.", - }), - Schema.Null, - ]), - ), -}); +); -export type V2TurnCompletedNotification__TurnStatus = +export type V2ThreadTurnsListResponse__CollabAgentToolCallStatus = + | "inProgress" | "completed" - | "interrupted" | "failed" - | "inProgress"; -export const V2TurnCompletedNotification__TurnStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadTurnsListResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadTurnsListResponse__CollabAgentToolCallStatus" }); -export type V2TurnCompletedNotification__WebSearchAction = +export type V2ThreadTurnsListResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadTurnsListResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadTurnsListResponse__CollabAgentTool" }); + +export type V2ThreadTurnsListResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadTurnsListResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadTurnsListResponse__SubAgentActivityKind" }); + +export type V2ThreadTurnsListResponse__WebSearchAction = | { readonly queries?: ReadonlyArray | null; readonly query?: string | null; @@ -10250,7 +14241,7 @@ export type V2TurnCompletedNotification__WebSearchAction = | { readonly type: "openPage"; readonly url?: string | null } | { readonly pattern?: string | null; readonly type: "findInPage"; readonly url?: string | null } | { readonly type: "other" }; -export const V2TurnCompletedNotification__WebSearchAction = Schema.Union( +export const V2ThreadTurnsListResponse__WebSearchAction = Schema.Union( [ Schema.Struct({ queries: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), @@ -10271,199 +14262,267 @@ export const V2TurnCompletedNotification__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadTurnsListResponse__WebSearchAction" }); -export type V2TurnPlanUpdatedNotification__TurnPlanStepStatus = - | "pending" - | "inProgress" - | "completed"; -export const V2TurnPlanUpdatedNotification__TurnPlanStepStatus = Schema.Literals([ - "pending", - "inProgress", - "completed", -]); +export type V2ThreadTurnsListResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadTurnsListResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadTurnsListResponse__ImageGenerationFailure" }); -export type V2TurnStartedNotification__AbsolutePathBuf = string; -export const V2TurnStartedNotification__AbsolutePathBuf = Schema.String.annotate({ +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.", + identifier: "V2ThreadTurnsListResponse__AbsolutePathBuf", }); -export type V2TurnStartedNotification__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2TurnStartedNotification__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", - "completed", - "errored", - "shutdown", - "notFound", -]); +export type V2ThreadTurnsListResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadTurnsListResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadTurnsListResponse__TurnItemsView" }); -export type V2TurnStartedNotification__CommandExecutionStatus = - | "inProgress" +export type V2ThreadTurnsListResponse__TurnStatus = | "completed" + | "interrupted" | "failed" - | "declined"; -export const V2TurnStartedNotification__CommandExecutionStatus = Schema.Literals([ - "inProgress", + | "inProgress"; +export const V2ThreadTurnsListResponse__TurnStatus = Schema.Literals([ "completed", + "interrupted", "failed", - "declined", -]); + "inProgress", +]).annotate({ identifier: "V2ThreadTurnsListResponse__TurnStatus" }); -export type V2TurnStartedNotification__DynamicToolCallOutputContentItem = - | { readonly text: string; readonly type: "inputText" } - | { readonly imageUrl: string; readonly type: "inputImage" } - | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2TurnStartedNotification__DynamicToolCallOutputContentItem = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - type: Schema.Literal("inputText").annotate({ - title: "InputTextDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), - Schema.Struct({ - imageUrl: Schema.String, - type: Schema.Literal("inputImage").annotate({ - title: "InputImageDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), - Schema.Struct({ - audioUrl: Schema.String, - type: Schema.Literal("inputAudio").annotate({ - title: "InputAudioDynamicToolCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), - ], - { 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.", + identifier: "V2ThreadUnarchiveResponse__AbsolutePathBuf", +}); -export type V2TurnStartedNotification__DynamicToolCallStatus = - | "inProgress" - | "completed" - | "failed"; -export const V2TurnStartedNotification__DynamicToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ThreadUnarchiveResponse__GitInfo = { + readonly branch?: string | null; + readonly originUrl?: string | null; + readonly sha?: string | null; +}; +export const V2ThreadUnarchiveResponse__GitInfo = Schema.Struct({ + branch: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + originUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + sha: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__GitInfo" }); -export type V2TurnStartedNotification__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2ThreadUnarchiveResponse__ThreadHistoryMode = "legacy" | "paginated"; +export const V2ThreadUnarchiveResponse__ThreadHistoryMode = Schema.Literals([ + "legacy", + "paginated", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__ThreadHistoryMode" }); + +export type V2ThreadUnarchiveResponse__ReasoningEffort = string; +export const V2ThreadUnarchiveResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2ThreadUnarchiveResponse__ReasoningEffort", + }), +); + +export type V2ThreadUnarchiveResponse__ThreadSectionAppearance = { + readonly color?: string | null; + readonly icon?: string | null; }; -export const V2TurnStartedNotification__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2ThreadUnarchiveResponse__ThreadSectionAppearance = Schema.Struct({ + color: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + icon: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + description: "Extensible visual presentation for a custom thread section.", + identifier: "V2ThreadUnarchiveResponse__ThreadSectionAppearance", }); -export type V2TurnStartedNotification__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2TurnStartedNotification__ImageDetail = Schema.Literals([ - "auto", - "low", - "high", - "original", -]); +export type V2ThreadUnarchiveResponse__AgentPath = string; +export const V2ThreadUnarchiveResponse__AgentPath = Schema.String.annotate({ + identifier: "V2ThreadUnarchiveResponse__AgentPath", +}); -export type V2TurnStartedNotification__LegacyAppPathString = string; -export const V2TurnStartedNotification__LegacyAppPathString = Schema.String; +export type V2ThreadUnarchiveResponse__ThreadId = string; +export const V2ThreadUnarchiveResponse__ThreadId = Schema.String.annotate({ + identifier: "V2ThreadUnarchiveResponse__ThreadId", +}); -export type V2TurnStartedNotification__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; -}; -export const V2TurnStartedNotification__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +export type V2ThreadUnarchiveResponse__ThreadActiveFlag = + | "waitingOnApproval" + | "waitingOnUserInput"; +export const V2ThreadUnarchiveResponse__ThreadActiveFlag = Schema.Literals([ + "waitingOnApproval", + "waitingOnUserInput", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__ThreadActiveFlag" }); + +export type V2ThreadUnarchiveResponse__ThreadSource = string; +export const V2ThreadUnarchiveResponse__ThreadSource = Schema.String.annotate({ + identifier: "V2ThreadUnarchiveResponse__ThreadSource", }); -export type V2TurnStartedNotification__McpToolCallError = { readonly message: string }; -export const V2TurnStartedNotification__McpToolCallError = Schema.Struct({ +export type V2ThreadUnarchiveResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2ThreadUnarchiveResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__NonSteerableTurnKind" }); + +export type V2ThreadUnarchiveResponse__MisalignmentSteer = { readonly message: string }; +export const V2ThreadUnarchiveResponse__MisalignmentSteer = Schema.Struct({ message: Schema.String, -}); +}).annotate({ identifier: "V2ThreadUnarchiveResponse__MisalignmentSteer" }); -export type V2TurnStartedNotification__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; +export type V2ThreadUnarchiveResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2ThreadUnarchiveResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__ByteRange" }); + +export type V2ThreadUnarchiveResponse__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2ThreadUnarchiveResponse__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__ImageDetail" }); + +export type V2ThreadUnarchiveResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2TurnStartedNotification__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), -}); +export const V2ThreadUnarchiveResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2ThreadUnarchiveResponse__HookPromptFragment" }); -export type V2TurnStartedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2TurnStartedNotification__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); +export type V2ThreadUnarchiveResponse__AgentMessageDelivery = "async"; +export const V2ThreadUnarchiveResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2ThreadUnarchiveResponse__AgentMessageDelivery", +}); -export type V2TurnStartedNotification__MemoryCitationEntry = { +export type V2ThreadUnarchiveResponse__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; readonly note: string; readonly path: string; }; -export const V2TurnStartedNotification__MemoryCitationEntry = Schema.Struct({ +export const V2ThreadUnarchiveResponse__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2ThreadUnarchiveResponse__MemoryCitationEntry" }); -export type V2TurnStartedNotification__MessagePhase = "commentary" | "final_answer"; -export const V2TurnStartedNotification__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export type V2ThreadUnarchiveResponse__MessagePhase = "commentary" | "final_answer"; +export const V2ThreadUnarchiveResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2ThreadUnarchiveResponse__MessagePhase", }); -export type V2TurnStartedNotification__NonSteerableTurnKind = "review" | "compact"; -export const V2TurnStartedNotification__NonSteerableTurnKind = Schema.Literals([ - "review", - "compact", -]); +export type V2ThreadUnarchiveResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2ThreadUnarchiveResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2ThreadUnarchiveResponse__AsyncUserInputQuestion" }); -export type V2TurnStartedNotification__PatchApplyStatus = +export type V2ThreadUnarchiveResponse__LegacyAppPathString = string; +export const V2ThreadUnarchiveResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2ThreadUnarchiveResponse__LegacyAppPathString", +}); + +export type V2ThreadUnarchiveResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2ThreadUnarchiveResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__CommandExecutionSource" }); + +export type V2ThreadUnarchiveResponse__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2TurnStartedNotification__PatchApplyStatus = Schema.Literals([ +export const V2ThreadUnarchiveResponse__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2ThreadUnarchiveResponse__CommandExecutionStatus" }); -export type V2TurnStartedNotification__PatchChangeKind = +export type V2ThreadUnarchiveResponse__PatchChangeKind = | { readonly type: "add" } | { readonly type: "delete" } | { readonly move_path?: string | null; readonly type: "update" }; -export const V2TurnStartedNotification__PatchChangeKind = Schema.Union( +export const V2ThreadUnarchiveResponse__PatchChangeKind = Schema.Union( [ Schema.Struct({ type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), @@ -10477,63 +14536,167 @@ export const V2TurnStartedNotification__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); - -export type V2TurnStartedNotification__ReasoningEffort = string; -export const V2TurnStartedNotification__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +).annotate({ identifier: "V2ThreadUnarchiveResponse__PatchChangeKind" }); -export type V2TurnStartedNotification__SubAgentActivityKind = - | "started" - | "interacted" - | "interrupted" - | "completed"; -export const V2TurnStartedNotification__SubAgentActivityKind = Schema.Literals([ - "started", - "interacted", - "interrupted", +export type V2ThreadUnarchiveResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2ThreadUnarchiveResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", "completed", -]); + "failed", + "declined", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__PatchApplyStatus" }); -export type V2TurnStartedNotification__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2ThreadUnarchiveResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; }; -export const V2TurnStartedNotification__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.", +export const V2ThreadUnarchiveResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__McpToolCallAppContext" }); + +export type V2ThreadUnarchiveResponse__McpToolCallError = { readonly message: string }; +export const V2ThreadUnarchiveResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2ThreadUnarchiveResponse__McpToolCallError" }); + +export type V2ThreadUnarchiveResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2ThreadUnarchiveResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__McpAppDisplayMode" }); + +export type V2ThreadUnarchiveResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2ThreadUnarchiveResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__McpToolCallResult" }); + +export type V2ThreadUnarchiveResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2ThreadUnarchiveResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__McpToolCallStatus" }); + +export type V2ThreadUnarchiveResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2ThreadUnarchiveResponse__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", }), - Schema.Null, - ]), - ), -}); + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadUnarchiveResponse__DynamicToolCallOutputContentItem" }); -export type V2TurnStartedNotification__TurnStatus = +export type V2ThreadUnarchiveResponse__DynamicToolCallStatus = + | "inProgress" | "completed" + | "failed"; +export const V2ThreadUnarchiveResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__DynamicToolCallStatus" }); + +export type V2ThreadUnarchiveResponse__CollabAgentStatus = + | "pendingInit" + | "running" | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2ThreadUnarchiveResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__CollabAgentStatus" }); + +export type V2ThreadUnarchiveResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" | "failed" - | "inProgress"; -export const V2TurnStartedNotification__TurnStatus = Schema.Literals([ + | "interrupted"; +export const V2ThreadUnarchiveResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", "completed", - "interrupted", "failed", - "inProgress", -]); + "interrupted", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__CollabAgentToolCallStatus" }); -export type V2TurnStartedNotification__WebSearchAction = +export type V2ThreadUnarchiveResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2ThreadUnarchiveResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__CollabAgentTool" }); + +export type V2ThreadUnarchiveResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2ThreadUnarchiveResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__SubAgentActivityKind" }); + +export type V2ThreadUnarchiveResponse__WebSearchAction = | { readonly queries?: ReadonlyArray | null; readonly query?: string | null; @@ -10542,7 +14705,7 @@ export type V2TurnStartedNotification__WebSearchAction = | { readonly type: "openPage"; readonly url?: string | null } | { readonly pattern?: string | null; readonly type: "findInPage"; readonly url?: string | null } | { readonly type: "other" }; -export const V2TurnStartedNotification__WebSearchAction = Schema.Union( +export const V2ThreadUnarchiveResponse__WebSearchAction = Schema.Union( [ Schema.Struct({ queries: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), @@ -10563,156 +14726,280 @@ export const V2TurnStartedNotification__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); - -export type V2TurnStartParams__AbsolutePathBuf = string; -export const V2TurnStartParams__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 V2TurnStartParams__AdditionalContextKind = "untrusted" | "application"; -export const V2TurnStartParams__AdditionalContextKind = Schema.Literals([ - "untrusted", - "application", -]); - -export type V2TurnStartParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const V2TurnStartParams__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); +).annotate({ identifier: "V2ThreadUnarchiveResponse__WebSearchAction" }); -export type V2TurnStartParams__AskForApproval = - | "untrusted" - | "on-request" - | "never" - | { - readonly granular: { - readonly mcp_elicitations: boolean; - readonly request_permissions?: boolean; - readonly rules: boolean; - readonly sandbox_approval: boolean; - readonly skill_approval?: boolean; - }; - }; -export const V2TurnStartParams__AskForApproval = Schema.Union( +export type V2ThreadUnarchiveResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2ThreadUnarchiveResponse__ImageGenerationFailure = Schema.Union( [ - Schema.Literals(["untrusted", "on-request", "never"]), Schema.Struct({ - granular: Schema.Struct({ - mcp_elicitations: Schema.Boolean, - request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - rules: Schema.Boolean, - sandbox_approval: Schema.Boolean, - skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - }).annotate({ title: "GranularAskForApproval" }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadUnarchiveResponse__ImageGenerationFailure" }); -export type V2TurnStartParams__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2TurnStartParams__ImageDetail = Schema.Literals(["auto", "low", "high", "original"]); +export type V2ThreadUnarchiveResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2ThreadUnarchiveResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadUnarchiveResponse__TurnItemsView" }); -export type V2TurnStartParams__LegacyAppPathString = string; -export const V2TurnStartParams__LegacyAppPathString = Schema.String; +export type V2ThreadUnarchiveResponse__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2ThreadUnarchiveResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2ThreadUnarchiveResponse__TurnStatus" }); -export type V2TurnStartParams__ModeKind = "plan" | "default"; -export const V2TurnStartParams__ModeKind = Schema.Literals(["plan", "default"]).annotate({ - description: "Initial collaboration mode to use when the TUI starts.", -}); +export type V2ThreadUnsubscribeResponse__ThreadUnsubscribeStatus = + | "notLoaded" + | "notSubscribed" + | "unsubscribed"; +export const V2ThreadUnsubscribeResponse__ThreadUnsubscribeStatus = Schema.Literals([ + "notLoaded", + "notSubscribed", + "unsubscribed", +]).annotate({ identifier: "V2ThreadUnsubscribeResponse__ThreadUnsubscribeStatus" }); -export type V2TurnStartParams__Personality = "none" | "friendly" | "pragmatic"; -export const V2TurnStartParams__Personality = Schema.Literals(["none", "friendly", "pragmatic"]); +export type V2TurnCompletedNotification__NonSteerableTurnKind = "review" | "compact"; +export const V2TurnCompletedNotification__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2TurnCompletedNotification__NonSteerableTurnKind" }); -export type V2TurnStartParams__ReasoningEffort = string; -export const V2TurnStartParams__ReasoningEffort = Schema.String.annotate({ - description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +export type V2TurnCompletedNotification__MisalignmentSteer = { readonly message: string }; +export const V2TurnCompletedNotification__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2TurnCompletedNotification__MisalignmentSteer" }); -export type V2TurnStartParams__ReasoningSummary = "auto" | "concise" | "detailed" | "none"; -export const V2TurnStartParams__ReasoningSummary = Schema.Union( +export type V2TurnCompletedNotification__ByteRange = { + readonly end: number; + readonly start: number; +}; +export const V2TurnCompletedNotification__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2TurnCompletedNotification__ByteRange" }); + +export type V2TurnCompletedNotification__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2TurnCompletedNotification__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2TurnCompletedNotification__ImageDetail" }); + +export type V2TurnCompletedNotification__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; +}; +export const V2TurnCompletedNotification__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2TurnCompletedNotification__HookPromptFragment" }); + +export type V2TurnCompletedNotification__AgentMessageDelivery = "async"; +export const V2TurnCompletedNotification__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2TurnCompletedNotification__AgentMessageDelivery", +}); + +export type V2TurnCompletedNotification__MemoryCitationEntry = { + readonly lineEnd: number; + readonly lineStart: number; + readonly note: string; + readonly path: string; +}; +export const V2TurnCompletedNotification__MemoryCitationEntry = Schema.Struct({ + lineEnd: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + lineStart: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + note: Schema.String, + path: Schema.String, +}).annotate({ identifier: "V2TurnCompletedNotification__MemoryCitationEntry" }); + +export type V2TurnCompletedNotification__MessagePhase = "commentary" | "final_answer"; +export const V2TurnCompletedNotification__MessagePhase = Schema.Union( [ - Schema.Literals(["auto", "concise", "detailed"]), - Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), ], { mode: "oneOf" }, ).annotate({ description: - "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", + '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.', + identifier: "V2TurnCompletedNotification__MessagePhase", }); -export type V2TurnStartParams__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2TurnCompletedNotification__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; }; -export const V2TurnStartParams__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 const V2TurnCompletedNotification__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2TurnCompletedNotification__AsyncUserInputQuestion" }); -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 V2TurnCompletedNotification__LegacyAppPathString = string; +export const V2TurnCompletedNotification__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2TurnCompletedNotification__LegacyAppPathString", }); -export type V2TurnStartResponse__CollabAgentStatus = - | "pendingInit" - | "running" - | "interrupted" - | "completed" - | "errored" - | "shutdown" - | "notFound"; -export const V2TurnStartResponse__CollabAgentStatus = Schema.Literals([ - "pendingInit", - "running", - "interrupted", +export type V2TurnCompletedNotification__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2TurnCompletedNotification__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2TurnCompletedNotification__CommandExecutionSource" }); + +export type V2TurnCompletedNotification__CommandExecutionStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2TurnCompletedNotification__CommandExecutionStatus = Schema.Literals([ + "inProgress", "completed", - "errored", - "shutdown", - "notFound", -]); + "failed", + "declined", +]).annotate({ identifier: "V2TurnCompletedNotification__CommandExecutionStatus" }); -export type V2TurnStartResponse__CommandExecutionStatus = +export type V2TurnCompletedNotification__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const V2TurnCompletedNotification__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" }, +).annotate({ identifier: "V2TurnCompletedNotification__PatchChangeKind" }); + +export type V2TurnCompletedNotification__PatchApplyStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2TurnStartResponse__CommandExecutionStatus = Schema.Literals([ +export const V2TurnCompletedNotification__PatchApplyStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2TurnCompletedNotification__PatchApplyStatus" }); -export type V2TurnStartResponse__DynamicToolCallOutputContentItem = +export type V2TurnCompletedNotification__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; +}; +export const V2TurnCompletedNotification__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2TurnCompletedNotification__McpToolCallAppContext" }); + +export type V2TurnCompletedNotification__McpToolCallError = { readonly message: string }; +export const V2TurnCompletedNotification__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2TurnCompletedNotification__McpToolCallError" }); + +export type V2TurnCompletedNotification__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2TurnCompletedNotification__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2TurnCompletedNotification__McpAppDisplayMode" }); + +export type V2TurnCompletedNotification__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2TurnCompletedNotification__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2TurnCompletedNotification__McpToolCallResult" }); + +export type V2TurnCompletedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2TurnCompletedNotification__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2TurnCompletedNotification__McpToolCallStatus" }); + +export type V2TurnCompletedNotification__DynamicToolCallOutputContentItem = | { readonly text: string; readonly type: "inputText" } | { readonly imageUrl: string; readonly type: "inputImage" } | { readonly audioUrl: string; readonly type: "inputAudio" }; -export const V2TurnStartResponse__DynamicToolCallOutputContentItem = Schema.Union( +export const V2TurnCompletedNotification__DynamicToolCallOutputContentItem = Schema.Union( [ Schema.Struct({ text: Schema.String, @@ -10734,117 +15021,324 @@ export const V2TurnStartResponse__DynamicToolCallOutputContentItem = Schema.Unio }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, +).annotate({ identifier: "V2TurnCompletedNotification__DynamicToolCallOutputContentItem" }); + +export type V2TurnCompletedNotification__DynamicToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2TurnCompletedNotification__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2TurnCompletedNotification__DynamicToolCallStatus" }); + +export type V2TurnCompletedNotification__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2TurnCompletedNotification__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2TurnCompletedNotification__CollabAgentStatus" }); + +export type V2TurnCompletedNotification__ReasoningEffort = string; +export const V2TurnCompletedNotification__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2TurnCompletedNotification__ReasoningEffort", + }), ); -export type V2TurnStartResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2TurnStartResponse__DynamicToolCallStatus = Schema.Literals([ +export type V2TurnCompletedNotification__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const V2TurnCompletedNotification__CollabAgentToolCallStatus = Schema.Literals([ "inProgress", "completed", "failed", -]); + "interrupted", +]).annotate({ identifier: "V2TurnCompletedNotification__CollabAgentToolCallStatus" }); -export type V2TurnStartResponse__HookPromptFragment = { - readonly hookRunId: string; - readonly text: string; +export type V2TurnCompletedNotification__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2TurnCompletedNotification__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2TurnCompletedNotification__CollabAgentTool" }); + +export type V2TurnCompletedNotification__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2TurnCompletedNotification__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2TurnCompletedNotification__SubAgentActivityKind" }); + +export type V2TurnCompletedNotification__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 V2TurnCompletedNotification__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: "SearchWebSearchActionType" }), + }).annotate({ title: "SearchWebSearchAction" }), + Schema.Struct({ + 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" }, +).annotate({ identifier: "V2TurnCompletedNotification__WebSearchAction" }); + +export type V2TurnCompletedNotification__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; }; -export const V2TurnStartResponse__HookPromptFragment = Schema.Struct({ - hookRunId: Schema.String, - text: Schema.String, +export const V2TurnCompletedNotification__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", + }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnCompletedNotification__ImageGenerationFailure" }); + +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.", + identifier: "V2TurnCompletedNotification__AbsolutePathBuf", }); -export type V2TurnStartResponse__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2TurnStartResponse__ImageDetail = Schema.Literals([ +export type V2TurnCompletedNotification__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2TurnCompletedNotification__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnCompletedNotification__TurnItemsView" }); + +export type V2TurnCompletedNotification__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2TurnCompletedNotification__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2TurnCompletedNotification__TurnStatus" }); + +export type V2TurnPlanUpdatedNotification__TurnPlanStepStatus = + | "pending" + | "inProgress" + | "completed"; +export const V2TurnPlanUpdatedNotification__TurnPlanStepStatus = Schema.Literals([ + "pending", + "inProgress", + "completed", +]).annotate({ identifier: "V2TurnPlanUpdatedNotification__TurnPlanStepStatus" }); + +export type V2TurnStartedNotification__NonSteerableTurnKind = "review" | "compact"; +export const V2TurnStartedNotification__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2TurnStartedNotification__NonSteerableTurnKind" }); + +export type V2TurnStartedNotification__MisalignmentSteer = { readonly message: string }; +export const V2TurnStartedNotification__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2TurnStartedNotification__MisalignmentSteer" }); + +export type V2TurnStartedNotification__ByteRange = { readonly end: number; readonly start: number }; +export const V2TurnStartedNotification__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2TurnStartedNotification__ByteRange" }); + +export type V2TurnStartedNotification__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2TurnStartedNotification__ImageDetail = Schema.Literals([ "auto", "low", "high", "original", -]); - -export type V2TurnStartResponse__LegacyAppPathString = string; -export const V2TurnStartResponse__LegacyAppPathString = Schema.String; +]).annotate({ identifier: "V2TurnStartedNotification__ImageDetail" }); -export type V2TurnStartResponse__McpToolCallAppContext = { - readonly actionName?: string | null; - readonly appName?: string | null; - readonly connectorId: string; - readonly linkId?: string | null; - readonly resourceUri?: string | null; +export type V2TurnStartedNotification__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const V2TurnStartResponse__McpToolCallAppContext = Schema.Struct({ - actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - connectorId: Schema.String, - linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2TurnStartResponse__McpToolCallError = { readonly message: string }; -export const V2TurnStartResponse__McpToolCallError = Schema.Struct({ message: Schema.String }); +export const V2TurnStartedNotification__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2TurnStartedNotification__HookPromptFragment" }); -export type V2TurnStartResponse__McpToolCallResult = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; - readonly structuredContent?: unknown; -}; -export const V2TurnStartResponse__McpToolCallResult = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), - structuredContent: Schema.optionalKey(Schema.Unknown), +export type V2TurnStartedNotification__AgentMessageDelivery = "async"; +export const V2TurnStartedNotification__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2TurnStartedNotification__AgentMessageDelivery", }); -export type V2TurnStartResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; -export const V2TurnStartResponse__McpToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", -]); - -export type V2TurnStartResponse__MemoryCitationEntry = { +export type V2TurnStartedNotification__MemoryCitationEntry = { readonly lineEnd: number; readonly lineStart: number; readonly note: string; readonly path: string; }; -export const V2TurnStartResponse__MemoryCitationEntry = Schema.Struct({ +export const V2TurnStartedNotification__MemoryCitationEntry = Schema.Struct({ lineEnd: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), lineStart: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), note: Schema.String, path: Schema.String, -}); +}).annotate({ identifier: "V2TurnStartedNotification__MemoryCitationEntry" }); -export type V2TurnStartResponse__MessagePhase = "commentary" | "final_answer"; -export const V2TurnStartResponse__MessagePhase = Schema.Literals([ - "commentary", - "final_answer", -]).annotate({ +export type V2TurnStartedNotification__MessagePhase = "commentary" | "final_answer"; +export const V2TurnStartedNotification__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2TurnStartedNotification__MessagePhase", }); -export type V2TurnStartResponse__NonSteerableTurnKind = "review" | "compact"; -export const V2TurnStartResponse__NonSteerableTurnKind = Schema.Literals(["review", "compact"]); +export type V2TurnStartedNotification__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; +}; +export const V2TurnStartedNotification__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2TurnStartedNotification__AsyncUserInputQuestion" }); -export type V2TurnStartResponse__PatchApplyStatus = +export type V2TurnStartedNotification__LegacyAppPathString = string; +export const V2TurnStartedNotification__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2TurnStartedNotification__LegacyAppPathString", +}); + +export type V2TurnStartedNotification__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2TurnStartedNotification__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2TurnStartedNotification__CommandExecutionSource" }); + +export type V2TurnStartedNotification__CommandExecutionStatus = | "inProgress" | "completed" | "failed" | "declined"; -export const V2TurnStartResponse__PatchApplyStatus = Schema.Literals([ +export const V2TurnStartedNotification__CommandExecutionStatus = Schema.Literals([ "inProgress", "completed", "failed", "declined", -]); +]).annotate({ identifier: "V2TurnStartedNotification__CommandExecutionStatus" }); -export type V2TurnStartResponse__PatchChangeKind = +export type V2TurnStartedNotification__PatchChangeKind = | { readonly type: "add" } | { readonly type: "delete" } | { readonly move_path?: string | null; readonly type: "update" }; -export const V2TurnStartResponse__PatchChangeKind = Schema.Union( +export const V2TurnStartedNotification__PatchChangeKind = Schema.Union( [ Schema.Struct({ type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), @@ -10858,59 +15352,177 @@ export const V2TurnStartResponse__PatchChangeKind = Schema.Union( }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnStartedNotification__PatchChangeKind" }); -export type V2TurnStartResponse__ReasoningEffort = string; -export const V2TurnStartResponse__ReasoningEffort = Schema.String.annotate({ +export type V2TurnStartedNotification__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2TurnStartedNotification__PatchApplyStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]).annotate({ identifier: "V2TurnStartedNotification__PatchApplyStatus" }); + +export type V2TurnStartedNotification__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; +}; +export const V2TurnStartedNotification__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2TurnStartedNotification__McpToolCallAppContext" }); + +export type V2TurnStartedNotification__McpToolCallError = { readonly message: string }; +export const V2TurnStartedNotification__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2TurnStartedNotification__McpToolCallError" }); + +export type V2TurnStartedNotification__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2TurnStartedNotification__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2TurnStartedNotification__McpAppDisplayMode" }); + +export type V2TurnStartedNotification__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2TurnStartedNotification__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2TurnStartedNotification__McpToolCallResult" }); + +export type V2TurnStartedNotification__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2TurnStartedNotification__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2TurnStartedNotification__McpToolCallStatus" }); + +export type V2TurnStartedNotification__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2TurnStartedNotification__DynamicToolCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), + Schema.Struct({ + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), + Schema.Struct({ + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnStartedNotification__DynamicToolCallOutputContentItem" }); + +export type V2TurnStartedNotification__DynamicToolCallStatus = + | "inProgress" + | "completed" + | "failed"; +export const V2TurnStartedNotification__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2TurnStartedNotification__DynamicToolCallStatus" }); + +export type V2TurnStartedNotification__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2TurnStartedNotification__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2TurnStartedNotification__CollabAgentStatus" }); + +export type V2TurnStartedNotification__ReasoningEffort = string; +export const V2TurnStartedNotification__ReasoningEffort = Schema.String.annotate({ description: "A non-empty reasoning effort value advertised by the model.", -}).check(Schema.isMinLength(1)); +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2TurnStartedNotification__ReasoningEffort", + }), +); -export type V2TurnStartResponse__SubAgentActivityKind = +export type V2TurnStartedNotification__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const V2TurnStartedNotification__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "interrupted", +]).annotate({ identifier: "V2TurnStartedNotification__CollabAgentToolCallStatus" }); + +export type V2TurnStartedNotification__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2TurnStartedNotification__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2TurnStartedNotification__CollabAgentTool" }); + +export type V2TurnStartedNotification__SubAgentActivityKind = | "started" | "interacted" | "interrupted" | "completed"; -export const V2TurnStartResponse__SubAgentActivityKind = Schema.Literals([ +export const V2TurnStartedNotification__SubAgentActivityKind = Schema.Literals([ "started", "interacted", "interrupted", "completed", -]); - -export type V2TurnStartResponse__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; -}; -export const V2TurnStartResponse__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 V2TurnStartResponse__TurnStatus = "completed" | "interrupted" | "failed" | "inProgress"; -export const V2TurnStartResponse__TurnStatus = Schema.Literals([ - "completed", - "interrupted", - "failed", - "inProgress", -]); +]).annotate({ identifier: "V2TurnStartedNotification__SubAgentActivityKind" }); -export type V2TurnStartResponse__WebSearchAction = +export type V2TurnStartedNotification__WebSearchAction = | { readonly queries?: ReadonlyArray | null; readonly query?: string | null; @@ -10919,7 +15531,7 @@ export type V2TurnStartResponse__WebSearchAction = | { readonly type: "openPage"; readonly url?: string | null } | { readonly pattern?: string | null; readonly type: "findInPage"; readonly url?: string | null } | { readonly type: "other" }; -export const V2TurnStartResponse__WebSearchAction = Schema.Union( +export const V2TurnStartedNotification__WebSearchAction = Schema.Union( [ Schema.Struct({ queries: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), @@ -10940,498 +15552,678 @@ export const V2TurnStartResponse__WebSearchAction = Schema.Union( }).annotate({ title: "OtherWebSearchAction" }), ], { mode: "oneOf" }, -); - -export type V2TurnSteerParams__AdditionalContextKind = "untrusted" | "application"; -export const V2TurnSteerParams__AdditionalContextKind = Schema.Literals([ - "untrusted", - "application", -]); +).annotate({ identifier: "V2TurnStartedNotification__WebSearchAction" }); -export type V2TurnSteerParams__ImageDetail = "auto" | "low" | "high" | "original"; -export const V2TurnSteerParams__ImageDetail = Schema.Literals(["auto", "low", "high", "original"]); - -export type V2TurnSteerParams__TextElement = { - readonly byteRange: { readonly end: number; readonly start: number }; - readonly placeholder?: string | null; +export type V2TurnStartedNotification__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; }; -export const V2TurnSteerParams__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.", +export const V2TurnStartedNotification__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - Schema.Null, - ]), - ), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnStartedNotification__ImageGenerationFailure" }); + +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.", + identifier: "V2TurnStartedNotification__AbsolutePathBuf", }); -export type V2WindowsSandboxReadinessResponse__WindowsSandboxReadiness = - | "ready" - | "notConfigured" - | "updateRequired"; -export const V2WindowsSandboxReadinessResponse__WindowsSandboxReadiness = Schema.Literals([ - "ready", - "notConfigured", - "updateRequired", -]); +export type V2TurnStartedNotification__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2TurnStartedNotification__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ + description: + "`items` contains every ThreadItem available from persisted app-server history for this turn.", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnStartedNotification__TurnItemsView" }); -export type V2WindowsSandboxSetupCompletedNotification__WindowsSandboxSetupMode = - | "elevated" - | "unelevated"; -export const V2WindowsSandboxSetupCompletedNotification__WindowsSandboxSetupMode = Schema.Literals([ - "elevated", - "unelevated", -]); +export type V2TurnStartedNotification__TurnStatus = + | "completed" + | "interrupted" + | "failed" + | "inProgress"; +export const V2TurnStartedNotification__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2TurnStartedNotification__TurnStatus" }); -export type V2WindowsSandboxSetupStartParams__AbsolutePathBuf = string; -export const V2WindowsSandboxSetupStartParams__AbsolutePathBuf = Schema.String.annotate({ +export type V2TurnStartParams__AskForApproval = + | "untrusted" + | "on-request" + | "never" + | { + readonly granular: { + readonly mcp_elicitations: boolean; + readonly request_permissions?: boolean; + readonly rules: boolean; + readonly sandbox_approval: boolean; + readonly skill_approval?: boolean; + }; + }; +export const V2TurnStartParams__AskForApproval = Schema.Union( + [ + Schema.Literals(["untrusted", "on-request", "never"]), + Schema.Struct({ + granular: Schema.Struct({ + mcp_elicitations: Schema.Boolean, + request_permissions: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + rules: Schema.Boolean, + sandbox_approval: Schema.Boolean, + skill_approval: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + }), + }).annotate({ title: "GranularAskForApproval" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnStartParams__AskForApproval" }); + +export type V2TurnStartParams__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; +export const V2TurnStartParams__ApprovalsReviewer = Schema.Literals([ + "user", + "auto_review", + "guardian_subagent", +]).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.", + "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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", + identifier: "V2TurnStartParams__ApprovalsReviewer", }); -export type V2WindowsSandboxSetupStartParams__WindowsSandboxSetupMode = "elevated" | "unelevated"; -export const V2WindowsSandboxSetupStartParams__WindowsSandboxSetupMode = Schema.Literals([ - "elevated", - "unelevated", -]); +export type V2TurnStartParams__ReasoningEffort = string; +export const V2TurnStartParams__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2TurnStartParams__ReasoningEffort", + }), +); -export type ApplyPatchApprovalResponse__NetworkPolicyAmendment = { - readonly action: ApplyPatchApprovalResponse__NetworkPolicyRuleAction; - readonly host: string; -}; -export const ApplyPatchApprovalResponse__NetworkPolicyAmendment = Schema.Struct({ - action: ApplyPatchApprovalResponse__NetworkPolicyRuleAction, - host: Schema.String, -}); +export type V2TurnStartParams__ByteRange = { readonly end: number; readonly start: number }; +export const V2TurnStartParams__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2TurnStartParams__ByteRange" }); -export type ClientRequest__PluginInstallParams = { - readonly marketplacePath?: ClientRequest__AbsolutePathBuf | null; - readonly pluginName: string; - readonly remoteMarketplaceName?: string | null; -}; -export const ClientRequest__PluginInstallParams = Schema.Struct({ - marketplacePath: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), - pluginName: Schema.String, - remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); +export type V2TurnStartParams__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2TurnStartParams__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2TurnStartParams__ImageDetail" }); -export type ClientRequest__PluginInstalledParams = { - readonly cwds?: ReadonlyArray | null; - readonly installSuggestionPluginNames?: ReadonlyArray | null; -}; -export const ClientRequest__PluginInstalledParams = Schema.Struct({ - cwds: Schema.optionalKey( - Schema.Union([ - Schema.Array(ClientRequest__AbsolutePathBuf).annotate({ - description: "Optional working directories used to discover repo marketplaces.", - }), - Schema.Null, - ]), - ), - installSuggestionPluginNames: Schema.optionalKey( - Schema.Union([ - Schema.Array(Schema.String).annotate({ - description: - "Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.", - }), - Schema.Null, - ]), - ), +export type V2TurnStartParams__Personality = "none" | "friendly" | "pragmatic"; +export const V2TurnStartParams__Personality = Schema.Literals([ + "none", + "friendly", + "pragmatic", +]).annotate({ + description: "Deprecated: `friendly` and `pragmatic` no longer select a style.", + identifier: "V2TurnStartParams__Personality", }); -export type ClientRequest__PluginReadParams = { - readonly marketplacePath?: ClientRequest__AbsolutePathBuf | null; - readonly pluginName: string; - readonly remoteMarketplaceName?: string | null; -}; -export const ClientRequest__PluginReadParams = Schema.Struct({ - marketplacePath: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), - pluginName: Schema.String, - remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +export type V2TurnStartParams__NetworkAccess = "restricted" | "enabled"; +export const V2TurnStartParams__NetworkAccess = Schema.Literals(["restricted", "enabled"]).annotate( + { identifier: "V2TurnStartParams__NetworkAccess" }, +); + +export type V2TurnStartParams__AbsolutePathBuf = string; +export const V2TurnStartParams__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.", + identifier: "V2TurnStartParams__AbsolutePathBuf", }); -export type ClientRequest__SandboxPolicy = - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const ClientRequest__SandboxPolicy = Schema.Union( +export type V2TurnStartParams__ReasoningSummary = "auto" | "concise" | "detailed" | "none"; +export const V2TurnStartParams__ReasoningSummary = Schema.Union( [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(ClientRequest__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + Schema.Literals(["auto", "concise", "detailed"]), + Schema.Literal("none").annotate({ description: "Option to disable reasoning summaries." }), ], { mode: "oneOf" }, -); +).annotate({ + description: + "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", + identifier: "V2TurnStartParams__ReasoningSummary", +}); -export type ClientRequest__SkillsConfigWriteParams = { - readonly enabled: boolean; - readonly name?: string | null; - readonly path?: ClientRequest__AbsolutePathBuf | null; +export type V2TurnStartParams__AdditionalContextKind = "untrusted" | "application"; +export const V2TurnStartParams__AdditionalContextKind = Schema.Literals([ + "untrusted", + "application", +]).annotate({ identifier: "V2TurnStartParams__AdditionalContextKind" }); + +export type V2TurnStartParams__ModeKind = "plan" | "default"; +export const V2TurnStartParams__ModeKind = Schema.Literals(["plan", "default"]).annotate({ + description: "Initial collaboration mode to use when the TUI starts.", + identifier: "V2TurnStartParams__ModeKind", +}); + +export type V2TurnStartParams__LegacyAppPathString = string; +export const V2TurnStartParams__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2TurnStartParams__LegacyAppPathString", +}); + +export type V2TurnStartResponse__NonSteerableTurnKind = "review" | "compact"; +export const V2TurnStartResponse__NonSteerableTurnKind = Schema.Literals([ + "review", + "compact", +]).annotate({ identifier: "V2TurnStartResponse__NonSteerableTurnKind" }); + +export type V2TurnStartResponse__MisalignmentSteer = { readonly message: string }; +export const V2TurnStartResponse__MisalignmentSteer = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2TurnStartResponse__MisalignmentSteer" }); + +export type V2TurnStartResponse__ByteRange = { readonly end: number; readonly start: number }; +export const V2TurnStartResponse__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2TurnStartResponse__ByteRange" }); + +export type V2TurnStartResponse__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2TurnStartResponse__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2TurnStartResponse__ImageDetail" }); + +export type V2TurnStartResponse__HookPromptFragment = { + readonly hookRunId: string; + readonly text: string; }; -export const ClientRequest__SkillsConfigWriteParams = Schema.Struct({ - enabled: Schema.Boolean, - name: Schema.optionalKey( - Schema.Union([Schema.String.annotate({ description: "Name-based selector." }), Schema.Null]), - ), - path: Schema.optionalKey( - Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Path-based selector.", - }), - ), +export const V2TurnStartResponse__HookPromptFragment = Schema.Struct({ + hookRunId: Schema.String, + text: Schema.String, +}).annotate({ identifier: "V2TurnStartResponse__HookPromptFragment" }); + +export type V2TurnStartResponse__AgentMessageDelivery = "async"; +export const V2TurnStartResponse__AgentMessageDelivery = Schema.Literal("async").annotate({ + identifier: "V2TurnStartResponse__AgentMessageDelivery", }); -export type ClientRequest__SkillsExtraRootsSetParams = { - readonly extraRoots: ReadonlyArray; +export type V2TurnStartResponse__MemoryCitationEntry = { + readonly lineEnd: number; + readonly lineStart: number; + readonly note: string; + readonly path: string; }; -export const ClientRequest__SkillsExtraRootsSetParams = Schema.Struct({ - extraRoots: Schema.Array(ClientRequest__AbsolutePathBuf), +export const V2TurnStartResponse__MemoryCitationEntry = Schema.Struct({ + lineEnd: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + lineStart: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + note: Schema.String, + path: Schema.String, +}).annotate({ identifier: "V2TurnStartResponse__MemoryCitationEntry" }); + +export type V2TurnStartResponse__MessagePhase = "commentary" | "final_answer"; +export const V2TurnStartResponse__MessagePhase = Schema.Union( + [ + Schema.Literal("commentary").annotate({ + description: + "Mid-turn assistant text (for example preamble/progress narration).\n\nAdditional tool calls or assistant output may follow before turn completion.", + }), + Schema.Literal("final_answer").annotate({ + description: "The assistant's terminal answer text for the current turn.", + }), + ], + { mode: "oneOf" }, +).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.', + identifier: "V2TurnStartResponse__MessagePhase", }); -export type ClientRequest__SendAddCreditsNudgeEmailParams = { - readonly creditType: ClientRequest__AddCreditsNudgeCreditType; +export type V2TurnStartResponse__AsyncUserInputQuestion = { + readonly options?: ReadonlyArray | null; + readonly title: string; }; -export const ClientRequest__SendAddCreditsNudgeEmailParams = Schema.Struct({ - creditType: ClientRequest__AddCreditsNudgeCreditType, +export const V2TurnStartResponse__AsyncUserInputQuestion = Schema.Struct({ + options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.String, +}).annotate({ identifier: "V2TurnStartResponse__AsyncUserInputQuestion" }); + +export type V2TurnStartResponse__LegacyAppPathString = string; +export const V2TurnStartResponse__LegacyAppPathString = Schema.String.annotate({ + identifier: "V2TurnStartResponse__LegacyAppPathString", }); -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 audio_url: string; readonly type: "input_audio" } - | { readonly text: string; readonly type: "output_text" }; -export const ClientRequest__ContentItem = Schema.Union( +export type V2TurnStartResponse__CommandExecutionSource = + | "agent" + | "userShell" + | "unifiedExecStartup" + | "unifiedExecInteraction"; +export const V2TurnStartResponse__CommandExecutionSource = Schema.Literals([ + "agent", + "userShell", + "unifiedExecStartup", + "unifiedExecInteraction", +]).annotate({ identifier: "V2TurnStartResponse__CommandExecutionSource" }); + +export type V2TurnStartResponse__CommandExecutionStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2TurnStartResponse__CommandExecutionStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]).annotate({ identifier: "V2TurnStartResponse__CommandExecutionStatus" }); + +export type V2TurnStartResponse__PatchChangeKind = + | { readonly type: "add" } + | { readonly type: "delete" } + | { readonly move_path?: string | null; readonly type: "update" }; +export const V2TurnStartResponse__PatchChangeKind = 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" }), + type: Schema.Literal("add").annotate({ title: "AddPatchChangeKindType" }), + }).annotate({ title: "AddPatchChangeKind" }), Schema.Struct({ - audio_url: Schema.String, - type: Schema.Literal("input_audio").annotate({ title: "InputAudioContentItemType" }), - }).annotate({ title: "InputAudioContentItem" }), + type: Schema.Literal("delete").annotate({ title: "DeletePatchChangeKindType" }), + }).annotate({ title: "DeletePatchChangeKind" }), Schema.Struct({ - text: Schema.String, - type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), - }).annotate({ title: "OutputTextContentItem" }), + move_path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("update").annotate({ title: "UpdatePatchChangeKindType" }), + }).annotate({ title: "UpdatePatchChangeKind" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnStartResponse__PatchChangeKind" }); -export type ClientRequest__FunctionCallOutputContentItem = - | { readonly text: string; readonly type: "input_text" } - | { - readonly detail?: ClientRequest__ImageDetail | null; - readonly image_url: string; - readonly type: "input_image"; - } - | { readonly audio_url: string; readonly type: "input_audio" } - | { readonly encrypted_content: string; readonly type: "encrypted_content" }; -export const ClientRequest__FunctionCallOutputContentItem = Schema.Union( +export type V2TurnStartResponse__PatchApplyStatus = + | "inProgress" + | "completed" + | "failed" + | "declined"; +export const V2TurnStartResponse__PatchApplyStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "declined", +]).annotate({ identifier: "V2TurnStartResponse__PatchApplyStatus" }); + +export type V2TurnStartResponse__McpToolCallAppContext = { + readonly actionName?: string | null; + readonly appName?: string | null; + readonly connectorId: string; + readonly linkId?: string | null; + readonly resourceUri?: string | null; +}; +export const V2TurnStartResponse__McpToolCallAppContext = Schema.Struct({ + actionName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + appName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + connectorId: Schema.String, + linkId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceUri: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2TurnStartResponse__McpToolCallAppContext" }); + +export type V2TurnStartResponse__McpToolCallError = { readonly message: string }; +export const V2TurnStartResponse__McpToolCallError = Schema.Struct({ + message: Schema.String, +}).annotate({ identifier: "V2TurnStartResponse__McpToolCallError" }); + +export type V2TurnStartResponse__McpAppDisplayMode = "inline" | "fullscreen"; +export const V2TurnStartResponse__McpAppDisplayMode = Schema.Literals([ + "inline", + "fullscreen", +]).annotate({ identifier: "V2TurnStartResponse__McpAppDisplayMode" }); + +export type V2TurnStartResponse__McpToolCallResult = { + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; + readonly structuredContent?: Schema.Json; +}; +export const V2TurnStartResponse__McpToolCallResult = Schema.Struct({ + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), +}).annotate({ identifier: "V2TurnStartResponse__McpToolCallResult" }); + +export type V2TurnStartResponse__McpToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2TurnStartResponse__McpToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2TurnStartResponse__McpToolCallStatus" }); + +export type V2TurnStartResponse__DynamicToolCallOutputContentItem = + | { readonly text: string; readonly type: "inputText" } + | { readonly imageUrl: string; readonly type: "inputImage" } + | { readonly audioUrl: string; readonly type: "inputAudio" }; +export const V2TurnStartResponse__DynamicToolCallOutputContentItem = Schema.Union( [ Schema.Struct({ text: Schema.String, - type: Schema.Literal("input_text").annotate({ - title: "InputTextFunctionCallOutputContentItemType", - }), - }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), - image_url: Schema.String, - type: Schema.Literal("input_image").annotate({ - title: "InputImageFunctionCallOutputContentItemType", + type: Schema.Literal("inputText").annotate({ + title: "InputTextDynamicToolCallOutputContentItemType", }), - }).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + }).annotate({ title: "InputTextDynamicToolCallOutputContentItem" }), Schema.Struct({ - audio_url: Schema.String, - type: Schema.Literal("input_audio").annotate({ - title: "InputAudioFunctionCallOutputContentItemType", + imageUrl: Schema.String, + type: Schema.Literal("inputImage").annotate({ + title: "InputImageDynamicToolCallOutputContentItemType", }), - }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + }).annotate({ title: "InputImageDynamicToolCallOutputContentItem" }), Schema.Struct({ - encrypted_content: Schema.String, - type: Schema.Literal("encrypted_content").annotate({ - title: "EncryptedContentFunctionCallOutputContentItemType", + audioUrl: Schema.String, + type: Schema.Literal("inputAudio").annotate({ + title: "InputAudioDynamicToolCallOutputContentItemType", }), - }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + }).annotate({ title: "InputAudioDynamicToolCallOutputContentItem" }), ], { mode: "oneOf" }, -).annotate({ - description: - "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.", -}); +).annotate({ identifier: "V2TurnStartResponse__DynamicToolCallOutputContentItem" }); -export type ClientRequest__InitializeParams = { - readonly capabilities?: ClientRequest__InitializeCapabilities | null; - readonly clientInfo: ClientRequest__ClientInfo; -}; -export const ClientRequest__InitializeParams = Schema.Struct({ - capabilities: Schema.optionalKey( - Schema.Union([ClientRequest__InitializeCapabilities, Schema.Null]), - ), - clientInfo: ClientRequest__ClientInfo, -}); +export type V2TurnStartResponse__DynamicToolCallStatus = "inProgress" | "completed" | "failed"; +export const V2TurnStartResponse__DynamicToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", +]).annotate({ identifier: "V2TurnStartResponse__DynamicToolCallStatus" }); -export type ClientRequest__LoginAccountParams = - | { readonly apiKey: string; readonly type: "apiKey" } - | { - readonly appBrand?: ClientRequest__LoginAppBrand | null; - readonly codexStreamlinedLogin?: boolean; - readonly type: "chatgpt"; - readonly useHostedLoginSuccessPage?: boolean; - } - | { readonly type: "chatgptDeviceCode" } - | { - readonly accessToken: string; - readonly chatgptAccountId: string; - readonly chatgptPlanType?: string | null; - readonly type: "chatgptAuthTokens"; - } - | { readonly apiKey: string; readonly region: string; readonly type: "amazonBedrock" }; -export const ClientRequest__LoginAccountParams = Schema.Union( - [ - Schema.Struct({ - apiKey: Schema.String, - type: Schema.Literal("apiKey").annotate({ title: "ApiKeyLoginAccountParamsType" }), - }).annotate({ title: "ApiKeyLoginAccountParams" }), - Schema.Struct({ - appBrand: Schema.optionalKey(Schema.Union([ClientRequest__LoginAppBrand, Schema.Null])), - codexStreamlinedLogin: Schema.optionalKey(Schema.Boolean), - type: Schema.Literal("chatgpt").annotate({ title: "ChatgptLoginAccountParamsType" }), - useHostedLoginSuccessPage: Schema.optionalKey(Schema.Boolean), - }).annotate({ title: "ChatgptLoginAccountParams" }), - Schema.Struct({ - type: Schema.Literal("chatgptDeviceCode").annotate({ - title: "ChatgptDeviceCodeLoginAccountParamsType", - }), - }).annotate({ title: "ChatgptDeviceCodeLoginAccountParams" }), - Schema.Struct({ - accessToken: Schema.String.annotate({ - description: - "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.", - }), - chatgptAccountId: Schema.String.annotate({ - description: "Workspace/account identifier supplied by the client.", - }), - chatgptPlanType: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.", - }), +export type V2TurnStartResponse__CollabAgentStatus = + | "pendingInit" + | "running" + | "interrupted" + | "completed" + | "errored" + | "shutdown" + | "notFound"; +export const V2TurnStartResponse__CollabAgentStatus = Schema.Literals([ + "pendingInit", + "running", + "interrupted", + "completed", + "errored", + "shutdown", + "notFound", +]).annotate({ identifier: "V2TurnStartResponse__CollabAgentStatus" }); + +export type V2TurnStartResponse__ReasoningEffort = string; +export const V2TurnStartResponse__ReasoningEffort = Schema.String.annotate({ + description: "A non-empty reasoning effort value advertised by the model.", +}).check( + Schema.isMinLength(1).annotate({ + expected: "a value with a length of at least 1", + identifier: "V2TurnStartResponse__ReasoningEffort", + }), +); + +export type V2TurnStartResponse__CollabAgentToolCallStatus = + | "inProgress" + | "completed" + | "failed" + | "interrupted"; +export const V2TurnStartResponse__CollabAgentToolCallStatus = Schema.Literals([ + "inProgress", + "completed", + "failed", + "interrupted", +]).annotate({ identifier: "V2TurnStartResponse__CollabAgentToolCallStatus" }); + +export type V2TurnStartResponse__CollabAgentTool = + | "spawnAgent" + | "sendInput" + | "resumeAgent" + | "wait" + | "closeAgent" + | "sendMessage" + | "followupTask" + | "interruptAgent" + | "listAgents"; +export const V2TurnStartResponse__CollabAgentTool = Schema.Literals([ + "spawnAgent", + "sendInput", + "resumeAgent", + "wait", + "closeAgent", + "sendMessage", + "followupTask", + "interruptAgent", + "listAgents", +]).annotate({ identifier: "V2TurnStartResponse__CollabAgentTool" }); + +export type V2TurnStartResponse__SubAgentActivityKind = + | "started" + | "interacted" + | "interrupted" + | "completed"; +export const V2TurnStartResponse__SubAgentActivityKind = Schema.Literals([ + "started", + "interacted", + "interrupted", + "completed", +]).annotate({ identifier: "V2TurnStartResponse__SubAgentActivityKind" }); + +export type V2TurnStartResponse__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 V2TurnStartResponse__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: "SearchWebSearchActionType" }), + }).annotate({ title: "SearchWebSearchAction" }), + Schema.Struct({ + 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" }, +).annotate({ identifier: "V2TurnStartResponse__WebSearchAction" }); + +export type V2TurnStartResponse__ImageGenerationFailure = { + readonly limitId: string; + readonly resetsAt?: number | null; + readonly type: "usageLimitExceeded"; +}; +export const V2TurnStartResponse__ImageGenerationFailure = Schema.Union( + [ + Schema.Struct({ + limitId: Schema.String, + resetsAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), Schema.Null, ]), ), - type: Schema.Literal("chatgptAuthTokens").annotate({ - title: "ChatgptAuthTokensLoginAccountParamsType", - }), - }).annotate({ - title: "ChatgptAuthTokensLoginAccountParams", - description: - "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", - }), - Schema.Struct({ - apiKey: Schema.String, - region: Schema.String, - type: Schema.Literal("amazonBedrock").annotate({ - title: "AmazonBedrockLoginAccountParamsType", + type: Schema.Literal("usageLimitExceeded").annotate({ + title: "UsageLimitExceededImageGenerationFailureType", }), - }).annotate({ - title: "AmazonBedrockLoginAccountParams", - description: "[UNSTABLE] Managed Amazon Bedrock login is experimental.", - }), + }).annotate({ title: "UsageLimitExceededImageGenerationFailure" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnStartResponse__ImageGenerationFailure" }); -export type ClientRequest__ListMcpServerStatusParams = { - readonly cursor?: string | null; - readonly detail?: ClientRequest__McpServerStatusDetail | null; - readonly limit?: number | null; - readonly threadId?: string | null; -}; -export const ClientRequest__ListMcpServerStatusParams = Schema.Struct({ - cursor: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Opaque pagination cursor returned by a previous call.", - }), - Schema.Null, - ]), - ), - detail: Schema.optionalKey( - Schema.Union([ClientRequest__McpServerStatusDetail, Schema.Null]).annotate({ +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.", + identifier: "V2TurnStartResponse__AbsolutePathBuf", +}); + +export type V2TurnStartResponse__TurnItemsView = "notLoaded" | "summary" | "full"; +export const V2TurnStartResponse__TurnItemsView = Schema.Union( + [ + Schema.Literal("notLoaded").annotate({ + description: "`items` was not loaded for this turn. The field is intentionally empty.", + }), + Schema.Literal("summary").annotate({ + description: "`items` contains only a display summary for this turn.", + }), + Schema.Literal("full").annotate({ description: - "Controls how much MCP inventory data to fetch for each server. Defaults to `Full` when omitted.", + "`items` contains every ThreadItem available from persisted app-server history for this turn.", }), - ), - limit: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ - description: "Optional page size; defaults to a server-defined value.", - format: "uint32", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnStartResponse__TurnItemsView" }); -export type ClientRequest__ConfigEdit = { - readonly keyPath: string; - readonly mergeStrategy: ClientRequest__MergeStrategy; - readonly value: unknown; -}; -export const ClientRequest__ConfigEdit = Schema.Struct({ - keyPath: Schema.String, - mergeStrategy: ClientRequest__MergeStrategy, - value: Schema.Unknown, -}); +export type V2TurnStartResponse__TurnStatus = "completed" | "interrupted" | "failed" | "inProgress"; +export const V2TurnStartResponse__TurnStatus = Schema.Literals([ + "completed", + "interrupted", + "failed", + "inProgress", +]).annotate({ identifier: "V2TurnStartResponse__TurnStatus" }); -export type ClientRequest__ConfigValueWriteParams = { - readonly expectedVersion?: string | null; - readonly filePath?: string | null; - readonly keyPath: string; - readonly mergeStrategy: ClientRequest__MergeStrategy; - readonly value: unknown; -}; -export const ClientRequest__ConfigValueWriteParams = Schema.Struct({ - expectedVersion: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - filePath: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Path to the config file to write; defaults to the user's `config.toml` when omitted.", - }), - Schema.Null, - ]), - ), - keyPath: Schema.String, - mergeStrategy: ClientRequest__MergeStrategy, - value: Schema.Unknown, -}); +export type V2TurnSteerParams__ByteRange = { readonly end: number; readonly start: number }; +export const V2TurnSteerParams__ByteRange = Schema.Struct({ + end: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), + start: Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ identifier: "V2TurnSteerParams__ByteRange" }); -export type ClientRequest__PluginListParams = { - readonly cwds?: ReadonlyArray | null; - readonly marketplaceKinds?: ReadonlyArray | null; -}; -export const ClientRequest__PluginListParams = Schema.Struct({ - cwds: Schema.optionalKey( - Schema.Union([ - Schema.Array(ClientRequest__AbsolutePathBuf).annotate({ - description: - "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces and the official curated marketplace are considered.", - }), - Schema.Null, - ]), - ), - marketplaceKinds: Schema.optionalKey( - Schema.Union([ - Schema.Array(ClientRequest__PluginListMarketplaceKind).annotate({ - description: - "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.", - }), - Schema.Null, - ]), - ), -}); +export type V2TurnSteerParams__ImageDetail = "auto" | "low" | "high" | "original"; +export const V2TurnSteerParams__ImageDetail = Schema.Literals([ + "auto", + "low", + "high", + "original", +]).annotate({ identifier: "V2TurnSteerParams__ImageDetail" }); -export type ClientRequest__PluginShareTarget = { - readonly principalId: string; - readonly principalType: ClientRequest__PluginSharePrincipalType; - readonly role: ClientRequest__PluginShareTargetRole; -}; -export const ClientRequest__PluginShareTarget = Schema.Struct({ - principalId: Schema.String, - principalType: ClientRequest__PluginSharePrincipalType, - role: ClientRequest__PluginShareTargetRole, +export type V2TurnSteerParams__AdditionalContextKind = "untrusted" | "application"; +export const V2TurnSteerParams__AdditionalContextKind = Schema.Literals([ + "untrusted", + "application", +]).annotate({ identifier: "V2TurnSteerParams__AdditionalContextKind" }); + +export type V2WindowsSandboxReadinessResponse__WindowsSandboxReadiness = + | "ready" + | "notConfigured" + | "updateRequired"; +export const V2WindowsSandboxReadinessResponse__WindowsSandboxReadiness = Schema.Literals([ + "ready", + "notConfigured", + "updateRequired", +]).annotate({ identifier: "V2WindowsSandboxReadinessResponse__WindowsSandboxReadiness" }); + +export type V2WindowsSandboxSetupCompletedNotification__WindowsSandboxSetupMode = + | "elevated" + | "unelevated"; +export const V2WindowsSandboxSetupCompletedNotification__WindowsSandboxSetupMode = Schema.Literals([ + "elevated", + "unelevated", +]).annotate({ identifier: "V2WindowsSandboxSetupCompletedNotification__WindowsSandboxSetupMode" }); + +export type V2WindowsSandboxSetupStartParams__AbsolutePathBuf = string; +export const V2WindowsSandboxSetupStartParams__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.", + identifier: "V2WindowsSandboxSetupStartParams__AbsolutePathBuf", }); -export type ClientRequest__Settings = { - readonly developer_instructions?: string | null; - readonly model: string; - readonly reasoning_effort?: ClientRequest__ReasoningEffort | null; +export type V2WindowsSandboxSetupStartParams__WindowsSandboxSetupMode = "elevated" | "unelevated"; +export const V2WindowsSandboxSetupStartParams__WindowsSandboxSetupMode = Schema.Literals([ + "elevated", + "unelevated", +]).annotate({ identifier: "V2WindowsSandboxSetupStartParams__WindowsSandboxSetupMode" }); + +export type ApplyPatchApprovalResponse__NetworkPolicyAmendment = { + readonly action: ApplyPatchApprovalResponse__NetworkPolicyRuleAction; + readonly host: string; }; -export const ClientRequest__Settings = Schema.Struct({ - developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - model: Schema.String, - reasoning_effort: Schema.optionalKey(Schema.Union([ClientRequest__ReasoningEffort, Schema.Null])), -}).annotate({ description: "Settings for a collaboration mode." }); +export const ApplyPatchApprovalResponse__NetworkPolicyAmendment = Schema.Struct({ + action: ApplyPatchApprovalResponse__NetworkPolicyRuleAction, + host: Schema.String, +}).annotate({ identifier: "ApplyPatchApprovalResponse__NetworkPolicyAmendment" }); -export type ClientRequest__ReviewStartParams = { - readonly delivery?: ClientRequest__ReviewDelivery | null; - readonly target: ClientRequest__ReviewTarget; - readonly threadId: string; +export type ClientRequest__InitializeParams = { + readonly capabilities?: ClientRequest__InitializeCapabilities | null; + readonly clientInfo: ClientRequest__ClientInfo; }; -export const ClientRequest__ReviewStartParams = Schema.Struct({ - delivery: Schema.optionalKey( - Schema.Union([ClientRequest__ReviewDelivery, Schema.Null]).annotate({ - description: - "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`).", - }), +export const ClientRequest__InitializeParams = Schema.Struct({ + capabilities: Schema.optionalKey( + Schema.Union([ClientRequest__InitializeCapabilities, Schema.Null]), ), - target: ClientRequest__ReviewTarget, - threadId: Schema.String, -}); + clientInfo: ClientRequest__ClientInfo, +}).annotate({ identifier: "ClientRequest__InitializeParams" }); export type ClientRequest__ThreadResumeParams = { readonly approvalPolicy?: ClientRequest__AskForApproval | null; readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; readonly baseInstructions?: string | null; - readonly config?: { readonly [x: string]: unknown } | null; + readonly config?: { readonly [x: string]: Schema.Json } | null; readonly cwd?: string | null; readonly developerInstructions?: string | null; + readonly excludeTurns?: boolean; readonly model?: string | null; readonly modelProvider?: string | null; readonly personality?: ClientRequest__Personality | null; @@ -11449,10 +16241,19 @@ export const ClientRequest__ThreadResumeParams = Schema.Struct({ ), baseInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), config: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.Unknown), Schema.Null]), + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + Schema.Null, + ]), ), cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), developerInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + excludeTurns: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.", + }), + ), model: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -11462,147 +16263,85 @@ export const ClientRequest__ThreadResumeParams = Schema.Struct({ ]), ), modelProvider: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - personality: Schema.optionalKey(Schema.Union([ClientRequest__Personality, Schema.Null])), + personality: Schema.optionalKey( + Schema.Union([ClientRequest__Personality, Schema.Null]).annotate({ + description: + "@deprecated `friendly` and `pragmatic` no longer select a style. Changing this does not rewrite the thread's existing instructions.", + }), + ), sandbox: Schema.optionalKey(Schema.Union([ClientRequest__SandboxMode, Schema.Null])), serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), threadId: Schema.String, }).annotate({ description: "There are three ways to resume a thread: 1. By thread_id: load the thread from disk by thread_id and resume it. 2. By history: instantiate the thread from memory and resume it. 3. By path: load the thread from disk by path and resume it.\n\nFor non-running threads, the precedence is: history > non-empty path > thread_id. If using history or a non-empty path for a non-running thread, the thread_id param will be ignored.\n\nIf thread_id identifies a running thread, app-server rejoins that thread and treats a non-empty path as a consistency check against the active rollout path. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.", + identifier: "ClientRequest__ThreadResumeParams", }); -export type ClientRequest__MigrationDetails = { - readonly commands?: ReadonlyArray; - readonly hooks?: ReadonlyArray; - readonly mcpServers?: ReadonlyArray; - readonly memory?: ReadonlyArray; - readonly plugins?: ReadonlyArray; - readonly sessions?: ReadonlyArray; - readonly skills?: ReadonlyArray; - readonly subagents?: ReadonlyArray; +export type ClientRequest__ThreadStartParams = { + readonly approvalPolicy?: ClientRequest__AskForApproval | null; + readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; + readonly baseInstructions?: string | null; + readonly config?: { readonly [x: string]: Schema.Json } | null; + readonly cwd?: string | null; + readonly developerInstructions?: string | null; + readonly ephemeral?: boolean | null; + readonly model?: string | null; + readonly modelProvider?: string | null; + readonly personality?: ClientRequest__Personality | null; + readonly sandbox?: ClientRequest__SandboxMode | null; + readonly serviceName?: string | null; + readonly serviceTier?: string | null; + readonly sessionStartSource?: ClientRequest__ThreadStartSource | null; + readonly threadSource?: ClientRequest__ThreadSource | null; }; -export const ClientRequest__MigrationDetails = Schema.Struct({ - commands: Schema.optionalKey( - Schema.Array(ClientRequest__CommandMigration).annotate({ default: [] }), - ), - hooks: Schema.optionalKey(Schema.Array(ClientRequest__HookMigration).annotate({ default: [] })), - mcpServers: Schema.optionalKey( - Schema.Array(ClientRequest__McpServerMigration).annotate({ default: [] }), - ), - memory: Schema.optionalKey(Schema.Array(Schema.String)), - plugins: Schema.optionalKey( - Schema.Array(ClientRequest__PluginsMigration).annotate({ default: [] }), +export const ClientRequest__ThreadStartParams = Schema.Struct({ + approvalPolicy: Schema.optionalKey(Schema.Union([ClientRequest__AskForApproval, Schema.Null])), + approvalsReviewer: Schema.optionalKey( + Schema.Union([ClientRequest__ApprovalsReviewer, Schema.Null]).annotate({ + description: + "Override where approval requests are routed for review on this thread and subsequent turns.", + }), ), - sessions: Schema.optionalKey( - Schema.Array(ClientRequest__SessionMigration).annotate({ default: [] }), + baseInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + config: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + Schema.Null, + ]), ), - skills: Schema.optionalKey(Schema.Array(ClientRequest__SkillMigration).annotate({ default: [] })), - subagents: Schema.optionalKey( - Schema.Array(ClientRequest__SubagentMigration).annotate({ default: [] }), + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + developerInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + ephemeral: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + model: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + modelProvider: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + personality: Schema.optionalKey( + Schema.Union([ClientRequest__Personality, Schema.Null]).annotate({ + description: "@deprecated `friendly` and `pragmatic` no longer select a style.", + }), ), -}); - -export type ClientRequest__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } - | { - readonly detail?: ClientRequest__ImageDetail | null; - readonly type: "image"; - readonly url: string; - } - | { - readonly detail?: ClientRequest__ImageDetail | null; - readonly path: string; - readonly type: "localImage"; - } - | { readonly type: "audio"; readonly url: string } - | { readonly path: string; readonly type: "localAudio" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const ClientRequest__UserInput = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(ClientRequest__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({ - detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), - Schema.Struct({ - type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), - url: Schema.String, - }).annotate({ title: "AudioUserInput" }), - Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), - 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 ClientRequest__ThreadGoalSetParams = { - readonly objective?: string | null; - readonly status?: ClientRequest__ThreadGoalStatus | null; - readonly threadId: string; - readonly tokenBudget?: number | null; -}; -export const ClientRequest__ThreadGoalSetParams = Schema.Struct({ - objective: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: Schema.optionalKey(Schema.Union([ClientRequest__ThreadGoalStatus, Schema.Null])), - threadId: Schema.String, - tokenBudget: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + sandbox: Schema.optionalKey(Schema.Union([ClientRequest__SandboxMode, Schema.Null])), + serviceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + sessionStartSource: Schema.optionalKey( + Schema.Union([ClientRequest__ThreadStartSource, Schema.Null]), ), -}); - -export type ClientRequest__ThreadMetadataUpdateParams = { - readonly gitInfo?: ClientRequest__ThreadMetadataGitInfoUpdateParams | null; - readonly threadId: string; -}; -export const ClientRequest__ThreadMetadataUpdateParams = Schema.Struct({ - gitInfo: Schema.optionalKey( - Schema.Union([ClientRequest__ThreadMetadataGitInfoUpdateParams, Schema.Null]).annotate({ - description: - "Patch the stored Git metadata for this thread. Omit a field to leave it unchanged, set it to `null` to clear it, or provide a string to replace the stored value.", + threadSource: Schema.optionalKey( + Schema.Union([ClientRequest__ThreadSource, Schema.Null]).annotate({ + description: "Optional client-supplied analytics source classification for this thread.", }), ), - threadId: Schema.String, -}); +}).annotate({ identifier: "ClientRequest__ThreadStartParams" }); export type ClientRequest__ThreadForkParams = { readonly approvalPolicy?: ClientRequest__AskForApproval | null; readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; readonly baseInstructions?: string | null; - readonly config?: { readonly [x: string]: unknown } | null; + readonly config?: { readonly [x: string]: Schema.Json } | null; readonly cwd?: string | null; readonly developerInstructions?: string | null; readonly ephemeral?: boolean; + readonly excludeTurns?: boolean; readonly lastTurnId?: string | null; readonly model?: string | null; readonly modelProvider?: string | null; @@ -11621,11 +16360,20 @@ export const ClientRequest__ThreadForkParams = Schema.Struct({ ), baseInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), config: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.Unknown), Schema.Null]), + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + Schema.Null, + ]), ), cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), developerInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), ephemeral: Schema.optionalKey(Schema.Boolean), + excludeTurns: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.", + }), + ), lastTurnId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -11656,34 +16404,116 @@ export const ClientRequest__ThreadForkParams = Schema.Struct({ }).annotate({ description: "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using a non-empty path, the thread_id param will be ignored. Empty string path values are treated as absent.\n\nPrefer using thread_id whenever possible.", + identifier: "ClientRequest__ThreadForkParams", }); -export type ClientRequest__ThreadListParams = { - readonly archived?: boolean | null; - readonly cursor?: string | null; - readonly cwd?: ClientRequest__ThreadListCwdFilter | null; - 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; - readonly useStateDbOnly?: boolean; +export type ClientRequest__ThreadGoalSetParams = { + readonly objective?: string | null; + readonly status?: ClientRequest__ThreadGoalStatus | null; + readonly threadId: string; + readonly tokenBudget?: number | null; }; -export const ClientRequest__ThreadListParams = Schema.Struct({ - archived: Schema.optionalKey( +export const ClientRequest__ThreadGoalSetParams = Schema.Struct({ + objective: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: Schema.optionalKey(Schema.Union([ClientRequest__ThreadGoalStatus, Schema.Null])), + threadId: Schema.String, + tokenBudget: Schema.optionalKey( Schema.Union([ - Schema.Boolean.annotate({ - description: - "Optional archived filter; when set to true, only archived threads are returned. If false or null, only non-archived threads are returned.", - }), + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), Schema.Null, ]), ), - cursor: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Opaque pagination cursor returned by a previous call.", +}).annotate({ identifier: "ClientRequest__ThreadGoalSetParams" }); + +export type ClientRequest__ThreadMetadataUpdateParams = { + readonly gitInfo?: ClientRequest__ThreadMetadataGitInfoUpdateParams | null; + readonly threadId: string; +}; +export const ClientRequest__ThreadMetadataUpdateParams = Schema.Struct({ + gitInfo: Schema.optionalKey( + Schema.Union([ClientRequest__ThreadMetadataGitInfoUpdateParams, Schema.Null]).annotate({ + description: + "Patch the stored Git metadata for this thread. Omit a field to leave it unchanged, set it to `null` to clear it, or provide a string to replace the stored value.", + }), + ), + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadMetadataUpdateParams" }); + +export type ClientRequest__ThreadItemsListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; + readonly sortDirection?: ClientRequest__SortDirection | null; + readonly threadId: string; + readonly turnId?: string | null; +}; +export const ClientRequest__ThreadItemsListParams = Schema.Struct({ + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque cursor to pass to the next call to continue after the last item.", + }), + Schema.Null, + ]), + ), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ description: "Optional item page size.", format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + sortDirection: Schema.optionalKey( + Schema.Union([ClientRequest__SortDirection, Schema.Null]).annotate({ + description: "Optional item pagination direction; defaults to ascending.", + }), + ), + threadId: Schema.String, + turnId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional turn id to filter by. When omitted, returns items across the thread.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__ThreadItemsListParams" }); + +export type ClientRequest__ThreadListParams = { + readonly archived?: boolean | null; + readonly cursor?: string | null; + readonly cwd?: ClientRequest__ThreadListCwdFilter | null; + readonly limit?: number | null; + readonly modelProviders?: ReadonlyArray | null; + readonly originators?: ReadonlyArray | null; + readonly searchTerm?: string | null; + readonly sectionId?: string | null; + readonly sortDirection?: ClientRequest__SortDirection | null; + readonly sortKey?: ClientRequest__ThreadSortKey | null; + readonly sourceKinds?: ReadonlyArray | null; + readonly useStateDbOnly?: boolean; +}; +export const ClientRequest__ThreadListParams = Schema.Struct({ + archived: Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + description: + "Optional archived filter; when set to true, only archived threads are returned. If false or null, only non-archived threads are returned.", + }), + Schema.Null, + ]), + ), + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque pagination cursor returned by a previous call.", }), Schema.Null, ]), @@ -11700,8 +16530,12 @@ export const ClientRequest__ThreadListParams = Schema.Struct({ description: "Optional page size; defaults to a reasonable server-side value.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -11714,6 +16548,15 @@ export const ClientRequest__ThreadListParams = Schema.Struct({ Schema.Null, ]), ), + originators: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + description: + "Optional originator allowlist, matching any supplied value exactly. Supported by hosted backends only; the local app-server rejects a nonempty list. Omitted or empty lists leave originators unrestricted.", + }), + Schema.Null, + ]), + ), searchTerm: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -11722,6 +16565,15 @@ export const ClientRequest__ThreadListParams = Schema.Struct({ Schema.Null, ]), ), + sectionId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Omit to include every section, set to `null` for unsectioned threads, or provide a section ID to return only threads in that section.", + }), + Schema.Null, + ]), + ), sortDirection: Schema.optionalKey( Schema.Union([ClientRequest__SortDirection, Schema.Null]).annotate({ description: "Optional sort direction; defaults to descending (newest first).", @@ -11747,582 +16599,495 @@ export const ClientRequest__ThreadListParams = Schema.Struct({ "If true, return from the state DB without scanning JSONL rollouts to repair thread metadata. Omitted or false preserves scan-and-repair behavior.", }), ), +}).annotate({ identifier: "ClientRequest__ThreadListParams" }); + +export type ClientRequest__ThreadSectionCreateParams = { + readonly appearance?: ClientRequest__ThreadSectionAppearance | null; + readonly name: string; +}; +export const ClientRequest__ThreadSectionCreateParams = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([ClientRequest__ThreadSectionAppearance, Schema.Null]), + ), + name: Schema.String.annotate({ description: "The user-visible name of the section." }), +}).annotate({ + description: "Parameters for creating an independently persisted thread section.", + identifier: "ClientRequest__ThreadSectionCreateParams", }); -export type ClientRequest__ThreadStartParams = { - readonly approvalPolicy?: ClientRequest__AskForApproval | null; - readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; - readonly baseInstructions?: string | null; - readonly config?: { readonly [x: string]: unknown } | null; - readonly cwd?: string | null; - readonly developerInstructions?: string | null; - readonly ephemeral?: boolean | null; - readonly model?: string | null; - readonly modelProvider?: string | null; - readonly personality?: ClientRequest__Personality | null; - readonly sandbox?: ClientRequest__SandboxMode | null; - readonly serviceName?: string | null; - readonly serviceTier?: string | null; - readonly sessionStartSource?: ClientRequest__ThreadStartSource | null; - readonly threadSource?: ClientRequest__ThreadSource | null; +export type ClientRequest__ThreadSectionUpdateParams = { + readonly appearance?: ClientRequest__ThreadSectionAppearance | null; + readonly name: string; + readonly sectionId: string; }; -export const ClientRequest__ThreadStartParams = Schema.Struct({ - approvalPolicy: Schema.optionalKey(Schema.Union([ClientRequest__AskForApproval, Schema.Null])), - approvalsReviewer: Schema.optionalKey( - Schema.Union([ClientRequest__ApprovalsReviewer, Schema.Null]).annotate({ - description: - "Override where approval requests are routed for review on this thread and subsequent turns.", +export const ClientRequest__ThreadSectionUpdateParams = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([ClientRequest__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Omit to preserve appearance, use `null` to clear it, or provide a replacement.", }), ), - baseInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - config: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.Unknown), Schema.Null]), + name: Schema.String.annotate({ description: "The updated user-visible name of the section." }), + sectionId: Schema.String.annotate({ + description: "The stable, server-generated identity of the section to update.", + }), +}).annotate({ + description: "Parameters for updating an independently persisted thread section.", + identifier: "ClientRequest__ThreadSectionUpdateParams", +}); + +export type ClientRequest__ThreadTurnsListParams = { + readonly cursor?: string | null; + readonly itemsView?: ClientRequest__TurnItemsView | 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, + ]), ), - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - developerInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - ephemeral: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - model: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - modelProvider: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - personality: Schema.optionalKey(Schema.Union([ClientRequest__Personality, Schema.Null])), - sandbox: Schema.optionalKey(Schema.Union([ClientRequest__SandboxMode, Schema.Null])), - serviceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - sessionStartSource: Schema.optionalKey( - Schema.Union([ClientRequest__ThreadStartSource, Schema.Null]), + itemsView: Schema.optionalKey( + Schema.Union([ClientRequest__TurnItemsView, Schema.Null]).annotate({ + description: "How much item detail to include for each returned turn; defaults to summary.", + }), ), - threadSource: Schema.optionalKey( - Schema.Union([ClientRequest__ThreadSource, Schema.Null]).annotate({ - description: "Optional client-supplied analytics source classification for this thread.", + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ description: "Optional turn page size.", format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + sortDirection: Schema.optionalKey( + Schema.Union([ClientRequest__SortDirection, Schema.Null]).annotate({ + description: "Optional turn pagination direction; defaults to descending.", }), ), -}); + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ThreadTurnsListParams" }); -export type ClientRequest__WindowsSandboxSetupStartParams = { - readonly cwd?: ClientRequest__AbsolutePathBuf | null; - readonly mode: ClientRequest__WindowsSandboxSetupMode; +export type ClientRequest__SkillsExtraRootsSetParams = { + readonly extraRoots: ReadonlyArray; }; -export const ClientRequest__WindowsSandboxSetupStartParams = Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), - mode: ClientRequest__WindowsSandboxSetupMode, -}); - -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 const ClientRequest__SkillsExtraRootsSetParams = Schema.Struct({ + extraRoots: Schema.Array(ClientRequest__AbsolutePathBuf), +}).annotate({ identifier: "ClientRequest__SkillsExtraRootsSetParams" }); -export type CommandExecutionRequestApprovalParams__FileSystemSpecialPath = - | { readonly kind: "root" } - | { readonly kind: "minimal" } - | { - readonly kind: "project_roots"; - readonly subpath?: CommandExecutionRequestApprovalParams__LegacyAppPathString | null; - } - | { readonly kind: "tmpdir" } - | { readonly kind: "slash_tmp" } - | { - readonly kind: "unknown"; - readonly path: string; - readonly subpath?: CommandExecutionRequestApprovalParams__LegacyAppPathString | 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("project_roots"), - subpath: Schema.optionalKey( - Schema.Union([CommandExecutionRequestApprovalParams__LegacyAppPathString, 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([CommandExecutionRequestApprovalParams__LegacyAppPathString, Schema.Null]), - ), - }), - ], - { mode: "oneOf" }, -); +export type ClientRequest__PluginInstalledParams = { + readonly cwds?: ReadonlyArray | null; + readonly installSuggestionPluginNames?: ReadonlyArray | null; +}; +export const ClientRequest__PluginInstalledParams = Schema.Struct({ + cwds: Schema.optionalKey( + Schema.Union([ + Schema.Array(ClientRequest__AbsolutePathBuf).annotate({ + description: "Optional working directories used to discover repo marketplaces.", + }), + Schema.Null, + ]), + ), + installSuggestionPluginNames: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + description: + "Additional uninstalled plugin names that should be returned when present locally. This is used by mention surfaces that intentionally expose install entrypoints.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__PluginInstalledParams" }); -export type CommandExecutionRequestApprovalParams__NetworkApprovalContext = { - readonly host: string; - readonly protocol: CommandExecutionRequestApprovalParams__NetworkApprovalProtocol; +export type ClientRequest__PluginReadParams = { + readonly marketplacePath?: ClientRequest__AbsolutePathBuf | null; + readonly pluginName: string; + readonly remoteMarketplaceName?: string | null; }; -export const CommandExecutionRequestApprovalParams__NetworkApprovalContext = Schema.Struct({ - host: Schema.String, - protocol: CommandExecutionRequestApprovalParams__NetworkApprovalProtocol, +export const ClientRequest__PluginReadParams = Schema.Struct({ + marketplacePath: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), + pluginName: Schema.String, + remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ClientRequest__PluginReadParams" }); + +export type ClientRequest__FsReadFileParams = { readonly path: ClientRequest__AbsolutePathBuf }; +export const ClientRequest__FsReadFileParams = Schema.Struct({ + path: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute path to read." }), +}).annotate({ + description: "Read a file from the host filesystem.", + identifier: "ClientRequest__FsReadFileParams", }); -export type CommandExecutionRequestApprovalParams__NetworkPolicyAmendment = { - readonly action: CommandExecutionRequestApprovalParams__NetworkPolicyRuleAction; - readonly host: string; +export type ClientRequest__FsWriteFileParams = { + readonly dataBase64: string; + readonly path: ClientRequest__AbsolutePathBuf; }; -export const CommandExecutionRequestApprovalParams__NetworkPolicyAmendment = Schema.Struct({ - action: CommandExecutionRequestApprovalParams__NetworkPolicyRuleAction, - host: Schema.String, +export const ClientRequest__FsWriteFileParams = Schema.Struct({ + dataBase64: Schema.String.annotate({ description: "File contents encoded as base64." }), + path: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute path to write." }), +}).annotate({ + description: "Write a file on the host filesystem.", + identifier: "ClientRequest__FsWriteFileParams", }); -export type CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment = { - readonly action: CommandExecutionRequestApprovalResponse__NetworkPolicyRuleAction; - readonly host: string; +export type ClientRequest__FsCreateDirectoryParams = { + readonly path: ClientRequest__AbsolutePathBuf; + readonly recursive?: boolean | null; }; -export const CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment = Schema.Struct({ - action: CommandExecutionRequestApprovalResponse__NetworkPolicyRuleAction, - host: Schema.String, +export const ClientRequest__FsCreateDirectoryParams = Schema.Struct({ + path: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute directory path to create." }), + recursive: Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + description: "Whether parent directories should also be created. Defaults to `true`.", + }), + Schema.Null, + ]), + ), +}).annotate({ + description: "Create a directory on the host filesystem.", + identifier: "ClientRequest__FsCreateDirectoryParams", }); -export type ExecCommandApprovalResponse__NetworkPolicyAmendment = { - readonly action: ExecCommandApprovalResponse__NetworkPolicyRuleAction; - readonly host: string; +export type ClientRequest__FsGetMetadataParams = { readonly path: ClientRequest__AbsolutePathBuf }; +export const ClientRequest__FsGetMetadataParams = Schema.Struct({ + path: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute path to inspect." }), +}).annotate({ + description: "Request metadata for an absolute path.", + identifier: "ClientRequest__FsGetMetadataParams", +}); + +export type ClientRequest__FsReadDirectoryParams = { + readonly path: ClientRequest__AbsolutePathBuf; }; -export const ExecCommandApprovalResponse__NetworkPolicyAmendment = Schema.Struct({ - action: ExecCommandApprovalResponse__NetworkPolicyRuleAction, - host: Schema.String, +export const ClientRequest__FsReadDirectoryParams = Schema.Struct({ + path: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute directory path to read." }), +}).annotate({ + description: "List direct child names for a directory.", + identifier: "ClientRequest__FsReadDirectoryParams", }); -export type FuzzyFileSearchResponse__FuzzyFileSearchResult = { - readonly file_name: string; - readonly indices?: ReadonlyArray | null; - readonly match_type: FuzzyFileSearchResponse__FuzzyFileSearchMatchType; - readonly path: string; - readonly root: string; - readonly score: number; +export type ClientRequest__FsRemoveParams = { + readonly force?: boolean | null; + readonly path: ClientRequest__AbsolutePathBuf; + readonly recursive?: boolean | null; }; -export const FuzzyFileSearchResponse__FuzzyFileSearchResult = Schema.Struct({ - file_name: Schema.String, - indices: Schema.optionalKey( +export const ClientRequest__FsRemoveParams = Schema.Struct({ + force: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - ), + Schema.Boolean.annotate({ + description: "Whether missing paths should be ignored. Defaults to `true`.", + }), Schema.Null, ]), ), - match_type: FuzzyFileSearchResponse__FuzzyFileSearchMatchType, - path: Schema.String, - root: Schema.String, - score: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}).annotate({ description: "Superset of [`codex_file_search::FileMatch`]" }); - -export type FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchResult = { - readonly file_name: string; - readonly indices?: ReadonlyArray | null; - readonly match_type: FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType; - readonly path: string; - readonly root: string; - readonly score: number; -}; -export const FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchResult = Schema.Struct({ - file_name: Schema.String, - indices: Schema.optionalKey( + path: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute path to remove." }), + recursive: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - ), + Schema.Boolean.annotate({ + description: "Whether directory removal should recurse. Defaults to `true`.", + }), Schema.Null, ]), ), - match_type: FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType, - path: Schema.String, - root: Schema.String, - score: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}).annotate({ description: "Superset of [`codex_file_search::FileMatch`]" }); - -export type JSONRPCMessage__JSONRPCError = { - readonly error: JSONRPCMessage__JSONRPCErrorError; - readonly id: JSONRPCMessage__RequestId; -}; -export const JSONRPCMessage__JSONRPCError = Schema.Struct({ - error: JSONRPCMessage__JSONRPCErrorError, - id: JSONRPCMessage__RequestId, -}).annotate({ description: "A response to a request that indicates an error occurred." }); - -export type JSONRPCMessage__JSONRPCResponse = { - readonly id: JSONRPCMessage__RequestId; - readonly result: unknown; -}; -export const JSONRPCMessage__JSONRPCResponse = Schema.Struct({ - id: JSONRPCMessage__RequestId, - result: Schema.Unknown, -}).annotate({ description: "A successful (non-error) response to a request." }); +}).annotate({ + description: "Remove a file or directory tree from the host filesystem.", + identifier: "ClientRequest__FsRemoveParams", +}); -export type JSONRPCMessage__JSONRPCRequest = { - readonly id: JSONRPCMessage__RequestId; - readonly method: string; - readonly params?: unknown; - readonly trace?: JSONRPCMessage__W3cTraceContext | null; +export type ClientRequest__FsCopyParams = { + readonly destinationPath: ClientRequest__AbsolutePathBuf; + readonly recursive?: boolean; + readonly sourcePath: ClientRequest__AbsolutePathBuf; }; -export const JSONRPCMessage__JSONRPCRequest = Schema.Struct({ - id: JSONRPCMessage__RequestId, - method: Schema.String, - params: Schema.optionalKey(Schema.Unknown), - trace: Schema.optionalKey( - Schema.Union([JSONRPCMessage__W3cTraceContext, Schema.Null]).annotate({ - description: "Optional W3C Trace Context for distributed tracing.", +export const ClientRequest__FsCopyParams = Schema.Struct({ + destinationPath: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute destination path." }), + recursive: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "Required for directory copies; ignored for file copies.", }), ), -}).annotate({ description: "A request that expects a response." }); + sourcePath: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute source path." }), +}).annotate({ + description: "Copy a file or directory tree on the host filesystem.", + identifier: "ClientRequest__FsCopyParams", +}); -export type McpServerElicitationRequestParams__McpElicitationBooleanSchema = { - readonly default?: boolean | null; - readonly description?: string | null; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationBooleanType; +export type ClientRequest__FsWatchParams = { + readonly path: ClientRequest__AbsolutePathBuf; + readonly watchId: string; }; -export const McpServerElicitationRequestParams__McpElicitationBooleanSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationBooleanType, +export const ClientRequest__FsWatchParams = Schema.Struct({ + path: Schema.suspend( + (): Schema.Codec => ClientRequest__AbsolutePathBuf, + ).annotate({ description: "Absolute file or directory path to watch." }), + watchId: Schema.String.annotate({ + description: "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.", + }), +}).annotate({ + description: "Start filesystem watch notifications for an absolute path.", + identifier: "ClientRequest__FsWatchParams", }); -export type McpServerElicitationRequestParams__McpElicitationTitledEnumItems = { - readonly anyOf: ReadonlyArray; +export type ClientRequest__SkillsConfigWriteParams = { + readonly enabled: boolean; + readonly name?: string | null; + readonly path?: ClientRequest__AbsolutePathBuf | null; }; -export const McpServerElicitationRequestParams__McpElicitationTitledEnumItems = Schema.Struct({ - anyOf: Schema.Array(McpServerElicitationRequestParams__McpElicitationConstOption), -}); +export const ClientRequest__SkillsConfigWriteParams = Schema.Struct({ + enabled: Schema.Boolean, + name: Schema.optionalKey( + Schema.Union([Schema.String.annotate({ description: "Name-based selector." }), Schema.Null]), + ), + path: Schema.optionalKey( + Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Path-based selector.", + }), + ), +}).annotate({ identifier: "ClientRequest__SkillsConfigWriteParams" }); -export type McpServerElicitationRequestParams__McpElicitationNumberSchema = { - readonly default?: number | null; - readonly description?: string | null; - readonly maximum?: number | null; - readonly minimum?: number | null; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationNumberType; +export type ClientRequest__PluginInstallParams = { + readonly installAttemptId?: string | null; + readonly marketplacePath?: ClientRequest__AbsolutePathBuf | null; + readonly pluginName: string; + readonly remoteMarketplaceName?: string | null; }; -export const McpServerElicitationRequestParams__McpElicitationNumberSchema = Schema.Struct({ - default: Schema.optionalKey( +export const ClientRequest__PluginInstallParams = Schema.Struct({ + installAttemptId: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "double" }).check(Schema.isFinite()), + Schema.String.annotate({ + description: "Client-generated identifier used to correlate one installation attempt.", + }), Schema.Null, ]), ), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - maximum: Schema.optionalKey( + marketplacePath: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), + pluginName: Schema.String, + remoteMarketplaceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ClientRequest__PluginInstallParams" }); + +export type ClientRequest__PluginListParams = { + readonly cwds?: ReadonlyArray | null; + readonly forceRefetch?: boolean; + readonly marketplaceKinds?: ReadonlyArray | null; +}; +export const ClientRequest__PluginListParams = Schema.Struct({ + cwds: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "double" }).check(Schema.isFinite()), + Schema.Array(ClientRequest__AbsolutePathBuf).annotate({ + description: + "Optional working directories used to discover repo marketplaces. When omitted, only home-scoped marketplaces and the official curated marketplace are considered.", + }), Schema.Null, ]), ), - minimum: Schema.optionalKey( + forceRefetch: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "Whether the client requests a fresh remote plugin catalog fetch.", + }), + ), + marketplaceKinds: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "double" }).check(Schema.isFinite()), + Schema.Array(ClientRequest__PluginListMarketplaceKind).annotate({ + description: + "Optional marketplace kind filter. When omitted, only local marketplaces are queried, plus the default remote catalog when enabled by feature flag.", + }), Schema.Null, ]), ), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationNumberType, -}); +}).annotate({ identifier: "ClientRequest__PluginListParams" }); -export type McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema = { - readonly default?: string | null; - readonly description?: string | null; - readonly enum: ReadonlyArray; - readonly enumNames?: ReadonlyArray | null; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationStringType; +export type ClientRequest__PluginShareTarget = { + readonly principalId: string; + readonly principalType: ClientRequest__PluginSharePrincipalType; + readonly role: ClientRequest__PluginShareTargetRole; }; -export const McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema = - Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - enum: Schema.Array(Schema.String), - enumNames: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationStringType, - }); +export const ClientRequest__PluginShareTarget = Schema.Struct({ + principalId: Schema.String, + principalType: ClientRequest__PluginSharePrincipalType, + role: ClientRequest__PluginShareTargetRole, +}).annotate({ identifier: "ClientRequest__PluginShareTarget" }); -export type McpServerElicitationRequestParams__McpElicitationStringSchema = { - readonly default?: string | null; - readonly description?: string | null; - readonly format?: McpServerElicitationRequestParams__McpElicitationStringFormat | null; - readonly maxLength?: number | null; - readonly minLength?: number | null; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationStringType; +export type ClientRequest__Settings = { + readonly developer_instructions?: string | null; + readonly model: string; + readonly reasoning_effort?: ClientRequest__ReasoningEffort | null; }; -export const McpServerElicitationRequestParams__McpElicitationStringSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - format: Schema.optionalKey( - Schema.Union([McpServerElicitationRequestParams__McpElicitationStringFormat, Schema.Null]), - ), - maxLength: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - minLength: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationStringType, +export const ClientRequest__Settings = Schema.Struct({ + developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + model: Schema.String, + reasoning_effort: Schema.optionalKey(Schema.Union([ClientRequest__ReasoningEffort, Schema.Null])), +}).annotate({ + description: "Settings for a collaboration mode.", + identifier: "ClientRequest__Settings", }); -export type McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema = { - readonly default?: string | null; - readonly description?: string | null; - readonly oneOf: ReadonlyArray; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationStringType; -}; -export const McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema = - Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - oneOf: Schema.Array(McpServerElicitationRequestParams__McpElicitationConstOption), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationStringType, - }); - -export type McpServerElicitationRequestParams__McpElicitationUntitledEnumItems = { - readonly enum: ReadonlyArray; - readonly type: McpServerElicitationRequestParams__McpElicitationStringType; +export type ClientRequest__ConfigurationReasoning = { + readonly effort: ClientRequest__ReasoningEffort; }; -export const McpServerElicitationRequestParams__McpElicitationUntitledEnumItems = Schema.Struct({ - enum: Schema.Array(Schema.String), - type: McpServerElicitationRequestParams__McpElicitationStringType, +export const ClientRequest__ConfigurationReasoning = Schema.Struct({ + effort: ClientRequest__ReasoningEffort, +}).annotate({ + description: "Reasoning settings interpreted by the backend for the routed model.", + identifier: "ClientRequest__ConfigurationReasoning", }); -export type McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema = { - readonly default?: string | null; - readonly description?: string | null; - readonly enum: ReadonlyArray; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationStringType; +export type ClientRequest__TextElement = { + readonly byteRange: ClientRequest__ByteRange; + readonly placeholder?: string | null; }; -export const McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema = - Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - enum: Schema.Array(Schema.String), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationStringType, - }); +export const ClientRequest__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => ClientRequest__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "ClientRequest__TextElement" }); -export type PermissionsRequestApprovalParams__FileSystemSpecialPath = - | { readonly kind: "root" } - | { readonly kind: "minimal" } +export type ClientRequest__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } | { - readonly kind: "project_roots"; - readonly subpath?: PermissionsRequestApprovalParams__LegacyAppPathString | null; + readonly detail?: ClientRequest__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; } - | { readonly kind: "tmpdir" } - | { readonly kind: "slash_tmp" } - | { - readonly kind: "unknown"; - readonly path: string; - readonly subpath?: PermissionsRequestApprovalParams__LegacyAppPathString | 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("project_roots"), - subpath: Schema.optionalKey( - Schema.Union([PermissionsRequestApprovalParams__LegacyAppPathString, 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([PermissionsRequestApprovalParams__LegacyAppPathString, Schema.Null]), - ), - }), - ], - { mode: "oneOf" }, -); - -export type PermissionsRequestApprovalResponse__FileSystemSpecialPath = - | { readonly kind: "root" } - | { readonly kind: "minimal" } | { - readonly kind: "project_roots"; - readonly subpath?: PermissionsRequestApprovalResponse__LegacyAppPathString | null; + readonly detail?: ClientRequest__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; } - | { readonly kind: "tmpdir" } - | { readonly kind: "slash_tmp" } - | { - readonly kind: "unknown"; - readonly path: string; - readonly subpath?: PermissionsRequestApprovalResponse__LegacyAppPathString | null; - }; -export const PermissionsRequestApprovalResponse__FileSystemSpecialPath = Schema.Union( + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const ClientRequest__FunctionCallOutputContentItem = 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("project_roots"), - subpath: Schema.optionalKey( - Schema.Union([PermissionsRequestApprovalResponse__LegacyAppPathString, Schema.Null]), - ), - }).annotate({ title: "KindFileSystemSpecialPath" }), - Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ - title: "TmpdirFileSystemSpecialPath", - }), - Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ - title: "SlashTmpFileSystemSpecialPath", - }), + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), Schema.Struct({ - kind: Schema.Literal("unknown"), - path: Schema.String, - subpath: Schema.optionalKey( - Schema.Union([PermissionsRequestApprovalResponse__LegacyAppPathString, Schema.Null]), - ), - }), + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), ], { mode: "oneOf" }, -); +).annotate({ + description: + "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.", + identifier: "ClientRequest__FunctionCallOutputContentItem", +}); -export type ServerNotification__CommandAction = +export type ClientRequest__ContentItem = + | { readonly text: string; readonly type: "input_text" } | { - readonly command: string; - readonly name: string; - readonly path: ServerNotification__AbsolutePathBuf; - readonly type: "read"; + readonly detail?: ClientRequest__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; } - | { 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 detail?: ClientRequest__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; } - | { readonly command: string; readonly type: "unknown" }; -export const ServerNotification__CommandAction = Schema.Union( + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly text: string; readonly type: "output_text" }; +export const ClientRequest__ContentItem = 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" }), + text: Schema.String, + type: Schema.Literal("input_text").annotate({ title: "InputTextContentItemType" }), + }).annotate({ title: "InputTextContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + file_id: Schema.String, + }).annotate({ title: "FileIdContentItem" }), + ]).annotate({ title: "InputImageContentItem" }), 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" }), + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ title: "InputAudioContentItemType" }), + }).annotate({ title: "InputAudioContentItem" }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + text: Schema.String, + type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), + }).annotate({ title: "OutputTextContentItem" }), ], { mode: "oneOf" }, -); - -export type ServerNotification__FsChangedNotification = { - readonly changedPaths: ReadonlyArray; - readonly watchId: string; -}; -export const ServerNotification__FsChangedNotification = Schema.Struct({ - changedPaths: Schema.Array(ServerNotification__AbsolutePathBuf).annotate({ - description: "File or directory paths associated with this event.", - }), - watchId: Schema.String.annotate({ - description: "Watch identifier previously provided to `fs/watch`.", - }), -}).annotate({ description: "Filesystem watch notification emitted for `fs/watch` subscribers." }); +).annotate({ identifier: "ClientRequest__ContentItem" }); -export type ServerNotification__SandboxPolicy = +export type ClientRequest__SandboxPolicy = | { readonly type: "dangerFullAccess" } | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } + | { readonly networkAccess?: ClientRequest__NetworkAccess; readonly type: "externalSandbox" } | { readonly excludeSlashTmp?: boolean; readonly excludeTmpdirEnvVar?: boolean; readonly networkAccess?: boolean; readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; + readonly writableRoots?: ReadonlyArray; }; -export const ServerNotification__SandboxPolicy = Schema.Union( +export const ClientRequest__SandboxPolicy = Schema.Union( [ Schema.Struct({ type: Schema.Literal("dangerFullAccess").annotate({ @@ -12335,7 +17100,9 @@ export const ServerNotification__SandboxPolicy = Schema.Union( }).annotate({ title: "ReadOnlySandboxPolicy" }), Schema.Struct({ networkAccess: Schema.optionalKey( - Schema.Literals(["restricted", "enabled"]).annotate({ default: "restricted" }), + Schema.suspend( + (): Schema.Codec => ClientRequest__NetworkAccess, + ).annotate({ default: "restricted" }), ), type: Schema.Literal("externalSandbox").annotate({ title: "ExternalSandboxSandboxPolicyType", @@ -12347,1450 +17114,831 @@ export const ServerNotification__SandboxPolicy = Schema.Union( networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), writableRoots: Schema.optionalKey( - Schema.Array(ServerNotification__AbsolutePathBuf).annotate({ default: [] }), + Schema.Array(ClientRequest__AbsolutePathBuf).annotate({ default: [] }), ), }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ClientRequest__SandboxPolicy" }); -export type ServerNotification__AppMetadata = { - readonly categories?: ReadonlyArray | null; - readonly developer?: string | null; - readonly firstPartyRequiresInstall?: boolean | null; - readonly firstPartyType?: string | null; - readonly review?: ServerNotification__AppReview | null; - readonly screenshots?: ReadonlyArray | null; - readonly seoDescription?: string | null; - readonly showInComposerWhenUnlinked?: boolean | null; - readonly subCategories?: ReadonlyArray | null; - readonly version?: string | null; - readonly versionId?: string | null; - readonly versionNotes?: string | null; +export type ClientRequest__ReviewStartParams = { + readonly delivery?: ClientRequest__ReviewDelivery | null; + readonly target: ClientRequest__ReviewTarget; + readonly threadId: string; }; -export const ServerNotification__AppMetadata = Schema.Struct({ - categories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - firstPartyRequiresInstall: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - firstPartyType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - review: Schema.optionalKey(Schema.Union([ServerNotification__AppReview, Schema.Null])), - screenshots: Schema.optionalKey( - Schema.Union([Schema.Array(ServerNotification__AppScreenshot), Schema.Null]), +export const ClientRequest__ReviewStartParams = Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([ClientRequest__ReviewDelivery, Schema.Null]).annotate({ + description: + "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`). Detached delivery is deprecated and emits `deprecationNotice`. Use `thread/start` followed by an inline review for a separate review thread.", + }), ), - seoDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - showInComposerWhenUnlinked: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - subCategories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - version: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - versionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - versionNotes: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ServerNotification__CollabAgentState = { - readonly message?: string | null; - readonly status: ServerNotification__CollabAgentStatus; -}; -export const ServerNotification__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: ServerNotification__CollabAgentStatus, -}); - -export type ServerNotification__ExternalAgentConfigImportItemTypeFailure = { - readonly cwd?: string | null; - readonly errorType?: string | null; - readonly failureStage: string; - readonly itemType: ServerNotification__ExternalAgentConfigMigrationItemType; - readonly message: string; - readonly source?: string | null; - readonly subErrorType?: string | null; -}; -export const ServerNotification__ExternalAgentConfigImportItemTypeFailure = Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - failureStage: Schema.String, - itemType: ServerNotification__ExternalAgentConfigMigrationItemType, - message: Schema.String, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ServerNotification__ExternalAgentConfigImportItemTypeSuccess = { - readonly cwd?: string | null; - readonly itemType: ServerNotification__ExternalAgentConfigMigrationItemType; - readonly source?: string | null; - readonly target?: string | null; -}; -export const ServerNotification__ExternalAgentConfigImportItemTypeSuccess = Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - itemType: ServerNotification__ExternalAgentConfigMigrationItemType, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); + target: ClientRequest__ReviewTarget, + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__ReviewStartParams" }); -export type ServerNotification__FuzzyFileSearchResult = { - readonly file_name: string; - readonly indices?: ReadonlyArray | null; - readonly match_type: ServerNotification__FuzzyFileSearchMatchType; - readonly path: string; - readonly root: string; - readonly score: number; +export type ClientRequest__McpServerOauthLoginParams = { + readonly clientRegistration?: ClientRequest__McpServerOauthClientRegistration | null; + readonly name: string; + readonly scopes?: ReadonlyArray | null; + readonly threadId?: string | null; + readonly timeoutSecs?: number | null; }; -export const ServerNotification__FuzzyFileSearchResult = Schema.Struct({ - file_name: Schema.String, - indices: Schema.optionalKey( +export const ClientRequest__McpServerOauthLoginParams = Schema.Struct({ + clientRegistration: Schema.optionalKey( + Schema.Union([ClientRequest__McpServerOauthClientRegistration, Schema.Null]).annotate({ + description: + "Registration strategy for this login only; omission selects automatic discovery.", + }), + ), + name: Schema.String, + scopes: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + timeoutSecs: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), ), Schema.Null, ]), ), - match_type: ServerNotification__FuzzyFileSearchMatchType, - path: Schema.String, - root: Schema.String, - score: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}).annotate({ description: "Superset of [`codex_file_search::FileMatch`]" }); +}).annotate({ identifier: "ClientRequest__McpServerOauthLoginParams" }); -export type ServerNotification__GuardianApprovalReview = { - readonly rationale?: string | null; - readonly riskLevel?: ServerNotification__GuardianRiskLevel | null; - readonly status: ServerNotification__GuardianApprovalReviewStatus; - readonly userAuthorization?: ServerNotification__GuardianUserAuthorization | null; +export type ClientRequest__ListMcpServerStatusParams = { + readonly cursor?: string | null; + readonly detail?: ClientRequest__McpServerStatusDetail | null; + readonly limit?: number | null; + readonly threadId?: string | null; }; -export const ServerNotification__GuardianApprovalReview = Schema.Struct({ - rationale: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - riskLevel: Schema.optionalKey(Schema.Union([ServerNotification__GuardianRiskLevel, Schema.Null])), - status: ServerNotification__GuardianApprovalReviewStatus, - userAuthorization: Schema.optionalKey( - Schema.Union([ServerNotification__GuardianUserAuthorization, Schema.Null]), +export const ClientRequest__ListMcpServerStatusParams = Schema.Struct({ + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque pagination cursor returned by a previous call.", + }), + Schema.Null, + ]), ), -}).annotate({ - description: - "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", -}); + detail: Schema.optionalKey( + Schema.Union([ClientRequest__McpServerStatusDetail, Schema.Null]).annotate({ + description: + "Controls how much MCP inventory data to fetch for each server. Defaults to `Full` when omitted.", + }), + ), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Optional page size; defaults to a server-defined value.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ClientRequest__ListMcpServerStatusParams" }); -export type ServerNotification__HookOutputEntry = { - readonly kind: ServerNotification__HookOutputEntryKind; - readonly text: string; +export type ClientRequest__WindowsSandboxSetupStartParams = { + readonly cwd?: ClientRequest__AbsolutePathBuf | null; + readonly mode: ClientRequest__WindowsSandboxSetupMode; }; -export const ServerNotification__HookOutputEntry = Schema.Struct({ - kind: ServerNotification__HookOutputEntryKind, - text: Schema.String, -}); +export const ClientRequest__WindowsSandboxSetupStartParams = Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([ClientRequest__AbsolutePathBuf, Schema.Null])), + mode: ClientRequest__WindowsSandboxSetupMode, +}).annotate({ identifier: "ClientRequest__WindowsSandboxSetupStartParams" }); -export type ServerNotification__FileSystemSpecialPath = - | { readonly kind: "root" } - | { readonly kind: "minimal" } +export type ClientRequest__LoginAccountParams = + | { readonly apiKey: string; readonly type: "apiKey" } | { - readonly kind: "project_roots"; - readonly subpath?: ServerNotification__LegacyAppPathString | null; + readonly appBrand?: ClientRequest__LoginAppBrand | null; + readonly codexStreamlinedLogin?: boolean; + readonly type: "chatgpt"; + readonly useHostedLoginSuccessPage?: boolean; } - | { readonly kind: "tmpdir" } - | { readonly kind: "slash_tmp" } + | { readonly type: "chatgptDeviceCode" } | { - readonly kind: "unknown"; - readonly path: string; - readonly subpath?: ServerNotification__LegacyAppPathString | null; + readonly accessToken: string; + readonly chatgptAccountId: string; + readonly chatgptPlanType?: string | null; + readonly type: "chatgptAuthTokens"; + } + | { readonly apiKey: string; readonly region: string; readonly type: "amazonBedrock" } + | { + readonly accessKeyId: string; + readonly region: string; + readonly secretAccessKey: string; + readonly sessionToken?: string | null; + readonly type: "amazonBedrockAccessKeys"; }; -export const ServerNotification__FileSystemSpecialPath = Schema.Union( +export const ClientRequest__LoginAccountParams = 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("project_roots"), - subpath: Schema.optionalKey( - Schema.Union([ServerNotification__LegacyAppPathString, Schema.Null]), - ), - }).annotate({ title: "KindFileSystemSpecialPath" }), - Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ - title: "TmpdirFileSystemSpecialPath", - }), - Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ - title: "SlashTmpFileSystemSpecialPath", - }), + apiKey: Schema.String, + type: Schema.Literal("apiKey").annotate({ title: "ApiKeyLoginAccountParamsType" }), + }).annotate({ title: "ApiKeyLoginAccountParams" }), Schema.Struct({ - kind: Schema.Literal("unknown"), - path: Schema.String, - subpath: Schema.optionalKey( - Schema.Union([ServerNotification__LegacyAppPathString, Schema.Null]), - ), - }), - ], - { mode: "oneOf" }, -); - -export type ServerNotification__McpServerStatusUpdatedNotification = { - readonly error?: string | null; - readonly failureReason?: ServerNotification__McpServerStartupFailureReason | null; - readonly name: string; - readonly status: ServerNotification__McpServerStartupState; - readonly threadId?: string | null; -}; -export const ServerNotification__McpServerStatusUpdatedNotification = Schema.Struct({ - error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - failureReason: Schema.optionalKey( - Schema.Union([ServerNotification__McpServerStartupFailureReason, Schema.Null]), - ), - name: Schema.String, - status: ServerNotification__McpServerStartupState, - threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ServerNotification__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const ServerNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(ServerNotification__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); - -export type ServerNotification__ModelReroutedNotification = { - readonly fromModel: string; - readonly reason: ServerNotification__ModelRerouteReason; - readonly threadId: string; - readonly toModel: string; - readonly turnId: string; -}; -export const ServerNotification__ModelReroutedNotification = Schema.Struct({ - fromModel: Schema.String, - reason: ServerNotification__ModelRerouteReason, - threadId: Schema.String, - toModel: Schema.String, - turnId: Schema.String, -}); - -export type ServerNotification__ModelVerificationNotification = { - readonly threadId: string; - readonly turnId: string; - readonly verifications: ReadonlyArray; -}; -export const ServerNotification__ModelVerificationNotification = Schema.Struct({ - threadId: Schema.String, - turnId: Schema.String, - verifications: Schema.Array(ServerNotification__ModelVerification), -}); - -export type ServerNotification__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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: ServerNotification__NonSteerableTurnKind; - }; - }; -export const ServerNotification__CodexErrorInfo = Schema.Union( - [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "internalServerError", - "unauthorized", - "badRequest", - "threadRollbackFailed", - "sandboxError", - "other", - ]), + appBrand: Schema.optionalKey(Schema.Union([ClientRequest__LoginAppBrand, Schema.Null])), + codexStreamlinedLogin: Schema.optionalKey(Schema.Boolean), + type: Schema.Literal("chatgpt").annotate({ title: "ChatgptLoginAccountParamsType" }), + useHostedLoginSuccessPage: Schema.optionalKey(Schema.Boolean), + }).annotate({ title: "ChatgptLoginAccountParams" }), Schema.Struct({ - httpConnectionFailed: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), + type: Schema.Literal("chatgptDeviceCode").annotate({ + title: "ChatgptDeviceCodeLoginAccountParamsType", }), - }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + }).annotate({ title: "ChatgptDeviceCodeLoginAccountParams" }), Schema.Struct({ - responseStreamConnectionFailed: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), + accessToken: Schema.String.annotate({ + description: + "Access token (JWT) supplied by the client. This token is used for backend API requests and email extraction.", }), - }).annotate({ - title: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), + chatgptAccountId: Schema.String.annotate({ + description: "Workspace/account identifier supplied by the client.", + }), + chatgptPlanType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional plan type supplied by the client.\n\nWhen `null`, Codex attempts to derive the plan type from access-token claims. If unavailable, the plan defaults to `unknown`.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("chatgptAuthTokens").annotate({ + title: "ChatgptAuthTokensLoginAccountParamsType", }), }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", + title: "ChatgptAuthTokensLoginAccountParams", description: - "The response SSE stream disconnected in the middle of a turn before completion.", + "[UNSTABLE] FOR OPENAI INTERNAL USE ONLY - DO NOT USE. The access token must contain the same scopes that Codex-managed ChatGPT auth tokens have.", }), Schema.Struct({ - responseTooManyFailedAttempts: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), + apiKey: Schema.String, + region: Schema.String, + type: Schema.Literal("amazonBedrock").annotate({ + title: "AmazonBedrockLoginAccountParamsType", }), }).annotate({ - title: "ResponseTooManyFailedAttemptsCodexErrorInfo", - description: "Reached the retry limit for responses.", + title: "AmazonBedrockLoginAccountParams", + description: "[UNSTABLE] Managed Amazon Bedrock login is experimental.", }), Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ turnKind: ServerNotification__NonSteerableTurnKind }), + accessKeyId: Schema.String, + region: Schema.String, + secretAccessKey: Schema.String, + sessionToken: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("amazonBedrockAccessKeys").annotate({ + title: "AmazonBedrockAccessKeysLoginAccountParamsType", + }), }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", + title: "AmazonBedrockAccessKeysLoginAccountParams", + description: "[UNSTABLE] Managed Amazon Bedrock AWS access key login is experimental.", }), ], { mode: "oneOf" }, -).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.", -}); +).annotate({ identifier: "ClientRequest__LoginAccountParams" }); -export type ServerNotification__FileUpdateChange = { - readonly diff: string; - readonly kind: ServerNotification__PatchChangeKind; - readonly path: string; +export type ClientRequest__SendAddCreditsNudgeEmailParams = { + readonly creditType: ClientRequest__AddCreditsNudgeCreditType; }; -export const ServerNotification__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: ServerNotification__PatchChangeKind, - path: Schema.String, -}); +export const ClientRequest__SendAddCreditsNudgeEmailParams = Schema.Struct({ + creditType: ClientRequest__AddCreditsNudgeCreditType, +}).annotate({ identifier: "ClientRequest__SendAddCreditsNudgeEmailParams" }); -export type ServerNotification__AccountUpdatedNotification = { - readonly authMode?: ServerNotification__AuthMode | null; - readonly planType?: ServerNotification__PlanType | null; +export type ClientRequest__CommandExecResizeParams = { + readonly processId: string; + readonly size: ClientRequest__CommandExecTerminalSize; }; -export const ServerNotification__AccountUpdatedNotification = Schema.Struct({ - authMode: Schema.optionalKey(Schema.Union([ServerNotification__AuthMode, Schema.Null])), - planType: Schema.optionalKey(Schema.Union([ServerNotification__PlanType, Schema.Null])), +export const ClientRequest__CommandExecResizeParams = Schema.Struct({ + processId: Schema.String.annotate({ + description: + "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + }), + size: Schema.suspend( + (): Schema.Codec => + ClientRequest__CommandExecTerminalSize, + ).annotate({ description: "New PTY size in character cells." }), +}).annotate({ + description: "Resize a running PTY-backed `command/exec` session.", + identifier: "ClientRequest__CommandExecResizeParams", }); -export type ServerNotification__ThreadRealtimeStartedNotification = { - readonly realtimeSessionId?: string | null; - readonly threadId: string; - readonly version: ServerNotification__RealtimeConversationVersion; -}; -export const ServerNotification__ThreadRealtimeStartedNotification = Schema.Struct({ - realtimeSessionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - threadId: Schema.String, - version: ServerNotification__RealtimeConversationVersion, -}).annotate({ description: "EXPERIMENTAL - emitted when thread realtime startup is accepted." }); - -export type ServerNotification__Settings = { - readonly developer_instructions?: string | null; - readonly model: string; - readonly reasoning_effort?: ServerNotification__ReasoningEffort | null; +export type ClientRequest__MigrationDetails = { + readonly commands?: ReadonlyArray; + readonly hooks?: ReadonlyArray; + readonly mcpServers?: ReadonlyArray; + readonly memory?: ReadonlyArray; + readonly plugins?: ReadonlyArray; + readonly sessions?: ReadonlyArray; + readonly skills?: ReadonlyArray; + readonly subagents?: ReadonlyArray; }; -export const ServerNotification__Settings = Schema.Struct({ - developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - model: Schema.String, - reasoning_effort: Schema.optionalKey( - Schema.Union([ServerNotification__ReasoningEffort, Schema.Null]), +export const ClientRequest__MigrationDetails = Schema.Struct({ + commands: Schema.optionalKey( + Schema.Array(ClientRequest__CommandMigration).annotate({ default: [] }), + ), + hooks: Schema.optionalKey(Schema.Array(ClientRequest__HookMigration).annotate({ default: [] })), + mcpServers: Schema.optionalKey( + Schema.Array(ClientRequest__McpServerMigration).annotate({ default: [] }), + ), + memory: Schema.optionalKey(Schema.Array(Schema.String)), + plugins: Schema.optionalKey( + Schema.Array(ClientRequest__PluginsMigration).annotate({ default: [] }), + ), + sessions: Schema.optionalKey( + Schema.Array(ClientRequest__SessionMigration).annotate({ default: [] }), ), -}).annotate({ description: "Settings for a collaboration mode." }); + skills: Schema.optionalKey(Schema.Array(ClientRequest__SkillMigration).annotate({ default: [] })), + subagents: Schema.optionalKey( + Schema.Array(ClientRequest__SubagentMigration).annotate({ default: [] }), + ), +}).annotate({ identifier: "ClientRequest__MigrationDetails" }); -export type ServerNotification__RemoteControlStatusChangedNotification = { - readonly environmentId?: string | null; - readonly installationId: string; - readonly serverName: string; - readonly status: ServerNotification__RemoteControlConnectionStatus; +export type ClientRequest__ExternalAgentConfigImportItemTypeFailure = { + readonly cwd?: string | null; + readonly errorType?: string | null; + readonly failureStage: string; + readonly itemType: ClientRequest__ExternalAgentConfigMigrationItemType; + readonly message: string; + readonly source?: string | null; + readonly subErrorType?: string | null; }; -export const ServerNotification__RemoteControlStatusChangedNotification = Schema.Struct({ - environmentId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - installationId: Schema.String, - serverName: Schema.String, - status: ServerNotification__RemoteControlConnectionStatus, -}).annotate({ - description: "Current remote-control connection status and remote identity exposed to clients.", -}); +export const ClientRequest__ExternalAgentConfigImportItemTypeFailure = Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + failureStage: Schema.String, + itemType: ClientRequest__ExternalAgentConfigMigrationItemType, + message: Schema.String, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ClientRequest__ExternalAgentConfigImportItemTypeFailure" }); -export type ServerNotification__ServerRequestResolvedNotification = { - readonly requestId: ServerNotification__RequestId; - readonly threadId: string; +export type ClientRequest__ExternalAgentConfigImportHistoryRecordSuccessParams = { + readonly cwd?: string | null; + readonly itemType: ClientRequest__ExternalAgentConfigMigrationItemType; + readonly source?: string | null; + readonly target?: string | null; + readonly title?: string | null; }; -export const ServerNotification__ServerRequestResolvedNotification = Schema.Struct({ - requestId: ServerNotification__RequestId, - threadId: Schema.String, -}); +export const ClientRequest__ExternalAgentConfigImportHistoryRecordSuccessParams = Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + itemType: ClientRequest__ExternalAgentConfigMigrationItemType, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Original title for an imported session, when available.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__ExternalAgentConfigImportHistoryRecordSuccessParams" }); -export type ServerNotification__RateLimitSnapshot = { - readonly credits?: ServerNotification__CreditsSnapshot | null; - readonly individualLimit?: ServerNotification__SpendControlLimitSnapshot | null; - readonly limitId?: string | null; - readonly limitName?: string | null; - readonly planType?: ServerNotification__PlanType | null; - readonly primary?: ServerNotification__RateLimitWindow | null; - readonly rateLimitReachedType?: ServerNotification__RateLimitReachedType | null; - readonly secondary?: ServerNotification__RateLimitWindow | null; - readonly spendControlReached?: boolean | null; +export type ClientRequest__ConfigValueWriteParams = { + readonly expectedVersion?: string | null; + readonly filePath?: string | null; + readonly keyPath: string; + readonly mergeStrategy: ClientRequest__MergeStrategy; + readonly value: Schema.Json; }; -export const ServerNotification__RateLimitSnapshot = Schema.Struct({ - credits: Schema.optionalKey(Schema.Union([ServerNotification__CreditsSnapshot, Schema.Null])), - individualLimit: Schema.optionalKey( - Schema.Union([ServerNotification__SpendControlLimitSnapshot, Schema.Null]), - ), - limitId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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])), - spendControlReached: Schema.optionalKey( +export const ClientRequest__ConfigValueWriteParams = Schema.Struct({ + expectedVersion: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + filePath: Schema.optionalKey( Schema.Union([ - Schema.Boolean.annotate({ + Schema.String.annotate({ description: - "Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.", + "Path to the config file to write; defaults to the user's `config.toml` when omitted.", }), Schema.Null, ]), ), -}); + keyPath: Schema.String, + mergeStrategy: ClientRequest__MergeStrategy, + value: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ identifier: "ClientRequest__ConfigValueWriteParams" }); -export type ServerNotification__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } +export type ClientRequest__ConfigEdit = { + readonly keyPath: string; + readonly mergeStrategy: ClientRequest__MergeStrategy; + readonly value: Schema.Json; +}; +export const ClientRequest__ConfigEdit = Schema.Struct({ + keyPath: Schema.String, + mergeStrategy: ClientRequest__MergeStrategy, + value: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ identifier: "ClientRequest__ConfigEdit" }); + +export type CommandExecutionRequestApprovalParams__CommandAction = | { - readonly detail?: ServerNotification__ImageDetail | null; - readonly type: "image"; - readonly url: string; + readonly command: string; + readonly name: string; + readonly path: CommandExecutionRequestApprovalParams__LegacyAppPathString; + readonly type: "read"; } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } | { - readonly detail?: ServerNotification__ImageDetail | null; - readonly path: string; - readonly type: "localImage"; + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; } - | { readonly type: "audio"; readonly url: string } - | { readonly path: string; readonly type: "localAudio" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const ServerNotification__UserInput = Schema.Union( + | { readonly command: string; readonly type: "unknown" }; +export const CommandExecutionRequestApprovalParams__CommandAction = Schema.Union( [ Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(ServerNotification__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({ - detail: Schema.optionalKey(Schema.Union([ServerNotification__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([ServerNotification__ImageDetail, Schema.Null])), - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), - Schema.Struct({ - type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), - url: Schema.String, - }).annotate({ title: "AudioUserInput" }), + command: Schema.String, + name: Schema.String, + path: CommandExecutionRequestApprovalParams__LegacyAppPathString, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), + command: Schema.String, + path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), Schema.Struct({ - name: Schema.String, - path: Schema.String, - type: Schema.Literal("skill").annotate({ title: "SkillUserInputType" }), - }).annotate({ title: "SkillUserInput" }), + 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({ - 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 ServerNotification__TextRange = { - readonly end: ServerNotification__TextPosition; - readonly start: ServerNotification__TextPosition; -}; -export const ServerNotification__TextRange = Schema.Struct({ - end: ServerNotification__TextPosition, - start: ServerNotification__TextPosition, -}); +).annotate({ identifier: "CommandExecutionRequestApprovalParams__CommandAction" }); -export type ServerNotification__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } +export type CommandExecutionRequestApprovalParams__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; + readonly kind: "project_roots"; + readonly subpath?: CommandExecutionRequestApprovalParams__LegacyAppPathString | null; + } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { + readonly kind: "unknown"; + readonly path: string; + readonly subpath?: CommandExecutionRequestApprovalParams__LegacyAppPathString | null; }; -export const ServerNotification__ThreadStatus = Schema.Union( +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({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey( + Schema.Union([CommandExecutionRequestApprovalParams__LegacyAppPathString, 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({ - activeFlags: Schema.Array(ServerNotification__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey( + Schema.Union([CommandExecutionRequestApprovalParams__LegacyAppPathString, Schema.Null]), + ), + }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "CommandExecutionRequestApprovalParams__FileSystemSpecialPath" }); -export type ServerNotification__ThreadGoal = { - readonly createdAt: number; - readonly objective: string; - readonly status: ServerNotification__ThreadGoalStatus; - readonly threadId: string; - readonly timeUsedSeconds: number; - readonly tokenBudget?: number | null; - readonly tokensUsed: number; - readonly updatedAt: number; +export type CommandExecutionRequestApprovalParams__NetworkApprovalContext = { + readonly host: string; + readonly protocol: CommandExecutionRequestApprovalParams__NetworkApprovalProtocol; }; -export const ServerNotification__ThreadGoal = Schema.Struct({ - createdAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - objective: Schema.String, - status: ServerNotification__ThreadGoalStatus, - threadId: Schema.String, - timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - tokenBudget: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - tokensUsed: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - updatedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -}); +export const CommandExecutionRequestApprovalParams__NetworkApprovalContext = Schema.Struct({ + host: Schema.String, + protocol: CommandExecutionRequestApprovalParams__NetworkApprovalProtocol, +}).annotate({ identifier: "CommandExecutionRequestApprovalParams__NetworkApprovalContext" }); -export type ServerNotification__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" - | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: ServerNotification__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: ServerNotification__ThreadId; - }; - } - | { readonly other: string }; -export const ServerNotification__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([ServerNotification__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: ServerNotification__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), - ], - { mode: "oneOf" }, -); - -export type ServerNotification__ThreadRealtimeOutputAudioDeltaNotification = { - readonly audio: ServerNotification__ThreadRealtimeAudioChunk; - readonly threadId: string; +export type CommandExecutionRequestApprovalParams__NetworkPolicyAmendment = { + readonly action: CommandExecutionRequestApprovalParams__NetworkPolicyRuleAction; + readonly host: string; }; -export const ServerNotification__ThreadRealtimeOutputAudioDeltaNotification = Schema.Struct({ - audio: ServerNotification__ThreadRealtimeAudioChunk, - threadId: Schema.String, -}).annotate({ description: "EXPERIMENTAL - streamed output audio emitted by thread realtime." }); +export const CommandExecutionRequestApprovalParams__NetworkPolicyAmendment = Schema.Struct({ + action: CommandExecutionRequestApprovalParams__NetworkPolicyRuleAction, + host: Schema.String, +}).annotate({ identifier: "CommandExecutionRequestApprovalParams__NetworkPolicyAmendment" }); -export type ServerNotification__ThreadTokenUsage = { - readonly last: ServerNotification__TokenUsageBreakdown; - readonly modelContextWindow?: number | null; - readonly total: ServerNotification__TokenUsageBreakdown; +export type CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment = { + readonly action: CommandExecutionRequestApprovalResponse__NetworkPolicyRuleAction; + readonly host: string; }; -export const ServerNotification__ThreadTokenUsage = Schema.Struct({ - last: ServerNotification__TokenUsageBreakdown, - modelContextWindow: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), - ), - total: ServerNotification__TokenUsageBreakdown, -}); +export const CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment = Schema.Struct({ + action: CommandExecutionRequestApprovalResponse__NetworkPolicyRuleAction, + host: Schema.String, +}).annotate({ identifier: "CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment" }); -export type ServerNotification__TurnPlanStep = { - readonly status: ServerNotification__TurnPlanStepStatus; - readonly step: string; +export type ExecCommandApprovalResponse__NetworkPolicyAmendment = { + readonly action: ExecCommandApprovalResponse__NetworkPolicyRuleAction; + readonly host: string; }; -export const ServerNotification__TurnPlanStep = Schema.Struct({ - status: ServerNotification__TurnPlanStepStatus, - step: Schema.String, -}); +export const ExecCommandApprovalResponse__NetworkPolicyAmendment = Schema.Struct({ + action: ExecCommandApprovalResponse__NetworkPolicyRuleAction, + host: Schema.String, +}).annotate({ identifier: "ExecCommandApprovalResponse__NetworkPolicyAmendment" }); -export type ServerNotification__WindowsSandboxSetupCompletedNotification = { - readonly error?: string | null; - readonly mode: ServerNotification__WindowsSandboxSetupMode; - readonly success: boolean; +export type FuzzyFileSearchResponse__FuzzyFileSearchResult = { + readonly file_name: string; + readonly indices?: ReadonlyArray | null; + readonly match_type: FuzzyFileSearchResponse__FuzzyFileSearchMatchType; + readonly path: string; + readonly root: string; + readonly score: number; }; -export const ServerNotification__WindowsSandboxSetupCompletedNotification = Schema.Struct({ - error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - mode: ServerNotification__WindowsSandboxSetupMode, - success: Schema.Boolean, +export const FuzzyFileSearchResponse__FuzzyFileSearchResult = Schema.Struct({ + file_name: Schema.String, + indices: Schema.optionalKey( + Schema.Union([ + Schema.Array( + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + ), + Schema.Null, + ]), + ), + match_type: FuzzyFileSearchResponse__FuzzyFileSearchMatchType, + path: Schema.String, + root: Schema.String, + score: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ + description: "Superset of [`codex_file_search::FileMatch`]", + identifier: "FuzzyFileSearchResponse__FuzzyFileSearchResult", }); -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; - readonly reason: ServerRequest__ChatgptAuthTokensRefreshReason; +export type FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchResult = { + readonly file_name: string; + readonly indices?: ReadonlyArray | null; + readonly match_type: FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType; + readonly path: string; + readonly root: string; + readonly score: number; }; -export const ServerRequest__ChatgptAuthTokensRefreshParams = Schema.Struct({ - previousAccountId: Schema.optionalKey( +export const FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchResult = Schema.Struct({ + file_name: Schema.String, + indices: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: - "Workspace/account identifier that Codex was previously using.\n\nClients that manage multiple accounts/workspaces can use this as a hint to refresh the token for the correct workspace.\n\nThis may be `null` when the prior auth state did not include a workspace identifier (`chatgpt_account_id`).", - }), + Schema.Array( + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + ), Schema.Null, ]), ), - reason: ServerRequest__ChatgptAuthTokensRefreshReason, + match_type: FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchMatchType, + path: Schema.String, + root: Schema.String, + score: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ + description: "Superset of [`codex_file_search::FileMatch`]", + identifier: "FuzzyFileSearchSessionUpdatedNotification__FuzzyFileSearchResult", }); -export type ServerRequest__FileSystemSpecialPath = - | { readonly kind: "root" } - | { readonly kind: "minimal" } - | { readonly kind: "project_roots"; readonly subpath?: ServerRequest__LegacyAppPathString | null } - | { readonly kind: "tmpdir" } - | { readonly kind: "slash_tmp" } - | { - readonly kind: "unknown"; - readonly path: string; - readonly subpath?: ServerRequest__LegacyAppPathString | 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("project_roots"), - subpath: Schema.optionalKey(Schema.Union([ServerRequest__LegacyAppPathString, 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([ServerRequest__LegacyAppPathString, Schema.Null])), - }), - ], - { mode: "oneOf" }, -); +export type JSONRPCMessage__JSONRPCResponse = { + readonly id: JSONRPCMessage__RequestId; + readonly result: Schema.Json; +}; +export const JSONRPCMessage__JSONRPCResponse = Schema.Struct({ + id: JSONRPCMessage__RequestId, + result: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ + description: "A successful (non-error) response to a request.", + identifier: "JSONRPCMessage__JSONRPCResponse", +}); -export type ServerRequest__McpElicitationBooleanSchema = { - readonly default?: boolean | null; - readonly description?: string | null; - readonly title?: string | null; - readonly type: ServerRequest__McpElicitationBooleanType; +export type JSONRPCMessage__JSONRPCRequest = { + readonly id: JSONRPCMessage__RequestId; + readonly method: string; + readonly params?: Schema.Json; + readonly trace?: JSONRPCMessage__W3cTraceContext | null; }; -export const ServerRequest__McpElicitationBooleanSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationBooleanType, +export const JSONRPCMessage__JSONRPCRequest = Schema.Struct({ + id: JSONRPCMessage__RequestId, + method: Schema.String, + params: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + trace: Schema.optionalKey( + Schema.Union([JSONRPCMessage__W3cTraceContext, Schema.Null]).annotate({ + description: "Optional W3C Trace Context for distributed tracing.", + }), + ), +}).annotate({ + description: "A request that expects a response.", + identifier: "JSONRPCMessage__JSONRPCRequest", }); -export type ServerRequest__McpElicitationTitledEnumItems = { - readonly anyOf: ReadonlyArray; +export type JSONRPCMessage__JSONRPCError = { + readonly error: JSONRPCMessage__JSONRPCErrorError; + readonly id: JSONRPCMessage__RequestId; }; -export const ServerRequest__McpElicitationTitledEnumItems = Schema.Struct({ - anyOf: Schema.Array(ServerRequest__McpElicitationConstOption), +export const JSONRPCMessage__JSONRPCError = Schema.Struct({ + error: JSONRPCMessage__JSONRPCErrorError, + id: JSONRPCMessage__RequestId, +}).annotate({ + description: "A response to a request that indicates an error occurred.", + identifier: "JSONRPCMessage__JSONRPCError", }); -export type ServerRequest__McpElicitationNumberSchema = { - readonly default?: number | null; +export type McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema = { + readonly default?: string | null; readonly description?: string | null; - readonly maximum?: number | null; - readonly minimum?: number | null; + readonly enum: ReadonlyArray; readonly title?: string | null; - readonly type: ServerRequest__McpElicitationNumberType; + readonly type: McpServerElicitationRequestParams__McpElicitationStringType; }; -export const ServerRequest__McpElicitationNumberSchema = Schema.Struct({ - default: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "double" }).check(Schema.isFinite()), - Schema.Null, - ]), - ), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - maximum: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "double" }).check(Schema.isFinite()), - Schema.Null, - ]), - ), - minimum: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "double" }).check(Schema.isFinite()), - Schema.Null, - ]), - ), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationNumberType, -}); +export const McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema = + Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + enum: Schema.Array(Schema.String), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: McpServerElicitationRequestParams__McpElicitationStringType, + }).annotate({ + identifier: "McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema", + }); -export type ServerRequest__McpElicitationLegacyTitledEnumSchema = { +export type McpServerElicitationRequestParams__McpElicitationUntitledEnumItems = { + readonly enum: ReadonlyArray; + readonly type: McpServerElicitationRequestParams__McpElicitationStringType; +}; +export const McpServerElicitationRequestParams__McpElicitationUntitledEnumItems = Schema.Struct({ + enum: Schema.Array(Schema.String), + type: McpServerElicitationRequestParams__McpElicitationStringType, +}).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationUntitledEnumItems" }); + +export type McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema = { readonly default?: string | null; readonly description?: string | null; readonly enum: ReadonlyArray; readonly enumNames?: ReadonlyArray | null; readonly title?: string | null; - readonly type: ServerRequest__McpElicitationStringType; + readonly type: McpServerElicitationRequestParams__McpElicitationStringType; }; -export const ServerRequest__McpElicitationLegacyTitledEnumSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - enum: Schema.Array(Schema.String), - enumNames: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationStringType, -}); +export const McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema = + Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + enum: Schema.Array(Schema.String), + enumNames: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: McpServerElicitationRequestParams__McpElicitationStringType, + }).annotate({ + identifier: "McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema", + }); -export type ServerRequest__McpElicitationStringSchema = { +export type McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema = { readonly default?: string | null; readonly description?: string | null; - readonly format?: ServerRequest__McpElicitationStringFormat | null; + readonly oneOf: ReadonlyArray; + readonly title?: string | null; + readonly type: McpServerElicitationRequestParams__McpElicitationStringType; +}; +export const McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema = + Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + oneOf: Schema.Array(McpServerElicitationRequestParams__McpElicitationConstOption), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: McpServerElicitationRequestParams__McpElicitationStringType, + }).annotate({ + identifier: "McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema", + }); + +export type McpServerElicitationRequestParams__McpElicitationTitledEnumItems = { + readonly anyOf: ReadonlyArray; +}; +export const McpServerElicitationRequestParams__McpElicitationTitledEnumItems = Schema.Struct({ + anyOf: Schema.Array(McpServerElicitationRequestParams__McpElicitationConstOption), +}).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationTitledEnumItems" }); + +export type McpServerElicitationRequestParams__McpElicitationStringSchema = { + readonly default?: string | null; + readonly description?: string | null; + readonly format?: McpServerElicitationRequestParams__McpElicitationStringFormat | null; readonly maxLength?: number | null; readonly minLength?: number | null; readonly title?: string | null; - readonly type: ServerRequest__McpElicitationStringType; + readonly type: McpServerElicitationRequestParams__McpElicitationStringType; }; -export const ServerRequest__McpElicitationStringSchema = Schema.Struct({ +export const McpServerElicitationRequestParams__McpElicitationStringSchema = Schema.Struct({ default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), format: Schema.optionalKey( - Schema.Union([ServerRequest__McpElicitationStringFormat, Schema.Null]), + Schema.Union([McpServerElicitationRequestParams__McpElicitationStringFormat, Schema.Null]), ), maxLength: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), minLength: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationStringType, -}); - -export type ServerRequest__McpElicitationTitledSingleSelectEnumSchema = { - readonly default?: string | null; - readonly description?: string | null; - readonly oneOf: ReadonlyArray; - readonly title?: string | null; - readonly type: ServerRequest__McpElicitationStringType; -}; -export const ServerRequest__McpElicitationTitledSingleSelectEnumSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - oneOf: Schema.Array(ServerRequest__McpElicitationConstOption), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationStringType, -}); - -export type ServerRequest__McpElicitationUntitledEnumItems = { - readonly enum: ReadonlyArray; - readonly type: ServerRequest__McpElicitationStringType; -}; -export const ServerRequest__McpElicitationUntitledEnumItems = Schema.Struct({ - enum: Schema.Array(Schema.String), - type: ServerRequest__McpElicitationStringType, -}); + type: McpServerElicitationRequestParams__McpElicitationStringType, +}).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationStringSchema" }); -export type ServerRequest__McpElicitationUntitledSingleSelectEnumSchema = { - readonly default?: string | null; +export type McpServerElicitationRequestParams__McpElicitationNumberSchema = { + readonly default?: number | null; readonly description?: string | null; - readonly enum: ReadonlyArray; + readonly maximum?: number | null; + readonly minimum?: number | null; readonly title?: string | null; - readonly type: ServerRequest__McpElicitationStringType; -}; -export const ServerRequest__McpElicitationUntitledSingleSelectEnumSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - enum: Schema.Array(Schema.String), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationStringType, -}); - -export type ServerRequest__NetworkApprovalContext = { - readonly host: string; - readonly protocol: ServerRequest__NetworkApprovalProtocol; -}; -export const ServerRequest__NetworkApprovalContext = Schema.Struct({ - host: Schema.String, - protocol: ServerRequest__NetworkApprovalProtocol, -}); - -export type ServerRequest__NetworkPolicyAmendment = { - readonly action: ServerRequest__NetworkPolicyRuleAction; - readonly host: string; -}; -export const ServerRequest__NetworkPolicyAmendment = Schema.Struct({ - action: ServerRequest__NetworkPolicyRuleAction, - host: Schema.String, -}); - -export type ServerRequest__ApplyPatchApprovalParams = { - readonly callId: string; - readonly conversationId: ServerRequest__ThreadId; - readonly fileChanges: { readonly [x: string]: ServerRequest__FileChange }; - readonly grantRoot?: string | null; - readonly reason?: string | null; + readonly type: McpServerElicitationRequestParams__McpElicitationNumberType; }; -export const ServerRequest__ApplyPatchApprovalParams = Schema.Struct({ - callId: Schema.String.annotate({ - description: - "Use to correlate this with [codex_protocol::protocol::PatchApplyBeginEvent] and [codex_protocol::protocol::PatchApplyEndEvent].", - }), - conversationId: ServerRequest__ThreadId, - fileChanges: Schema.Record(Schema.String, ServerRequest__FileChange), - grantRoot: Schema.optionalKey( +export const McpServerElicitationRequestParams__McpElicitationNumberSchema = Schema.Struct({ + default: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: - "When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", - }), + Schema.Number.annotate({ format: "double" }).check( + Schema.isFinite().annotate({ expected: "a finite number" }), + ), Schema.Null, ]), ), - reason: Schema.optionalKey( + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + maximum: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: "Optional explanatory reason (e.g. request for extra write access).", - }), + Schema.Number.annotate({ format: "double" }).check( + Schema.isFinite().annotate({ expected: "a finite number" }), + ), Schema.Null, ]), ), -}); - -export type ServerRequest__ExecCommandApprovalParams = { - readonly approvalId?: string | null; - readonly callId: string; - readonly command: ReadonlyArray; - readonly conversationId: ServerRequest__ThreadId; - readonly cwd: string; - readonly parsedCmd: ReadonlyArray; - readonly reason?: string | null; -}; -export const ServerRequest__ExecCommandApprovalParams = Schema.Struct({ - approvalId: Schema.optionalKey( + minimum: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Identifier for this specific approval callback." }), + Schema.Number.annotate({ format: "double" }).check( + Schema.isFinite().annotate({ expected: "a finite number" }), + ), Schema.Null, ]), ), - callId: Schema.String.annotate({ - description: - "Use to correlate this with [codex_protocol::protocol::ExecCommandBeginEvent] and [codex_protocol::protocol::ExecCommandEndEvent].", - }), - command: Schema.Array(Schema.String), - conversationId: ServerRequest__ThreadId, - cwd: Schema.String, - parsedCmd: Schema.Array(ServerRequest__ParsedCommand), - reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: McpServerElicitationRequestParams__McpElicitationNumberType, +}).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationNumberSchema" }); -export type ServerRequest__ToolRequestUserInputQuestion = { - readonly header: string; - readonly id: string; - readonly isOther?: boolean; - readonly isSecret?: boolean; - readonly options?: ReadonlyArray | null; - readonly question: string; +export type McpServerElicitationRequestParams__McpElicitationBooleanSchema = { + readonly default?: boolean | null; + readonly description?: string | null; + readonly title?: string | null; + readonly type: McpServerElicitationRequestParams__McpElicitationBooleanType; }; -export const ServerRequest__ToolRequestUserInputQuestion = Schema.Struct({ - header: Schema.String, - id: Schema.String, - isOther: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - isSecret: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - options: Schema.optionalKey( - Schema.Union([Schema.Array(ServerRequest__ToolRequestUserInputOption), Schema.Null]), - ), - question: Schema.String, -}).annotate({ - description: "EXPERIMENTAL. Represents one request_user_input question and its required options.", -}); +export const McpServerElicitationRequestParams__McpElicitationBooleanSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: McpServerElicitationRequestParams__McpElicitationBooleanType, +}).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationBooleanSchema" }); -export type ToolRequestUserInputParams__ToolRequestUserInputQuestion = { - readonly header: string; - readonly id: string; - readonly isOther?: boolean; - readonly isSecret?: boolean; - readonly options?: ReadonlyArray | null; - readonly question: string; -}; -export const ToolRequestUserInputParams__ToolRequestUserInputQuestion = Schema.Struct({ - header: Schema.String, - id: Schema.String, - isOther: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - isSecret: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - options: Schema.optionalKey( - Schema.Union([ - Schema.Array(ToolRequestUserInputParams__ToolRequestUserInputOption), - Schema.Null, - ]), - ), - question: Schema.String, -}).annotate({ - description: "EXPERIMENTAL. Represents one request_user_input question and its required options.", -}); - -export type V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot = { - readonly credits?: V2AccountRateLimitsUpdatedNotification__CreditsSnapshot | null; - readonly individualLimit?: V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot | null; - readonly limitId?: string | null; - readonly limitName?: string | null; - readonly planType?: V2AccountRateLimitsUpdatedNotification__PlanType | null; - readonly primary?: V2AccountRateLimitsUpdatedNotification__RateLimitWindow | null; - readonly rateLimitReachedType?: V2AccountRateLimitsUpdatedNotification__RateLimitReachedType | null; - readonly secondary?: V2AccountRateLimitsUpdatedNotification__RateLimitWindow | null; - readonly spendControlReached?: boolean | null; -}; -export const V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot = Schema.Struct({ - credits: Schema.optionalKey( - Schema.Union([V2AccountRateLimitsUpdatedNotification__CreditsSnapshot, Schema.Null]), - ), - individualLimit: Schema.optionalKey( - Schema.Union([V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot, Schema.Null]), - ), - limitId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - limitName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - planType: Schema.optionalKey( - Schema.Union([V2AccountRateLimitsUpdatedNotification__PlanType, Schema.Null]), - ), - 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]), - ), - spendControlReached: Schema.optionalKey( - Schema.Union([ - Schema.Boolean.annotate({ - description: - "Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.", - }), - Schema.Null, - ]), - ), -}); - -export type V2AppListUpdatedNotification__AppMetadata = { - readonly categories?: ReadonlyArray | null; - readonly developer?: string | null; - readonly firstPartyRequiresInstall?: boolean | null; - readonly firstPartyType?: string | null; - readonly review?: V2AppListUpdatedNotification__AppReview | null; - readonly screenshots?: ReadonlyArray | null; - readonly seoDescription?: string | null; - readonly showInComposerWhenUnlinked?: boolean | null; - readonly subCategories?: ReadonlyArray | null; - readonly version?: string | null; - readonly versionId?: string | null; - readonly versionNotes?: string | null; -}; -export const V2AppListUpdatedNotification__AppMetadata = Schema.Struct({ - categories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - firstPartyRequiresInstall: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - firstPartyType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - review: Schema.optionalKey(Schema.Union([V2AppListUpdatedNotification__AppReview, Schema.Null])), - screenshots: Schema.optionalKey( - Schema.Union([Schema.Array(V2AppListUpdatedNotification__AppScreenshot), Schema.Null]), - ), - seoDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - showInComposerWhenUnlinked: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - subCategories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - version: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - versionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - versionNotes: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2AppsListResponse__AppMetadata = { - readonly categories?: ReadonlyArray | null; - readonly developer?: string | null; - readonly firstPartyRequiresInstall?: boolean | null; - readonly firstPartyType?: string | null; - readonly review?: V2AppsListResponse__AppReview | null; - readonly screenshots?: ReadonlyArray | null; - readonly seoDescription?: string | null; - readonly showInComposerWhenUnlinked?: boolean | null; - readonly subCategories?: ReadonlyArray | null; - readonly version?: string | null; - readonly versionId?: string | null; - readonly versionNotes?: string | null; -}; -export const V2AppsListResponse__AppMetadata = Schema.Struct({ - categories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - firstPartyRequiresInstall: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - firstPartyType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - review: Schema.optionalKey(Schema.Union([V2AppsListResponse__AppReview, Schema.Null])), - screenshots: Schema.optionalKey( - Schema.Union([Schema.Array(V2AppsListResponse__AppScreenshot), Schema.Null]), - ), - seoDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - showInComposerWhenUnlinked: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - subCategories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - version: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - versionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - versionNotes: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2AppsReadResponse__ConnectorMetadata = { - readonly description?: string | null; - readonly iconUrl?: string | null; - readonly id: string; - readonly name: string; - readonly toolSummaries?: ReadonlyArray | null; -}; -export const V2AppsReadResponse__ConnectorMetadata = Schema.Struct({ - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - id: Schema.String, - name: Schema.String, - toolSummaries: Schema.optionalKey( - Schema.Union([Schema.Array(V2AppsReadResponse__AppToolSummary), Schema.Null]), - ), -}).annotate({ description: "EXPERIMENTAL - metadata returned by app/read." }); - -export type V2CommandExecParams__SandboxPolicy = - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const V2CommandExecParams__SandboxPolicy = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(V2CommandExecParams__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, -); - -export type V2ConfigBatchWriteParams__ConfigEdit = { - readonly keyPath: string; - readonly mergeStrategy: V2ConfigBatchWriteParams__MergeStrategy; - readonly value: unknown; -}; -export const V2ConfigBatchWriteParams__ConfigEdit = Schema.Struct({ - keyPath: Schema.String, - mergeStrategy: V2ConfigBatchWriteParams__MergeStrategy, - value: Schema.Unknown, -}); - -export type V2ConfigReadResponse__ConfigLayerSource = - | { readonly domain: string; readonly key: string; readonly type: "mdm" } - | { readonly file: string; readonly type: "system" } - | { readonly id: string; readonly name: string; readonly type: "enterpriseManaged" } - | { readonly file: string; readonly profile?: string | null; readonly type: "user" } - | { readonly dotCodexFolder: V2ConfigReadResponse__AbsolutePathBuf; readonly type: "project" } - | { readonly type: "sessionFlags" } +export type PermissionsRequestApprovalParams__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } | { - readonly file: V2ConfigReadResponse__AbsolutePathBuf; - readonly type: "legacyManagedConfigTomlFromFile"; + readonly kind: "project_roots"; + readonly subpath?: PermissionsRequestApprovalParams__LegacyAppPathString | null; } - | { readonly type: "legacyManagedConfigTomlFromMdm" }; -export const V2ConfigReadResponse__ConfigLayerSource = Schema.Union( + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { + readonly kind: "unknown"; + readonly path: string; + readonly subpath?: PermissionsRequestApprovalParams__LegacyAppPathString | null; + }; +export const PermissionsRequestApprovalParams__FileSystemSpecialPath = Schema.Union( [ - Schema.Struct({ - domain: Schema.String, - key: Schema.String, - type: Schema.Literal("mdm").annotate({ title: "MdmConfigLayerSourceType" }), - }).annotate({ - title: "MdmConfigLayerSource", - description: "Managed preferences layer delivered by MDM (macOS only).", - }), - Schema.Struct({ - file: 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.", - }), - type: Schema.Literal("system").annotate({ title: "SystemConfigLayerSourceType" }), - }).annotate({ - title: "SystemConfigLayerSource", - description: "Managed config layer from a file (usually `managed_config.toml`).", + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", }), - Schema.Struct({ - id: Schema.String.annotate({ description: "Stable identifier for the delivered layer." }), - name: Schema.String.annotate({ - description: - "Admin-facing name for the delivered layer. This is surfaced in diagnostics so users know which cloud layer needs administrator attention.", - }), - type: Schema.Literal("enterpriseManaged").annotate({ - title: "EnterpriseManagedConfigLayerSourceType", - }), - }).annotate({ - title: "EnterpriseManagedConfigLayerSource", - description: "Enterprise-managed config layer delivered by the cloud config bundle.", + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", }), Schema.Struct({ - file: 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.", - }), - profile: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.", - }), - Schema.Null, - ]), + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalParams__LegacyAppPathString, Schema.Null]), ), - type: Schema.Literal("user").annotate({ title: "UserConfigLayerSourceType" }), - }).annotate({ - title: "UserConfigLayerSource", - description: - "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", - }), - Schema.Struct({ - dotCodexFolder: V2ConfigReadResponse__AbsolutePathBuf, - type: Schema.Literal("project").annotate({ title: "ProjectConfigLayerSourceType" }), - }).annotate({ - title: "ProjectConfigLayerSource", - description: - "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", }), - Schema.Struct({ - type: Schema.Literal("sessionFlags").annotate({ title: "SessionFlagsConfigLayerSourceType" }), - }).annotate({ - title: "SessionFlagsConfigLayerSource", - description: "Session-layer overrides supplied via `-c`/`--config`.", + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", }), Schema.Struct({ - file: V2ConfigReadResponse__AbsolutePathBuf, - type: Schema.Literal("legacyManagedConfigTomlFromFile").annotate({ - title: "LegacyManagedConfigTomlFromFileConfigLayerSourceType", - }), - }).annotate({ - title: "LegacyManagedConfigTomlFromFileConfigLayerSource", - description: - '`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a "best effort" while we phase out `managed_config.toml` in favor of `requirements.toml`.', + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalParams__LegacyAppPathString, Schema.Null]), + ), }), - Schema.Struct({ - type: Schema.Literal("legacyManagedConfigTomlFromMdm").annotate({ - title: "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", - }), - }).annotate({ title: "LegacyManagedConfigTomlFromMdmConfigLayerSource" }), ], { mode: "oneOf" }, -); - -export type V2ConfigReadResponse__AppsDefaultConfig = { - readonly approvals_reviewer?: V2ConfigReadResponse__ApprovalsReviewer | null; - readonly default_tools_approval_mode?: V2ConfigReadResponse__AppToolApproval | null; - readonly destructive_enabled?: boolean; - readonly enabled?: boolean; - readonly open_world_enabled?: boolean; -}; -export const V2ConfigReadResponse__AppsDefaultConfig = Schema.Struct({ - approvals_reviewer: Schema.optionalKey( - Schema.Union([V2ConfigReadResponse__ApprovalsReviewer, Schema.Null]), - ), - default_tools_approval_mode: Schema.optionalKey( - Schema.Union([V2ConfigReadResponse__AppToolApproval, Schema.Null]), - ), - destructive_enabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), - enabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), - open_world_enabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), -}); - -export type V2ConfigReadResponse__WebSearchToolConfig = { - readonly allowed_domains?: ReadonlyArray | null; - readonly context_size?: V2ConfigReadResponse__WebSearchContextSize | null; - readonly location?: V2ConfigReadResponse__WebSearchLocation | null; -}; -export const V2ConfigReadResponse__WebSearchToolConfig = Schema.Struct({ - allowed_domains: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - context_size: Schema.optionalKey( - Schema.Union([V2ConfigReadResponse__WebSearchContextSize, Schema.Null]), - ), - location: Schema.optionalKey( - Schema.Union([V2ConfigReadResponse__WebSearchLocation, Schema.Null]), - ), -}); - -export type V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup = { - readonly hooks: ReadonlyArray; - readonly matcher?: string | null; -}; -export const V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup = Schema.Struct({ - hooks: Schema.Array(V2ConfigRequirementsReadResponse__ConfiguredHookHandler), - matcher: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ConfigRequirementsReadResponse__NewThreadModelDefaults = { - readonly model?: string | null; - readonly modelReasoningEffort?: V2ConfigRequirementsReadResponse__ReasoningEffort | null; - readonly serviceTier?: string | null; -}; -export const V2ConfigRequirementsReadResponse__NewThreadModelDefaults = Schema.Struct({ - model: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - modelReasoningEffort: Schema.optionalKey( - Schema.Union([V2ConfigRequirementsReadResponse__ReasoningEffort, Schema.Null]), - ), - serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ConfigWarningNotification__TextRange = { - readonly end: V2ConfigWarningNotification__TextPosition; - readonly start: V2ConfigWarningNotification__TextPosition; -}; -export const V2ConfigWarningNotification__TextRange = Schema.Struct({ - end: V2ConfigWarningNotification__TextPosition, - start: V2ConfigWarningNotification__TextPosition, -}); +).annotate({ identifier: "PermissionsRequestApprovalParams__FileSystemSpecialPath" }); -export type V2ConfigWriteResponse__ConfigLayerSource = - | { readonly domain: string; readonly key: string; readonly type: "mdm" } - | { readonly file: string; readonly type: "system" } - | { readonly id: string; readonly name: string; readonly type: "enterpriseManaged" } - | { readonly file: string; readonly profile?: string | null; readonly type: "user" } - | { readonly dotCodexFolder: V2ConfigWriteResponse__AbsolutePathBuf; readonly type: "project" } - | { readonly type: "sessionFlags" } +export type PermissionsRequestApprovalResponse__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } | { - readonly file: V2ConfigWriteResponse__AbsolutePathBuf; - readonly type: "legacyManagedConfigTomlFromFile"; + readonly kind: "project_roots"; + readonly subpath?: PermissionsRequestApprovalResponse__LegacyAppPathString | null; } - | { readonly type: "legacyManagedConfigTomlFromMdm" }; -export const V2ConfigWriteResponse__ConfigLayerSource = Schema.Union( + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { + readonly kind: "unknown"; + readonly path: string; + readonly subpath?: PermissionsRequestApprovalResponse__LegacyAppPathString | null; + }; +export const PermissionsRequestApprovalResponse__FileSystemSpecialPath = Schema.Union( [ - Schema.Struct({ - domain: Schema.String, - key: Schema.String, - type: Schema.Literal("mdm").annotate({ title: "MdmConfigLayerSourceType" }), - }).annotate({ - title: "MdmConfigLayerSource", - description: "Managed preferences layer delivered by MDM (macOS only).", - }), - Schema.Struct({ - file: 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.", - }), - type: Schema.Literal("system").annotate({ title: "SystemConfigLayerSourceType" }), - }).annotate({ - title: "SystemConfigLayerSource", - description: "Managed config layer from a file (usually `managed_config.toml`).", + Schema.Struct({ kind: Schema.Literal("root") }).annotate({ + title: "RootFileSystemSpecialPath", }), - Schema.Struct({ - id: Schema.String.annotate({ description: "Stable identifier for the delivered layer." }), - name: Schema.String.annotate({ - description: - "Admin-facing name for the delivered layer. This is surfaced in diagnostics so users know which cloud layer needs administrator attention.", - }), - type: Schema.Literal("enterpriseManaged").annotate({ - title: "EnterpriseManagedConfigLayerSourceType", - }), - }).annotate({ - title: "EnterpriseManagedConfigLayerSource", - description: "Enterprise-managed config layer delivered by the cloud config bundle.", + Schema.Struct({ kind: Schema.Literal("minimal") }).annotate({ + title: "MinimalFileSystemSpecialPath", }), Schema.Struct({ - file: 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.", - }), - profile: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.", - }), - Schema.Null, - ]), + kind: Schema.Literal("project_roots"), + subpath: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalResponse__LegacyAppPathString, Schema.Null]), ), - type: Schema.Literal("user").annotate({ title: "UserConfigLayerSourceType" }), - }).annotate({ - title: "UserConfigLayerSource", - description: - "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", - }), - Schema.Struct({ - dotCodexFolder: V2ConfigWriteResponse__AbsolutePathBuf, - type: Schema.Literal("project").annotate({ title: "ProjectConfigLayerSourceType" }), - }).annotate({ - title: "ProjectConfigLayerSource", - description: - "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + }).annotate({ title: "KindFileSystemSpecialPath" }), + Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ + title: "TmpdirFileSystemSpecialPath", }), - Schema.Struct({ - type: Schema.Literal("sessionFlags").annotate({ title: "SessionFlagsConfigLayerSourceType" }), - }).annotate({ - title: "SessionFlagsConfigLayerSource", - description: "Session-layer overrides supplied via `-c`/`--config`.", + Schema.Struct({ kind: Schema.Literal("slash_tmp") }).annotate({ + title: "SlashTmpFileSystemSpecialPath", }), Schema.Struct({ - file: V2ConfigWriteResponse__AbsolutePathBuf, - type: Schema.Literal("legacyManagedConfigTomlFromFile").annotate({ - title: "LegacyManagedConfigTomlFromFileConfigLayerSourceType", - }), - }).annotate({ - title: "LegacyManagedConfigTomlFromFileConfigLayerSource", - description: - '`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a "best effort" while we phase out `managed_config.toml` in favor of `requirements.toml`.', + kind: Schema.Literal("unknown"), + path: Schema.String, + subpath: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalResponse__LegacyAppPathString, Schema.Null]), + ), }), - Schema.Struct({ - type: Schema.Literal("legacyManagedConfigTomlFromMdm").annotate({ - title: "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", - }), - }).annotate({ title: "LegacyManagedConfigTomlFromMdmConfigLayerSource" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "PermissionsRequestApprovalResponse__FileSystemSpecialPath" }); -export type V2ErrorNotification__CodexErrorInfo = +export type ServerNotification__CodexErrorInfo = | "contextWindowExceeded" | "sessionBudgetExceeded" | "usageLimitExceeded" + | "rateLimitExceeded" | "serverOverloaded" | "cyberPolicy" + | "misalignmentPolicyViolation" | "internalServerError" | "unauthorized" | "badRequest" @@ -13803,17 +17951,19 @@ export type V2ErrorNotification__CodexErrorInfo = | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } | { readonly activeTurnNotSteerable: { - readonly turnKind: V2ErrorNotification__NonSteerableTurnKind; + readonly turnKind: ServerNotification__NonSteerableTurnKind; }; }; -export const V2ErrorNotification__CodexErrorInfo = Schema.Union( +export const ServerNotification__CodexErrorInfo = Schema.Union( [ Schema.Literals([ "contextWindowExceeded", "sessionBudgetExceeded", "usageLimitExceeded", + "rateLimitExceeded", "serverOverloaded", "cyberPolicy", + "misalignmentPolicyViolation", "internalServerError", "unauthorized", "badRequest", @@ -13826,8 +17976,12 @@ export const V2ErrorNotification__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -13838,8 +17992,12 @@ export const V2ErrorNotification__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -13853,8 +18011,12 @@ export const V2ErrorNotification__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -13869,8 +18031,12 @@ export const V2ErrorNotification__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -13880,9 +18046,7 @@ export const V2ErrorNotification__CodexErrorInfo = Schema.Union( description: "Reached the retry limit for responses.", }), Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ErrorNotification__NonSteerableTurnKind, - }), + activeTurnNotSteerable: Schema.Struct({ turnKind: ServerNotification__NonSteerableTurnKind }), }).annotate({ title: "ActiveTurnNotSteerableCodexErrorInfo", description: @@ -13893,422 +18057,243 @@ export const V2ErrorNotification__CodexErrorInfo = Schema.Union( ).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.", + identifier: "ServerNotification__CodexErrorInfo", }); -export type V2ExternalAgentConfigDetectResponse__MigrationDetails = { - readonly commands?: ReadonlyArray; - readonly hooks?: ReadonlyArray; - readonly mcpServers?: ReadonlyArray; - readonly memory?: ReadonlyArray; - readonly plugins?: ReadonlyArray; - readonly sessions?: ReadonlyArray; - readonly skills?: ReadonlyArray; - readonly subagents?: ReadonlyArray; +export type ServerNotification__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: ServerNotification__MisalignmentSteer | null; }; -export const V2ExternalAgentConfigDetectResponse__MigrationDetails = Schema.Struct({ - commands: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigDetectResponse__CommandMigration).annotate({ default: [] }), - ), - hooks: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigDetectResponse__HookMigration).annotate({ default: [] }), +export const ServerNotification__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), ), - mcpServers: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigDetectResponse__McpServerMigration).annotate({ default: [] }), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), ), - memory: Schema.optionalKey(Schema.Array(Schema.String)), - plugins: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigDetectResponse__PluginsMigration).annotate({ default: [] }), + steer: Schema.optionalKey( + Schema.Union([ServerNotification__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), ), - sessions: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigDetectResponse__SessionMigration).annotate({ default: [] }), - ), - skills: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigDetectResponse__SkillMigration).annotate({ default: [] }), - ), - subagents: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigDetectResponse__SubagentMigration).annotate({ default: [] }), - ), -}); - -export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeFailure = - { - readonly cwd?: string | null; - readonly errorType?: string | null; - readonly failureStage: string; - readonly itemType: V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType; - readonly message: string; - readonly source?: string | null; - readonly subErrorType?: string | null; - }; -export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeFailure = - Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - failureStage: Schema.String, - itemType: - V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType, - message: Schema.String, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }); - -export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeSuccess = - { - readonly cwd?: string | null; - readonly itemType: V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType; - readonly source?: string | null; - readonly target?: string | null; - }; -export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeSuccess = - Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - itemType: - V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }); - -export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeFailure = - { - readonly cwd?: string | null; - readonly errorType?: string | null; - readonly failureStage: string; - readonly itemType: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType; - readonly message: string; - readonly source?: string | null; - readonly subErrorType?: string | null; - }; -export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeFailure = - Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - failureStage: Schema.String, - itemType: - V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType, - message: Schema.String, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }); - -export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeSuccess = - { - readonly cwd?: string | null; - readonly itemType: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType; - readonly source?: string | null; - readonly target?: string | null; - }; -export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeSuccess = - Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - itemType: - V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }); - -export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorCandidate = - { - readonly name: string; - readonly sessionCount: number; - readonly source: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorSource; - }; -export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorCandidate = - Schema.Struct({ - name: Schema.String, - sessionCount: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - source: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorSource, - }); +}).annotate({ identifier: "ServerNotification__MisalignmentErrorDetails" }); -export type V2ExternalAgentConfigImportParams__MigrationDetails = { - readonly commands?: ReadonlyArray; - readonly hooks?: ReadonlyArray; - readonly mcpServers?: ReadonlyArray; - readonly memory?: ReadonlyArray; - readonly plugins?: ReadonlyArray; - readonly sessions?: ReadonlyArray; - readonly skills?: ReadonlyArray; - readonly subagents?: ReadonlyArray; +export type ServerNotification__FsChangedNotification = { + readonly changedPaths: ReadonlyArray; + readonly watchId: string; }; -export const V2ExternalAgentConfigImportParams__MigrationDetails = Schema.Struct({ - commands: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigImportParams__CommandMigration).annotate({ default: [] }), - ), - hooks: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigImportParams__HookMigration).annotate({ default: [] }), - ), - mcpServers: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigImportParams__McpServerMigration).annotate({ default: [] }), - ), - memory: Schema.optionalKey(Schema.Array(Schema.String)), - plugins: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigImportParams__PluginsMigration).annotate({ default: [] }), - ), - sessions: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigImportParams__SessionMigration).annotate({ default: [] }), - ), - skills: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigImportParams__SkillMigration).annotate({ default: [] }), - ), - subagents: Schema.optionalKey( - Schema.Array(V2ExternalAgentConfigImportParams__SubagentMigration).annotate({ default: [] }), - ), +export const ServerNotification__FsChangedNotification = Schema.Struct({ + changedPaths: Schema.Array(ServerNotification__AbsolutePathBuf).annotate({ + description: "File or directory paths associated with this event.", + }), + watchId: Schema.String.annotate({ + description: "Watch identifier previously provided to `fs/watch`.", + }), +}).annotate({ + description: "Filesystem watch notification emitted for `fs/watch` subscribers.", + identifier: "ServerNotification__FsChangedNotification", }); -export type V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeFailure = - { - readonly cwd?: string | null; - readonly errorType?: string | null; - readonly failureStage: string; - readonly itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType; - readonly message: string; - readonly source?: string | null; - readonly subErrorType?: string | null; - }; -export const V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeFailure = - Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - failureStage: Schema.String, - itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType, - message: Schema.String, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }); - -export type V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeSuccess = - { - readonly cwd?: string | null; - readonly itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType; - readonly source?: string | null; - readonly target?: string | null; - }; -export const V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeSuccess = - Schema.Struct({ - cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType, - source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - }); - -export type V2FileChangePatchUpdatedNotification__FileUpdateChange = { - readonly diff: string; - readonly kind: V2FileChangePatchUpdatedNotification__PatchChangeKind; - readonly path: string; +export type ServerNotification__Settings = { + readonly developer_instructions?: string | null; + readonly model: string; + readonly reasoning_effort?: ServerNotification__ReasoningEffort | null; }; -export const V2FileChangePatchUpdatedNotification__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2FileChangePatchUpdatedNotification__PatchChangeKind, - path: Schema.String, +export const ServerNotification__Settings = Schema.Struct({ + developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + model: Schema.String, + reasoning_effort: Schema.optionalKey( + Schema.Union([ServerNotification__ReasoningEffort, Schema.Null]), + ), +}).annotate({ + description: "Settings for a collaboration mode.", + identifier: "ServerNotification__Settings", }); -export type V2GetAccountRateLimitsResponse__RateLimitResetCredit = { - readonly description?: string | null; - readonly expiresAt?: number | null; - readonly grantedAt: number; +export type ServerNotification__ThreadSection = { + readonly appearance?: ServerNotification__ThreadSectionAppearance | null; readonly id: string; - readonly resetType: V2GetAccountRateLimitsResponse__RateLimitResetType; - readonly status: V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus; - readonly title?: string | null; + readonly name: string; }; -export const V2GetAccountRateLimitsResponse__RateLimitResetCredit = Schema.Struct({ - description: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Backend-provided display description for this credit, or `null` when unavailable.", - }), - Schema.Null, - ]), - ), - expiresAt: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ - description: - "Unix timestamp in seconds when the credit expires, or `null` if it does not expire.", - format: "int64", - }).check(Schema.isInt()), - Schema.Null, - ]), - ), - grantedAt: Schema.Number.annotate({ - description: "Unix timestamp in seconds when the credit was granted.", - format: "int64", - }).check(Schema.isInt()), - id: Schema.String.annotate({ description: "Opaque backend identifier for this reset credit." }), - resetType: V2GetAccountRateLimitsResponse__RateLimitResetType, - status: V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus, - title: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Backend-provided display title for this credit, or `null` when unavailable.", - }), - Schema.Null, - ]), +export const ServerNotification__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([ServerNotification__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "ServerNotification__ThreadSection", }); -export type V2GetAccountRateLimitsResponse__RateLimitSnapshot = { - readonly credits?: V2GetAccountRateLimitsResponse__CreditsSnapshot | null; - readonly individualLimit?: V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot | null; - readonly limitId?: string | null; - 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 spendControlReached?: boolean | null; -}; -export const V2GetAccountRateLimitsResponse__RateLimitSnapshot = Schema.Struct({ - credits: Schema.optionalKey( - Schema.Union([V2GetAccountRateLimitsResponse__CreditsSnapshot, Schema.Null]), - ), - individualLimit: Schema.optionalKey( - Schema.Union([V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot, Schema.Null]), - ), - limitId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - limitName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - planType: Schema.optionalKey( - Schema.Union([V2GetAccountRateLimitsResponse__PlanType, Schema.Null]), - ), - 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]), - ), - spendControlReached: Schema.optionalKey( - Schema.Union([ - Schema.Boolean.annotate({ - description: - "Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.", +export type ServerNotification__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: ServerNotification__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: ServerNotification__ThreadId; + }; + } + | { readonly other: string }; +export const ServerNotification__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([ServerNotification__AgentPath, Schema.Null])), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: ServerNotification__ThreadId, }), - Schema.Null, - ]), - ), -}); + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ServerNotification__SubAgentSource" }); -export type V2GetAccountResponse__Account = - | { readonly type: "apiKey" } +export type ServerNotification__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } | { - readonly email: string | null; - readonly planType: V2GetAccountResponse__PlanType; - readonly type: "chatgpt"; - } - | { readonly type: "amazonBedrock"; readonly usesCodexManagedCredentials?: boolean }; -export const V2GetAccountResponse__Account = Schema.Union( + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const ServerNotification__ThreadStatus = Schema.Union( [ Schema.Struct({ - type: Schema.Literal("apiKey").annotate({ title: "ApiKeyAccountType" }), - }).annotate({ title: "ApiKeyAccount" }), + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), Schema.Struct({ - email: Schema.Union([Schema.String, Schema.Null]), - planType: V2GetAccountResponse__PlanType, - type: Schema.Literal("chatgpt").annotate({ title: "ChatgptAccountType" }), - }).annotate({ title: "ChatgptAccount" }), + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), Schema.Struct({ - type: Schema.Literal("amazonBedrock").annotate({ title: "AmazonBedrockAccountType" }), - usesCodexManagedCredentials: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - }).annotate({ title: "AmazonBedrockAccount" }), + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(ServerNotification__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerNotification__ThreadStatus" }); -export type V2GetWorkspaceMessagesResponse__WorkspaceMessage = { - readonly archivedAt?: number | null; - readonly createdAt?: number | null; - readonly messageBody: string; - readonly messageId: string; - readonly messageType: V2GetWorkspaceMessagesResponse__WorkspaceMessageType; +export type ServerNotification__TextElement = { + readonly byteRange: ServerNotification__ByteRange; + readonly placeholder?: string | null; }; -export const V2GetWorkspaceMessagesResponse__WorkspaceMessage = Schema.Struct({ - archivedAt: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ - description: "Unix timestamp (in seconds) when the message was archived.", - format: "int64", - }).check(Schema.isInt()), - Schema.Null, - ]), - ), - createdAt: Schema.optionalKey( +export const ServerNotification__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => ServerNotification__ByteRange, + ).annotate({ description: "Byte range in the parent `text` buffer that this element occupies." }), + placeholder: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ - description: "Unix timestamp (in seconds) when the message was created.", - format: "int64", - }).check(Schema.isInt()), + Schema.String.annotate({ + description: "Optional human-readable placeholder for the element, displayed in the UI.", + }), Schema.Null, ]), ), - messageBody: Schema.String, - messageId: Schema.String, - messageType: V2GetWorkspaceMessagesResponse__WorkspaceMessageType, -}); - -export type V2HookCompletedNotification__HookOutputEntry = { - readonly kind: V2HookCompletedNotification__HookOutputEntryKind; - readonly text: string; -}; -export const V2HookCompletedNotification__HookOutputEntry = Schema.Struct({ - kind: V2HookCompletedNotification__HookOutputEntryKind, - text: Schema.String, -}); +}).annotate({ identifier: "ServerNotification__TextElement" }); -export type V2HooksListResponse__HookMetadata = { - readonly command?: string | null; - readonly currentHash: string; - readonly displayOrder: number; - readonly enabled: boolean; - readonly eventName: V2HooksListResponse__HookEventName; - readonly handlerType: V2HooksListResponse__HookHandlerType; - readonly isManaged: boolean; - readonly key: string; - readonly matcher?: string | null; - readonly pluginId?: string | null; - readonly source: V2HooksListResponse__HookSource; - readonly sourcePath: V2HooksListResponse__AbsolutePathBuf; - readonly statusMessage?: string | null; - readonly timeoutSec: number; - readonly trustStatus: V2HooksListResponse__HookTrustStatus; -}; -export const V2HooksListResponse__HookMetadata = Schema.Struct({ - command: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - currentHash: Schema.String, - displayOrder: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - enabled: Schema.Boolean, - eventName: V2HooksListResponse__HookEventName, - handlerType: V2HooksListResponse__HookHandlerType, - isManaged: Schema.Boolean, - key: Schema.String, - matcher: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - source: V2HooksListResponse__HookSource, - sourcePath: V2HooksListResponse__AbsolutePathBuf, - statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - timeoutSec: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - trustStatus: V2HooksListResponse__HookTrustStatus, +export type ServerNotification__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: ServerNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: ServerNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const ServerNotification__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ServerNotification__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ServerNotification__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "ServerNotification__FunctionCallOutputContentItem", }); -export type V2HookStartedNotification__HookOutputEntry = { - readonly kind: V2HookStartedNotification__HookOutputEntryKind; - readonly text: string; +export type ServerNotification__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; }; -export const V2HookStartedNotification__HookOutputEntry = Schema.Struct({ - kind: V2HookStartedNotification__HookOutputEntryKind, - text: Schema.String, -}); +export const ServerNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(ServerNotification__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "ServerNotification__MemoryCitation" }); -export type V2ItemCompletedNotification__CommandAction = +export type ServerNotification__CommandAction = | { readonly command: string; readonly name: string; - readonly path: V2ItemCompletedNotification__AbsolutePathBuf; + readonly path: ServerNotification__LegacyAppPathString; readonly type: "read"; } | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } @@ -14319,12 +18304,12 @@ export type V2ItemCompletedNotification__CommandAction = readonly type: "search"; } | { readonly command: string; readonly type: "unknown" }; -export const V2ItemCompletedNotification__CommandAction = Schema.Union( +export const ServerNotification__CommandAction = Schema.Union( [ Schema.Struct({ command: Schema.String, name: Schema.String, - path: V2ItemCompletedNotification__AbsolutePathBuf, + path: ServerNotification__LegacyAppPathString, type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), }).annotate({ title: "ReadCommandAction" }), Schema.Struct({ @@ -14344,226 +18329,23 @@ export const V2ItemCompletedNotification__CommandAction = Schema.Union( }).annotate({ title: "UnknownCommandAction" }), ], { mode: "oneOf" }, -); - -export type V2ItemCompletedNotification__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ItemCompletedNotification__CollabAgentStatus; -}; -export const V2ItemCompletedNotification__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ItemCompletedNotification__CollabAgentStatus, -}); - -export type V2ItemCompletedNotification__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const V2ItemCompletedNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ItemCompletedNotification__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); - -export type V2ItemCompletedNotification__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ItemCompletedNotification__PatchChangeKind; - readonly path: string; -}; -export const V2ItemCompletedNotification__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ItemCompletedNotification__PatchChangeKind, - path: Schema.String, -}); +).annotate({ identifier: "ServerNotification__CommandAction" }); -export type V2ItemCompletedNotification__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } +export type ServerNotification__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } | { - readonly detail?: V2ItemCompletedNotification__ImageDetail | null; - readonly type: "image"; - readonly url: string; + readonly kind: "project_roots"; + readonly subpath?: ServerNotification__LegacyAppPathString | null; } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } | { - readonly detail?: V2ItemCompletedNotification__ImageDetail | null; + readonly kind: "unknown"; readonly path: string; - readonly type: "localImage"; - } - | { readonly type: "audio"; readonly url: string } - | { readonly path: string; readonly type: "localAudio" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ItemCompletedNotification__UserInput = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(V2ItemCompletedNotification__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({ - detail: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__ImageDetail, Schema.Null]), - ), - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), - Schema.Struct({ - type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), - url: Schema.String, - }).annotate({ title: "AudioUserInput" }), - Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), - 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 V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReview = { - readonly rationale?: string | null; - readonly riskLevel?: V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel | null; - readonly status: V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus; - readonly userAuthorization?: V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization | null; -}; -export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReview = - Schema.Struct({ - rationale: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - riskLevel: Schema.optionalKey( - Schema.Union([ - V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel, - Schema.Null, - ]), - ), - status: V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus, - userAuthorization: Schema.optionalKey( - Schema.Union([ - V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization, - Schema.Null, - ]), - ), - }).annotate({ - description: - "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", - }); - -export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath = - | { readonly kind: "root" } - | { readonly kind: "minimal" } - | { - readonly kind: "project_roots"; - readonly subpath?: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString | null; - } - | { readonly kind: "tmpdir" } - | { readonly kind: "slash_tmp" } - | { - readonly kind: "unknown"; - readonly path: string; - readonly subpath?: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString | 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("project_roots"), - subpath: Schema.optionalKey( - Schema.Union([ - V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, - 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([ - V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, - Schema.Null, - ]), - ), - }), - ], - { mode: "oneOf" }, - ); - -export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReview = { - readonly rationale?: string | null; - readonly riskLevel?: V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel | null; - readonly status: V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus; - readonly userAuthorization?: V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization | null; -}; -export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReview = - Schema.Struct({ - rationale: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - riskLevel: Schema.optionalKey( - Schema.Union([ - V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel, - Schema.Null, - ]), - ), - status: V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus, - userAuthorization: Schema.optionalKey( - Schema.Union([ - V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization, - Schema.Null, - ]), - ), - }).annotate({ - description: - "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", - }); - -export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath = - | { readonly kind: "root" } - | { readonly kind: "minimal" } - | { - readonly kind: "project_roots"; - readonly subpath?: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString | null; - } - | { readonly kind: "tmpdir" } - | { readonly kind: "slash_tmp" } - | { - readonly kind: "unknown"; - readonly path: string; - readonly subpath?: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString | null; + readonly subpath?: ServerNotification__LegacyAppPathString | null; }; -export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath = Schema.Union( +export const ServerNotification__FileSystemSpecialPath = Schema.Union( [ Schema.Struct({ kind: Schema.Literal("root") }).annotate({ title: "RootFileSystemSpecialPath", @@ -14574,10 +18356,7 @@ export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialP Schema.Struct({ kind: Schema.Literal("project_roots"), subpath: Schema.optionalKey( - Schema.Union([ - V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, - Schema.Null, - ]), + Schema.Union([ServerNotification__LegacyAppPathString, Schema.Null]), ), }).annotate({ title: "KindFileSystemSpecialPath" }), Schema.Struct({ kind: Schema.Literal("tmpdir") }).annotate({ @@ -14590,1365 +18369,1643 @@ export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialP kind: Schema.Literal("unknown"), path: Schema.String, subpath: Schema.optionalKey( - Schema.Union([ - V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, - Schema.Null, - ]), + Schema.Union([ServerNotification__LegacyAppPathString, Schema.Null]), ), }), ], { mode: "oneOf" }, -); - -export type V2ItemStartedNotification__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: V2ItemStartedNotification__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 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; - readonly status: V2ItemStartedNotification__CollabAgentStatus; -}; -export const V2ItemStartedNotification__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ItemStartedNotification__CollabAgentStatus, -}); - -export type V2ItemStartedNotification__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const V2ItemStartedNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ItemStartedNotification__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); +).annotate({ identifier: "ServerNotification__FileSystemSpecialPath" }); -export type V2ItemStartedNotification__FileUpdateChange = { +export type ServerNotification__FileUpdateChange = { readonly diff: string; - readonly kind: V2ItemStartedNotification__PatchChangeKind; + readonly kind: ServerNotification__PatchChangeKind; readonly path: string; }; -export const V2ItemStartedNotification__FileUpdateChange = Schema.Struct({ +export const ServerNotification__FileUpdateChange = Schema.Struct({ diff: Schema.String, - kind: V2ItemStartedNotification__PatchChangeKind, + kind: ServerNotification__PatchChangeKind, path: Schema.String, -}); - -export type V2ItemStartedNotification__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } - | { - readonly detail?: V2ItemStartedNotification__ImageDetail | null; - readonly type: "image"; - readonly url: string; - } - | { - readonly detail?: V2ItemStartedNotification__ImageDetail | null; - readonly path: string; - readonly type: "localImage"; - } - | { readonly type: "audio"; readonly url: string } - | { readonly path: string; readonly type: "localAudio" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ItemStartedNotification__UserInput = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(V2ItemStartedNotification__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({ - detail: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__ImageDetail, Schema.Null]), - ), - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), - Schema.Struct({ - type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), - url: Schema.String, - }).annotate({ title: "AudioUserInput" }), - Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), - 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" }, -); +}).annotate({ identifier: "ServerNotification__FileUpdateChange" }); -export type V2ListMcpServerStatusResponse__McpServerStatus = { - readonly authStatus: V2ListMcpServerStatusResponse__McpAuthStatus; - readonly name: string; - readonly resourceTemplates: ReadonlyArray; - readonly resources: ReadonlyArray; - readonly serverInfo?: V2ListMcpServerStatusResponse__McpServerInfo | null; - readonly tools: { readonly [x: string]: V2ListMcpServerStatusResponse__Tool }; +export type ServerNotification__McpAppUi = { + readonly preferredModelDisplayMode: ServerNotification__McpAppDisplayMode; + readonly resourceUri: string; }; -export const V2ListMcpServerStatusResponse__McpServerStatus = Schema.Struct({ - authStatus: V2ListMcpServerStatusResponse__McpAuthStatus, - name: Schema.String, - resourceTemplates: Schema.Array(V2ListMcpServerStatusResponse__ResourceTemplate), - resources: Schema.Array(V2ListMcpServerStatusResponse__Resource), - serverInfo: Schema.optionalKey( - Schema.Union([V2ListMcpServerStatusResponse__McpServerInfo, Schema.Null]), - ), - tools: Schema.Record(Schema.String, V2ListMcpServerStatusResponse__Tool), +export const ServerNotification__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: ServerNotification__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "ServerNotification__McpAppUi", }); -export type V2ModelListResponse__ReasoningEffortOption = { - readonly description: string; - readonly reasoningEffort: V2ModelListResponse__ReasoningEffort; +export type ServerNotification__CollabAgentState = { + readonly message?: string | null; + readonly status: ServerNotification__CollabAgentStatus; }; -export const V2ModelListResponse__ReasoningEffortOption = Schema.Struct({ - description: Schema.String, - reasoningEffort: V2ModelListResponse__ReasoningEffort, -}); +export const ServerNotification__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: ServerNotification__CollabAgentStatus, +}).annotate({ identifier: "ServerNotification__CollabAgentState" }); -export type V2PluginInstalledResponse__MarketplaceLoadErrorInfo = { - readonly marketplacePath: V2PluginInstalledResponse__AbsolutePathBuf; - readonly message: string; +export type ServerNotification__ThreadAttachmentUpdatedNotification = { + readonly attachmentId: string; + readonly attachmentType: string; + readonly identityKey: string; + readonly operation: ServerNotification__ThreadAttachmentOperation; + readonly threadId: string; }; -export const V2PluginInstalledResponse__MarketplaceLoadErrorInfo = Schema.Struct({ - marketplacePath: V2PluginInstalledResponse__AbsolutePathBuf, - message: Schema.String, +export const ServerNotification__ThreadAttachmentUpdatedNotification = Schema.Struct({ + attachmentId: Schema.String, + attachmentType: Schema.String, + identityKey: Schema.String, + operation: ServerNotification__ThreadAttachmentOperation, + threadId: Schema.String, +}).annotate({ + description: "Notification published after a thread attachment is created or deleted.", + identifier: "ServerNotification__ThreadAttachmentUpdatedNotification", }); -export type V2PluginInstalledResponse__PluginInterface = { - readonly brandColor?: string | null; - readonly capabilities: ReadonlyArray; - readonly category?: string | null; - readonly composerIcon?: V2PluginInstalledResponse__AbsolutePathBuf | null; - readonly composerIconUrl?: string | null; - readonly defaultPrompt?: ReadonlyArray | null; - readonly developerName?: string | null; - readonly displayName?: string | null; - readonly logo?: V2PluginInstalledResponse__AbsolutePathBuf | null; - readonly logoDark?: V2PluginInstalledResponse__AbsolutePathBuf | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly longDescription?: string | null; - readonly privacyPolicyUrl?: string | null; - readonly screenshotUrls: ReadonlyArray; - readonly screenshots: ReadonlyArray; - readonly shortDescription?: string | null; - readonly termsOfServiceUrl?: string | null; - readonly websiteUrl?: string | null; +export type ServerNotification__ThreadGoal = { + readonly createdAt: number; + readonly objective: string; + readonly status: ServerNotification__ThreadGoalStatus; + readonly threadId: string; + readonly timeUsedSeconds: number; + readonly tokenBudget?: number | null; + readonly tokensUsed: number; + readonly updatedAt: number; }; -export const V2PluginInstalledResponse__PluginInterface = Schema.Struct({ - brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - capabilities: Schema.Array(Schema.String), - category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - composerIcon: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local composer icon path, resolved from the installed plugin package.", - }), +export const ServerNotification__ThreadGoal = Schema.Struct({ + createdAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), ), - composerIconUrl: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Remote composer icon URL from the plugin catalog." }), - Schema.Null, - ]), + objective: Schema.String, + status: ServerNotification__ThreadGoalStatus, + threadId: Schema.String, + timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), ), - defaultPrompt: Schema.optionalKey( + tokenBudget: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.String).annotate({ - description: - "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", - }), + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), Schema.Null, ]), ), - developerName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logo: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local logo path, resolved from the installed plugin package.", - }), - ), - logoDark: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local dark-mode 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, - ]), + tokensUsed: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), ), - logoUrlDark: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Remote dark-mode logo URL from the plugin catalog." }), - Schema.Null, - ]), + updatedAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), ), - longDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - privacyPolicyUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - screenshotUrls: Schema.Array(Schema.String).annotate({ - description: "Remote screenshot URLs from the plugin catalog.", - }), - screenshots: Schema.Array(V2PluginInstalledResponse__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])), -}); +}).annotate({ identifier: "ServerNotification__ThreadGoal" }); -export type V2PluginInstalledResponse__PluginSource = - | { readonly path: V2PluginInstalledResponse__AbsolutePathBuf; readonly type: "local" } - | { - readonly path?: string | null; - readonly refName?: string | null; - readonly sha?: string | null; - readonly type: "git"; - readonly url: string; - } +export type ServerNotification__ProjectChangedNotification = { + readonly changeType: ServerNotification__ProjectChangeType; + readonly projectId: string; +}; +export const ServerNotification__ProjectChangedNotification = Schema.Struct({ + changeType: ServerNotification__ProjectChangeType, + projectId: Schema.String, +}).annotate({ identifier: "ServerNotification__ProjectChangedNotification" }); + +export type ServerNotification__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { readonly networkAccess?: boolean; readonly type: "readOnly" } + | { readonly networkAccess?: ServerNotification__NetworkAccess; readonly type: "externalSandbox" } | { - readonly package: string; - readonly registry?: string | null; - readonly type: "npm"; - readonly version?: string | null; - } - | { readonly type: "remote" }; -export const V2PluginInstalledResponse__PluginSource = Schema.Union( + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const ServerNotification__SandboxPolicy = Schema.Union( [ Schema.Struct({ - path: V2PluginInstalledResponse__AbsolutePathBuf, - type: Schema.Literal("local").annotate({ title: "LocalPluginSourceType" }), - }).annotate({ title: "LocalPluginSource" }), + type: Schema.Literal("dangerFullAccess").annotate({ + title: "DangerFullAccessSandboxPolicyType", + }), + }).annotate({ title: "DangerFullAccessSandboxPolicy" }), 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" }), + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), Schema.Struct({ - package: Schema.String, - registry: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional HTTPS registry URL. Authentication stays in the user's npm config.", - }), - Schema.Null, - ]), - ), - type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), - version: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Optional npm version or version range." }), - Schema.Null, - ]), + networkAccess: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => ServerNotification__NetworkAccess, + ).annotate({ default: "restricted" }), ), - }).annotate({ title: "NpmPluginSource" }), + type: Schema.Literal("externalSandbox").annotate({ + title: "ExternalSandboxSandboxPolicyType", + }), + }).annotate({ title: "ExternalSandboxSandboxPolicy" }), 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.", - }), - ], + excludeSlashTmp: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + excludeTmpdirEnvVar: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(ServerNotification__AbsolutePathBuf).annotate({ default: [] }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerNotification__SandboxPolicy" }); -export type V2PluginInstalledResponse__PluginSharePrincipal = { - readonly name: string; - readonly principalId: string; - readonly principalType: V2PluginInstalledResponse__PluginSharePrincipalType; - readonly role: V2PluginInstalledResponse__PluginSharePrincipalRole; +export type ServerNotification__ThreadTokenUsage = { + readonly last: ServerNotification__TokenUsageBreakdown; + readonly modelContextWindow?: number | null; + readonly total: ServerNotification__TokenUsageBreakdown; }; -export const V2PluginInstalledResponse__PluginSharePrincipal = Schema.Struct({ - name: Schema.String, - principalId: Schema.String, - principalType: V2PluginInstalledResponse__PluginSharePrincipalType, - role: V2PluginInstalledResponse__PluginSharePrincipalRole, +export const ServerNotification__ThreadTokenUsage = Schema.Struct({ + last: ServerNotification__TokenUsageBreakdown, + modelContextWindow: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + total: ServerNotification__TokenUsageBreakdown, +}).annotate({ identifier: "ServerNotification__ThreadTokenUsage" }); + +export type ServerNotification__HookOutputEntry = { + readonly kind: ServerNotification__HookOutputEntryKind; + readonly text: string; +}; +export const ServerNotification__HookOutputEntry = Schema.Struct({ + kind: ServerNotification__HookOutputEntryKind, + text: Schema.String, +}).annotate({ identifier: "ServerNotification__HookOutputEntry" }); + +export type ServerNotification__TurnPlanStep = { + readonly status: ServerNotification__TurnPlanStepStatus; + readonly step: string; +}; +export const ServerNotification__TurnPlanStep = Schema.Struct({ + status: ServerNotification__TurnPlanStepStatus, + step: Schema.String, +}).annotate({ identifier: "ServerNotification__TurnPlanStep" }); + +export type ServerNotification__GuardianApprovalReview = { + readonly rationale?: string | null; + readonly riskLevel?: ServerNotification__GuardianRiskLevel | null; + readonly status: ServerNotification__GuardianApprovalReviewStatus; + readonly userAuthorization?: ServerNotification__GuardianUserAuthorization | null; +}; +export const ServerNotification__GuardianApprovalReview = Schema.Struct({ + rationale: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + riskLevel: Schema.optionalKey(Schema.Union([ServerNotification__GuardianRiskLevel, Schema.Null])), + status: ServerNotification__GuardianApprovalReviewStatus, + userAuthorization: Schema.optionalKey( + Schema.Union([ServerNotification__GuardianUserAuthorization, Schema.Null]), + ), +}).annotate({ + description: + "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", + identifier: "ServerNotification__GuardianApprovalReview", }); -export type V2PluginListResponse__MarketplaceLoadErrorInfo = { - readonly marketplacePath: V2PluginListResponse__AbsolutePathBuf; - readonly message: string; +export type ServerNotification__CommandExecOutputDeltaNotification = { + readonly capReached: boolean; + readonly deltaBase64: string; + readonly processId: string; + readonly stream: ServerNotification__CommandExecOutputStream; }; -export const V2PluginListResponse__MarketplaceLoadErrorInfo = Schema.Struct({ - marketplacePath: V2PluginListResponse__AbsolutePathBuf, - message: Schema.String, +export const ServerNotification__CommandExecOutputDeltaNotification = Schema.Struct({ + capReached: Schema.Boolean.annotate({ + description: + "`true` on the final streamed chunk for a stream when `outputBytesCap` truncated later output on that stream.", + }), + deltaBase64: Schema.String.annotate({ description: "Base64-encoded output bytes." }), + processId: Schema.String.annotate({ + description: + "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", + }), + stream: Schema.suspend( + (): Schema.Codec => + ServerNotification__CommandExecOutputStream, + ).annotate({ description: "Output stream for this chunk." }), +}).annotate({ + description: + "Base64-encoded output chunk emitted for a streaming `command/exec` request.\n\nThese notifications are connection-scoped. If the originating connection closes, the server terminates the process.", + identifier: "ServerNotification__CommandExecOutputDeltaNotification", }); -export type V2PluginListResponse__PluginInterface = { - readonly brandColor?: string | null; - 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 logoDark?: V2PluginListResponse__AbsolutePathBuf | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly longDescription?: string | null; - readonly privacyPolicyUrl?: string | null; - readonly screenshotUrls: ReadonlyArray; - readonly screenshots: ReadonlyArray; - readonly shortDescription?: string | null; - readonly termsOfServiceUrl?: string | null; - readonly websiteUrl?: string | null; +export type ServerNotification__ProcessOutputDeltaNotification = { + readonly capReached: boolean; + readonly deltaBase64: string; + readonly processHandle: string; + readonly stream: ServerNotification__ProcessOutputStream; }; -export const V2PluginListResponse__PluginInterface = Schema.Struct({ - brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - capabilities: Schema.Array(Schema.String), - category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - composerIcon: Schema.optionalKey( - Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local composer icon path, resolved from the installed plugin package.", - }), +export const ServerNotification__ProcessOutputDeltaNotification = Schema.Struct({ + capReached: Schema.Boolean.annotate({ + description: + "True on the final streamed chunk for this stream when output was truncated by `outputBytesCap`.", + }), + deltaBase64: Schema.String.annotate({ description: "Base64-encoded output bytes." }), + processHandle: Schema.String.annotate({ + description: "Client-supplied, connection-scoped `processHandle` from `process/spawn`.", + }), + stream: Schema.suspend( + (): Schema.Codec => + ServerNotification__ProcessOutputStream, + ).annotate({ description: "Output stream this chunk belongs to." }), +}).annotate({ + description: "Base64-encoded output chunk emitted for a streaming `process/spawn` request.", + identifier: "ServerNotification__ProcessOutputDeltaNotification", +}); + +export type ServerNotification__ServerRequestResolvedNotification = { + readonly requestId: ServerNotification__RequestId; + readonly threadId: string; +}; +export const ServerNotification__ServerRequestResolvedNotification = Schema.Struct({ + requestId: ServerNotification__RequestId, + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ServerRequestResolvedNotification" }); + +export type ServerNotification__McpServerStatusUpdatedNotification = { + readonly error?: string | null; + readonly failureReason?: ServerNotification__McpServerStartupFailureReason | null; + readonly name: string; + readonly status: ServerNotification__McpServerStartupState; + readonly threadId?: string | null; +}; +export const ServerNotification__McpServerStatusUpdatedNotification = Schema.Struct({ + error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + failureReason: Schema.optionalKey( + Schema.Union([ServerNotification__McpServerStartupFailureReason, Schema.Null]), ), - composerIconUrl: Schema.optionalKey( + name: Schema.String, + status: ServerNotification__McpServerStartupState, + threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__McpServerStatusUpdatedNotification" }); + +export type ServerNotification__McpServerEventStreamNotification = { + readonly notification: ServerNotification__McpServerEventNotification; + readonly subscriptionId: string; +}; +export const ServerNotification__McpServerEventStreamNotification = Schema.Struct({ + notification: ServerNotification__McpServerEventNotification, + subscriptionId: Schema.String, +}).annotate({ identifier: "ServerNotification__McpServerEventStreamNotification" }); + +export type ServerNotification__AccountUpdatedNotification = { + readonly authMode?: ServerNotification__AuthMode | null; + readonly planType?: ServerNotification__PlanType | null; +}; +export const ServerNotification__AccountUpdatedNotification = Schema.Struct({ + authMode: Schema.optionalKey(Schema.Union([ServerNotification__AuthMode, Schema.Null])), + planType: Schema.optionalKey(Schema.Union([ServerNotification__PlanType, Schema.Null])), +}).annotate({ identifier: "ServerNotification__AccountUpdatedNotification" }); + +export type ServerNotification__RateLimitSnapshot = { + readonly credits?: ServerNotification__CreditsSnapshot | null; + readonly individualLimit?: ServerNotification__SpendControlLimitSnapshot | null; + readonly limitId?: string | null; + readonly limitName?: string | null; + readonly normalModelSlug?: string | null; + readonly planType?: ServerNotification__PlanType | null; + readonly primary?: ServerNotification__RateLimitWindow | null; + readonly rateLimitReachedType?: ServerNotification__RateLimitReachedType | null; + readonly secondary?: ServerNotification__RateLimitWindow | null; + readonly spendControlReached?: boolean | null; +}; +export const ServerNotification__RateLimitSnapshot = Schema.Struct({ + credits: Schema.optionalKey(Schema.Union([ServerNotification__CreditsSnapshot, Schema.Null])), + individualLimit: Schema.optionalKey( + Schema.Union([ServerNotification__SpendControlLimitSnapshot, Schema.Null]), + ), + limitId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + limitName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + normalModelSlug: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Remote composer icon URL from the plugin catalog." }), + Schema.String.annotate({ + description: + "Normal model whose display name and reasoning options describe this quota alias.", + }), Schema.Null, ]), ), - defaultPrompt: Schema.optionalKey( + 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])), + spendControlReached: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.String).annotate({ + Schema.Boolean.annotate({ description: - "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", + "Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.", }), Schema.Null, ]), ), - 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]).annotate({ - description: "Local logo path, resolved from the installed plugin package.", - }), - ), - logoDark: Schema.optionalKey( - Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local dark-mode logo path, resolved from the installed plugin package.", - }), +}).annotate({ identifier: "ServerNotification__RateLimitSnapshot" }); + +export type ServerNotification__AppMetadata = { + readonly categories?: ReadonlyArray | null; + readonly developer?: string | null; + readonly firstPartyRequiresInstall?: boolean | null; + readonly review?: ServerNotification__AppReview | null; + readonly screenshots?: ReadonlyArray | null; + readonly seoDescription?: string | null; + readonly showInComposerWhenUnlinked?: boolean | null; + readonly subCategories?: ReadonlyArray | null; + readonly version?: string | null; + readonly versionId?: string | null; + readonly versionNotes?: string | null; +}; +export const ServerNotification__AppMetadata = Schema.Struct({ + categories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + firstPartyRequiresInstall: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + review: Schema.optionalKey(Schema.Union([ServerNotification__AppReview, Schema.Null])), + screenshots: Schema.optionalKey( + Schema.Union([Schema.Array(ServerNotification__AppScreenshot), Schema.Null]), ), - logoUrl: Schema.optionalKey( + seoDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + showInComposerWhenUnlinked: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + subCategories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + version: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + versionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + versionNotes: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__AppMetadata" }); + +export type ServerNotification__RemoteControlStatusChangedNotification = { + readonly environmentId?: string | null; + readonly installationId: string; + readonly serverName: string; + readonly status: ServerNotification__RemoteControlConnectionStatus; +}; +export const ServerNotification__RemoteControlStatusChangedNotification = Schema.Struct({ + environmentId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + installationId: Schema.String, + serverName: Schema.String, + status: ServerNotification__RemoteControlConnectionStatus, +}).annotate({ + description: "Current remote-control connection status and remote identity exposed to clients.", + identifier: "ServerNotification__RemoteControlStatusChangedNotification", +}); + +export type ServerNotification__ExternalAgentConfigImportItemTypeFailure = { + readonly cwd?: string | null; + readonly errorType?: string | null; + readonly failureStage: string; + readonly itemType: ServerNotification__ExternalAgentConfigMigrationItemType; + readonly message: string; + readonly source?: string | null; + readonly subErrorType?: string | null; +}; +export const ServerNotification__ExternalAgentConfigImportItemTypeFailure = Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + failureStage: Schema.String, + itemType: ServerNotification__ExternalAgentConfigMigrationItemType, + message: Schema.String, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__ExternalAgentConfigImportItemTypeFailure" }); + +export type ServerNotification__ExternalAgentConfigImportItemTypeSuccess = { + readonly cwd?: string | null; + readonly itemType: ServerNotification__ExternalAgentConfigMigrationItemType; + readonly source?: string | null; + readonly target?: string | null; + readonly title?: string | null; +}; +export const ServerNotification__ExternalAgentConfigImportItemTypeSuccess = Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + itemType: ServerNotification__ExternalAgentConfigMigrationItemType, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Remote logo URL from the plugin catalog." }), + Schema.String.annotate({ + description: "Original title for an imported session; null for other item types.", + }), Schema.Null, ]), ), - logoUrlDark: Schema.optionalKey( +}).annotate({ identifier: "ServerNotification__ExternalAgentConfigImportItemTypeSuccess" }); + +export type ServerNotification__ModelReroutedNotification = { + readonly fromModel: string; + readonly reason: ServerNotification__ModelRerouteReason; + readonly threadId: string; + readonly toModel: string; + readonly turnId: string; +}; +export const ServerNotification__ModelReroutedNotification = Schema.Struct({ + fromModel: Schema.String, + reason: ServerNotification__ModelRerouteReason, + threadId: Schema.String, + toModel: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__ModelReroutedNotification" }); + +export type ServerNotification__ModelVerificationNotification = { + readonly threadId: string; + readonly turnId: string; + readonly verifications: ReadonlyArray; +}; +export const ServerNotification__ModelVerificationNotification = Schema.Struct({ + threadId: Schema.String, + turnId: Schema.String, + verifications: Schema.Array(ServerNotification__ModelVerification), +}).annotate({ identifier: "ServerNotification__ModelVerificationNotification" }); + +export type ServerNotification__TextRange = { + readonly end: ServerNotification__TextPosition; + readonly start: ServerNotification__TextPosition; +}; +export const ServerNotification__TextRange = Schema.Struct({ + end: ServerNotification__TextPosition, + start: ServerNotification__TextPosition, +}).annotate({ identifier: "ServerNotification__TextRange" }); + +export type ServerNotification__FuzzyFileSearchResult = { + readonly file_name: string; + readonly indices?: ReadonlyArray | null; + readonly match_type: ServerNotification__FuzzyFileSearchMatchType; + readonly path: string; + readonly root: string; + readonly score: number; +}; +export const ServerNotification__FuzzyFileSearchResult = Schema.Struct({ + file_name: Schema.String, + indices: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Remote dark-mode logo URL from the plugin catalog." }), + Schema.Array( + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + ), Schema.Null, ]), ), - longDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - privacyPolicyUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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])), + match_type: ServerNotification__FuzzyFileSearchMatchType, + path: Schema.String, + root: Schema.String, + score: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), +}).annotate({ + description: "Superset of [`codex_file_search::FileMatch`]", + identifier: "ServerNotification__FuzzyFileSearchResult", }); -export type V2PluginListResponse__PluginSource = - | { readonly path: V2PluginListResponse__AbsolutePathBuf; readonly type: "local" } +export type ServerNotification__ThreadRealtimeStartedNotification = { + readonly realtimeSessionId?: string | null; + readonly threadId: string; + readonly version: ServerNotification__RealtimeConversationVersion; +}; +export const ServerNotification__ThreadRealtimeStartedNotification = Schema.Struct({ + realtimeSessionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + threadId: Schema.String, + version: ServerNotification__RealtimeConversationVersion, +}).annotate({ + description: "EXPERIMENTAL - emitted when thread realtime startup is accepted.", + identifier: "ServerNotification__ThreadRealtimeStartedNotification", +}); + +export type ServerNotification__ThreadRealtimeItem = | { - readonly path?: string | null; - readonly refName?: string | null; - readonly sha?: string | null; - readonly type: "git"; - readonly url: string; + readonly id: string; + readonly realtimeSessionId: string; + readonly type: "realtimeSessionStarted"; } | { - readonly package: string; - readonly registry?: string | null; - readonly type: "npm"; - readonly version?: string | null; + readonly id: string; + readonly realtimeSessionId: string; + readonly role: ServerNotification__ThreadRealtimeTranscriptRole; + readonly text: string; + readonly type: "transcriptSegment"; } - | { readonly type: "remote" }; -export const V2PluginListResponse__PluginSource = Schema.Union( + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly item_id: string; + readonly presentation: ServerNotification__ThreadRealtimeBemItemPresentation; + readonly turn_id: string; + readonly type: "bemItemPromoted"; + } + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly outcome: ServerNotification__ThreadRealtimeSessionOutcome; + readonly type: "realtimeSessionClosed"; + }; +export const ServerNotification__ThreadRealtimeItem = Schema.Union( [ Schema.Struct({ - path: V2PluginListResponse__AbsolutePathBuf, - type: Schema.Literal("local").annotate({ title: "LocalPluginSourceType" }), - }).annotate({ title: "LocalPluginSource" }), + id: Schema.String, + realtimeSessionId: Schema.String, + type: Schema.Literal("realtimeSessionStarted").annotate({ + title: "RealtimeSessionStartedThreadRealtimeItemType", + }), + }).annotate({ title: "RealtimeSessionStartedThreadRealtimeItem" }), 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" }), + id: Schema.String, + realtimeSessionId: Schema.String, + role: ServerNotification__ThreadRealtimeTranscriptRole, + text: Schema.String, + type: Schema.Literal("transcriptSegment").annotate({ + title: "TranscriptSegmentThreadRealtimeItemType", + }), + }).annotate({ title: "TranscriptSegmentThreadRealtimeItem" }), Schema.Struct({ - package: Schema.String, - registry: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional HTTPS registry URL. Authentication stays in the user's npm config.", - }), - Schema.Null, - ]), - ), - type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), - version: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Optional npm version or version range." }), - Schema.Null, - ]), - ), - }).annotate({ title: "NpmPluginSource" }), + id: Schema.String, + realtimeSessionId: Schema.String, + item_id: Schema.String, + presentation: ServerNotification__ThreadRealtimeBemItemPresentation, + turn_id: Schema.String, + type: Schema.Literal("bemItemPromoted").annotate({ + title: "BemItemPromotedThreadRealtimeItemType", + }), + }).annotate({ title: "BemItemPromotedThreadRealtimeItem" }), 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.", - }), + id: Schema.String, + realtimeSessionId: Schema.String, + outcome: ServerNotification__ThreadRealtimeSessionOutcome, + type: Schema.Literal("realtimeSessionClosed").annotate({ + title: "RealtimeSessionClosedThreadRealtimeItemType", + }), + }).annotate({ title: "RealtimeSessionClosedThreadRealtimeItem" }), ], { mode: "oneOf" }, -); +).annotate({ + description: "EXPERIMENTAL - a thread-scoped realtime item in the canonical timeline.", + identifier: "ServerNotification__ThreadRealtimeItem", +}); -export type V2PluginListResponse__PluginSharePrincipal = { - readonly name: string; - readonly principalId: string; - readonly principalType: V2PluginListResponse__PluginSharePrincipalType; - readonly role: V2PluginListResponse__PluginSharePrincipalRole; +export type ServerNotification__ThreadRealtimeOutputAudioDeltaNotification = { + readonly audio: ServerNotification__ThreadRealtimeAudioChunk; + readonly threadId: string; }; -export const V2PluginListResponse__PluginSharePrincipal = Schema.Struct({ - name: Schema.String, - principalId: Schema.String, - principalType: V2PluginListResponse__PluginSharePrincipalType, - role: V2PluginListResponse__PluginSharePrincipalRole, +export const ServerNotification__ThreadRealtimeOutputAudioDeltaNotification = Schema.Struct({ + audio: ServerNotification__ThreadRealtimeAudioChunk, + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - streamed output audio emitted by thread realtime.", + identifier: "ServerNotification__ThreadRealtimeOutputAudioDeltaNotification", }); -export type V2PluginReadResponse__PluginInterface = { - readonly brandColor?: string | null; - 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 logoDark?: V2PluginReadResponse__AbsolutePathBuf | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly longDescription?: string | null; - readonly privacyPolicyUrl?: string | null; - readonly screenshotUrls: ReadonlyArray; - readonly screenshots: ReadonlyArray; - readonly shortDescription?: string | null; - readonly termsOfServiceUrl?: string | null; - readonly websiteUrl?: string | null; +export type ServerNotification__WindowsSandboxSetupCompletedNotification = { + readonly error?: string | null; + readonly mode: ServerNotification__WindowsSandboxSetupMode; + readonly success: boolean; }; -export const V2PluginReadResponse__PluginInterface = Schema.Struct({ - brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - capabilities: Schema.Array(Schema.String), - category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - composerIcon: Schema.optionalKey( - 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, - ]), +export const ServerNotification__WindowsSandboxSetupCompletedNotification = Schema.Struct({ + error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + mode: ServerNotification__WindowsSandboxSetupMode, + success: Schema.Boolean, +}).annotate({ identifier: "ServerNotification__WindowsSandboxSetupCompletedNotification" }); + +export type ServerNotification__AccountLoginCompletedNotification = { + readonly error?: string | null; + readonly loginId?: string | null; + readonly onboardingEntrypoint?: ServerNotification__DesktopOnboardingEntrypoint | null; + readonly success: boolean; +}; +export const ServerNotification__AccountLoginCompletedNotification = Schema.Struct({ + error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + loginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + onboardingEntrypoint: Schema.optionalKey( + Schema.Union([ServerNotification__DesktopOnboardingEntrypoint, Schema.Null]), ), - defaultPrompt: Schema.optionalKey( - Schema.Union([ - Schema.Array(Schema.String).annotate({ - description: - "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", - }), - Schema.Null, - ]), - ), - 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]).annotate({ - description: "Local logo path, resolved from the installed plugin package.", - }), - ), - logoDark: Schema.optionalKey( - Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local dark-mode 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, - ]), - ), - logoUrlDark: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Remote dark-mode 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])), - 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])), -}); + success: Schema.Boolean, +}).annotate({ identifier: "ServerNotification__AccountLoginCompletedNotification" }); -export type V2PluginReadResponse__PluginSource = - | { readonly path: V2PluginReadResponse__AbsolutePathBuf; readonly type: "local" } +export type ServerRequest__CommandAction = | { - readonly path?: string | null; - readonly refName?: string | null; - readonly sha?: string | null; - readonly type: "git"; - readonly url: string; + readonly command: string; + readonly name: string; + readonly path: ServerRequest__LegacyAppPathString; + readonly type: "read"; } + | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } | { - readonly package: string; - readonly registry?: string | null; - readonly type: "npm"; - readonly version?: string | null; + readonly command: string; + readonly path?: string | null; + readonly query?: string | null; + readonly type: "search"; } - | { readonly type: "remote" }; -export const V2PluginReadResponse__PluginSource = Schema.Union( + | { readonly command: string; readonly type: "unknown" }; +export const ServerRequest__CommandAction = Schema.Union( [ Schema.Struct({ - path: V2PluginReadResponse__AbsolutePathBuf, - type: Schema.Literal("local").annotate({ title: "LocalPluginSourceType" }), - }).annotate({ title: "LocalPluginSource" }), + command: Schema.String, + name: Schema.String, + path: ServerRequest__LegacyAppPathString, + type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), + }).annotate({ title: "ReadCommandAction" }), Schema.Struct({ + command: Schema.String, 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" }), + type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), + }).annotate({ title: "ListFilesCommandAction" }), Schema.Struct({ - package: Schema.String, - registry: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional HTTPS registry URL. Authentication stays in the user's npm config.", - }), - Schema.Null, - ]), - ), - type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), - version: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Optional npm version or version range." }), - Schema.Null, - ]), - ), - }).annotate({ title: "NpmPluginSource" }), + 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({ - 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.", + command: Schema.String, + type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), + }).annotate({ title: "UnknownCommandAction" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "ServerRequest__CommandAction" }); + +export type ServerRequest__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { readonly kind: "project_roots"; readonly subpath?: ServerRequest__LegacyAppPathString | null } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { + readonly kind: "unknown"; + readonly path: string; + readonly subpath?: ServerRequest__LegacyAppPathString | 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("project_roots"), + subpath: Schema.optionalKey(Schema.Union([ServerRequest__LegacyAppPathString, 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([ServerRequest__LegacyAppPathString, Schema.Null])), }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerRequest__FileSystemSpecialPath" }); -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 type ServerRequest__NetworkApprovalContext = { + readonly host: string; + readonly protocol: ServerRequest__NetworkApprovalProtocol; }; -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 const ServerRequest__NetworkApprovalContext = Schema.Struct({ + host: Schema.String, + protocol: ServerRequest__NetworkApprovalProtocol, +}).annotate({ identifier: "ServerRequest__NetworkApprovalContext" }); + +export type ServerRequest__NetworkPolicyAmendment = { + readonly action: ServerRequest__NetworkPolicyRuleAction; + readonly host: string; +}; +export const ServerRequest__NetworkPolicyAmendment = Schema.Struct({ + action: ServerRequest__NetworkPolicyRuleAction, + host: Schema.String, +}).annotate({ identifier: "ServerRequest__NetworkPolicyAmendment" }); + +export type ServerRequest__ToolRequestUserInputQuestion = { + readonly header: string; + readonly id: string; + readonly isOther?: boolean; + readonly isSecret?: boolean; + readonly options?: ReadonlyArray | null; + readonly question: string; +}; +export const ServerRequest__ToolRequestUserInputQuestion = Schema.Struct({ + header: Schema.String, + id: Schema.String, + isOther: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + isSecret: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + options: Schema.optionalKey( + Schema.Union([Schema.Array(ServerRequest__ToolRequestUserInputOption), Schema.Null]), + ), + question: Schema.String, +}).annotate({ + description: "EXPERIMENTAL. Represents one request_user_input question and its required options.", + identifier: "ServerRequest__ToolRequestUserInputQuestion", }); -export type V2PluginReadResponse__AppTemplateSummary = { - readonly canonicalConnectorId?: string | null; - readonly category?: string | null; +export type ServerRequest__McpElicitationUntitledSingleSelectEnumSchema = { + readonly default?: string | null; readonly description?: string | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly materializedAppIds: ReadonlyArray; - readonly name: string; - readonly reason?: V2PluginReadResponse__AppTemplateUnavailableReason | null; - readonly templateId: string; + readonly enum: ReadonlyArray; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationStringType; }; -export const V2PluginReadResponse__AppTemplateSummary = Schema.Struct({ - canonicalConnectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +export const ServerRequest__McpElicitationUntitledSingleSelectEnumSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - materializedAppIds: Schema.Array(Schema.String), - name: Schema.String, - reason: Schema.optionalKey( - Schema.Union([V2PluginReadResponse__AppTemplateUnavailableReason, Schema.Null]), - ), - templateId: Schema.String, -}); + enum: Schema.Array(Schema.String), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationStringType, +}).annotate({ identifier: "ServerRequest__McpElicitationUntitledSingleSelectEnumSchema" }); -export type V2PluginReadResponse__PluginHookSummary = { - readonly eventName: V2PluginReadResponse__HookEventName; - readonly key: string; +export type ServerRequest__McpElicitationUntitledEnumItems = { + readonly enum: ReadonlyArray; + readonly type: ServerRequest__McpElicitationStringType; }; -export const V2PluginReadResponse__PluginHookSummary = Schema.Struct({ - eventName: V2PluginReadResponse__HookEventName, - key: Schema.String, -}); +export const ServerRequest__McpElicitationUntitledEnumItems = Schema.Struct({ + enum: Schema.Array(Schema.String), + type: ServerRequest__McpElicitationStringType, +}).annotate({ identifier: "ServerRequest__McpElicitationUntitledEnumItems" }); -export type V2PluginReadResponse__PluginSharePrincipal = { - readonly name: string; - readonly principalId: string; - readonly principalType: V2PluginReadResponse__PluginSharePrincipalType; - readonly role: V2PluginReadResponse__PluginSharePrincipalRole; +export type ServerRequest__McpElicitationLegacyTitledEnumSchema = { + readonly default?: string | null; + readonly description?: string | null; + readonly enum: ReadonlyArray; + readonly enumNames?: ReadonlyArray | null; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationStringType; }; -export const V2PluginReadResponse__PluginSharePrincipal = Schema.Struct({ - name: Schema.String, - principalId: Schema.String, - principalType: V2PluginReadResponse__PluginSharePrincipalType, - role: V2PluginReadResponse__PluginSharePrincipalRole, -}); +export const ServerRequest__McpElicitationLegacyTitledEnumSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + enum: Schema.Array(Schema.String), + enumNames: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationStringType, +}).annotate({ identifier: "ServerRequest__McpElicitationLegacyTitledEnumSchema" }); -export type V2PluginReadResponse__ScheduledTaskSchedule = - | { - readonly days?: ReadonlyArray | null; - readonly intervalHours: number; - readonly type: "hourly"; - } - | { readonly time: string; readonly type: "daily" } - | { readonly time: string; readonly type: "weekdays" } - | { - readonly days: ReadonlyArray; - readonly time: string; - readonly type: "weekly"; - }; -export const V2PluginReadResponse__ScheduledTaskSchedule = Schema.Union( - [ - Schema.Struct({ - days: Schema.optionalKey( - Schema.Union([Schema.Array(V2PluginReadResponse__ScheduledTaskWeekday), Schema.Null]), - ), - intervalHours: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - type: Schema.Literal("hourly").annotate({ title: "HourlyScheduledTaskScheduleType" }), - }).annotate({ title: "HourlyScheduledTaskSchedule" }), - Schema.Struct({ - time: Schema.String, - type: Schema.Literal("daily").annotate({ title: "DailyScheduledTaskScheduleType" }), - }).annotate({ title: "DailyScheduledTaskSchedule" }), - Schema.Struct({ - time: Schema.String, - type: Schema.Literal("weekdays").annotate({ title: "WeekdaysScheduledTaskScheduleType" }), - }).annotate({ title: "WeekdaysScheduledTaskSchedule" }), - Schema.Struct({ - days: Schema.Array(V2PluginReadResponse__ScheduledTaskWeekday), - time: Schema.String, - type: Schema.Literal("weekly").annotate({ title: "WeeklyScheduledTaskScheduleType" }), - }).annotate({ title: "WeeklyScheduledTaskSchedule" }), - ], - { mode: "oneOf" }, -); +export type ServerRequest__McpElicitationTitledSingleSelectEnumSchema = { + readonly default?: string | null; + readonly description?: string | null; + readonly oneOf: ReadonlyArray; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationStringType; +}; +export const ServerRequest__McpElicitationTitledSingleSelectEnumSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + oneOf: Schema.Array(ServerRequest__McpElicitationConstOption), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationStringType, +}).annotate({ identifier: "ServerRequest__McpElicitationTitledSingleSelectEnumSchema" }); -export type V2PluginShareListResponse__PluginInterface = { - readonly brandColor?: string | null; - readonly capabilities: ReadonlyArray; - readonly category?: string | null; - readonly composerIcon?: V2PluginShareListResponse__AbsolutePathBuf | null; - readonly composerIconUrl?: string | null; - readonly defaultPrompt?: ReadonlyArray | null; - readonly developerName?: string | null; - readonly displayName?: string | null; - readonly logo?: V2PluginShareListResponse__AbsolutePathBuf | null; - readonly logoDark?: V2PluginShareListResponse__AbsolutePathBuf | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly longDescription?: string | null; - readonly privacyPolicyUrl?: string | null; - readonly screenshotUrls: ReadonlyArray; - readonly screenshots: ReadonlyArray; - readonly shortDescription?: string | null; - readonly termsOfServiceUrl?: string | null; - readonly websiteUrl?: string | null; +export type ServerRequest__McpElicitationTitledEnumItems = { + readonly anyOf: ReadonlyArray; }; -export const V2PluginShareListResponse__PluginInterface = Schema.Struct({ - brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - capabilities: Schema.Array(Schema.String), - category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - composerIcon: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local composer icon path, resolved from the installed plugin package.", - }), +export const ServerRequest__McpElicitationTitledEnumItems = Schema.Struct({ + anyOf: Schema.Array(ServerRequest__McpElicitationConstOption), +}).annotate({ identifier: "ServerRequest__McpElicitationTitledEnumItems" }); + +export type ServerRequest__McpElicitationStringSchema = { + readonly default?: string | null; + readonly description?: string | null; + readonly format?: ServerRequest__McpElicitationStringFormat | null; + readonly maxLength?: number | null; + readonly minLength?: number | null; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationStringType; +}; +export const ServerRequest__McpElicitationStringSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + format: Schema.optionalKey( + Schema.Union([ServerRequest__McpElicitationStringFormat, Schema.Null]), ), - composerIconUrl: Schema.optionalKey( + maxLength: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Remote composer icon URL from the plugin catalog." }), + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), - defaultPrompt: Schema.optionalKey( + minLength: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.String).annotate({ - description: - "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", - }), + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), - developerName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logo: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local logo path, resolved from the installed plugin package.", - }), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationStringType, +}).annotate({ identifier: "ServerRequest__McpElicitationStringSchema" }); + +export type ServerRequest__McpElicitationNumberSchema = { + readonly default?: number | null; + readonly description?: string | null; + readonly maximum?: number | null; + readonly minimum?: number | null; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationNumberType; +}; +export const ServerRequest__McpElicitationNumberSchema = Schema.Struct({ + default: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "double" }).check( + Schema.isFinite().annotate({ expected: "a finite number" }), + ), + Schema.Null, + ]), ), - logoDark: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__AbsolutePathBuf, Schema.Null]).annotate({ - description: "Local dark-mode logo path, resolved from the installed plugin package.", - }), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + maximum: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "double" }).check( + Schema.isFinite().annotate({ expected: "a finite number" }), + ), + Schema.Null, + ]), ), - logoUrl: Schema.optionalKey( + minimum: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Remote logo URL from the plugin catalog." }), + Schema.Number.annotate({ format: "double" }).check( + Schema.isFinite().annotate({ expected: "a finite number" }), + ), Schema.Null, ]), ), - logoUrlDark: Schema.optionalKey( + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationNumberType, +}).annotate({ identifier: "ServerRequest__McpElicitationNumberSchema" }); + +export type ServerRequest__McpElicitationBooleanSchema = { + readonly default?: boolean | null; + readonly description?: string | null; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationBooleanType; +}; +export const ServerRequest__McpElicitationBooleanSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationBooleanType, +}).annotate({ identifier: "ServerRequest__McpElicitationBooleanSchema" }); + +export type ServerRequest__ChatgptAuthTokensRefreshParams = { + readonly previousAccountId?: string | null; + readonly reason: ServerRequest__ChatgptAuthTokensRefreshReason; +}; +export const ServerRequest__ChatgptAuthTokensRefreshParams = Schema.Struct({ + previousAccountId: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Remote dark-mode logo URL from the plugin catalog." }), + Schema.String.annotate({ + description: + "Workspace/account identifier that Codex was previously using.\n\nClients that manage multiple accounts/workspaces can use this as a hint to refresh the token for the correct workspace.\n\nThis may be `null` when the prior auth state did not include a workspace identifier (`chatgpt_account_id`).", + }), Schema.Null, ]), ), - longDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - privacyPolicyUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - screenshotUrls: Schema.Array(Schema.String).annotate({ - description: "Remote screenshot URLs from the plugin catalog.", + reason: ServerRequest__ChatgptAuthTokensRefreshReason, +}).annotate({ identifier: "ServerRequest__ChatgptAuthTokensRefreshParams" }); + +export type ServerRequest__ApplyPatchApprovalParams = { + readonly callId: string; + readonly conversationId: ServerRequest__ThreadId; + readonly fileChanges: { readonly [x: string]: ServerRequest__FileChange }; + readonly grantRoot?: string | null; + readonly reason?: string | null; +}; +export const ServerRequest__ApplyPatchApprovalParams = Schema.Struct({ + callId: Schema.String.annotate({ + description: + "Use to correlate this with [codex_protocol::protocol::PatchApplyBeginEvent] and [codex_protocol::protocol::PatchApplyEndEvent].", }), - screenshots: Schema.Array(V2PluginShareListResponse__AbsolutePathBuf).annotate({ - description: "Local screenshot paths, resolved from the installed plugin package.", + conversationId: ServerRequest__ThreadId, + fileChanges: Schema.Record(Schema.String, ServerRequest__FileChange), + grantRoot: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "When set, the agent is asking the user to allow writes under this root for the remainder of the session (unclear if this is honored today).", + }), + Schema.Null, + ]), + ), + reason: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional explanatory reason (e.g. request for extra write access).", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ServerRequest__ApplyPatchApprovalParams" }); + +export type ServerRequest__ExecCommandApprovalParams = { + readonly approvalId?: string | null; + readonly callId: string; + readonly command: ReadonlyArray; + readonly conversationId: ServerRequest__ThreadId; + readonly cwd: string; + readonly parsedCmd: ReadonlyArray; + readonly reason?: string | null; +}; +export const ServerRequest__ExecCommandApprovalParams = Schema.Struct({ + approvalId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Identifier for this specific approval callback." }), + Schema.Null, + ]), + ), + callId: Schema.String.annotate({ + description: + "Use to correlate this with [codex_protocol::protocol::ExecCommandBeginEvent] and [codex_protocol::protocol::ExecCommandEndEvent].", }), - 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])), -}); + command: Schema.Array(Schema.String), + conversationId: ServerRequest__ThreadId, + cwd: Schema.String, + parsedCmd: Schema.Array(ServerRequest__ParsedCommand), + reason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerRequest__ExecCommandApprovalParams" }); -export type V2PluginShareListResponse__PluginSource = - | { readonly path: V2PluginShareListResponse__AbsolutePathBuf; readonly type: "local" } - | { - readonly path?: string | null; - readonly refName?: string | null; - readonly sha?: string | null; - readonly type: "git"; - readonly url: string; - } - | { - readonly package: string; - readonly registry?: string | null; - readonly type: "npm"; - readonly version?: string | null; - } - | { readonly type: "remote" }; -export const V2PluginShareListResponse__PluginSource = Schema.Union( - [ - Schema.Struct({ - path: V2PluginShareListResponse__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({ - package: Schema.String, - registry: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional HTTPS registry URL. Authentication stays in the user's npm config.", - }), - Schema.Null, - ]), - ), - type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), - version: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Optional npm version or version range." }), - Schema.Null, - ]), - ), - }).annotate({ title: "NpmPluginSource" }), - 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 V2PluginShareListResponse__PluginSharePrincipal = { - readonly name: string; - readonly principalId: string; - readonly principalType: V2PluginShareListResponse__PluginSharePrincipalType; - readonly role: V2PluginShareListResponse__PluginSharePrincipalRole; +export type ToolRequestUserInputParams__ToolRequestUserInputQuestion = { + readonly header: string; + readonly id: string; + readonly isOther?: boolean; + readonly isSecret?: boolean; + readonly options?: ReadonlyArray | null; + readonly question: string; }; -export const V2PluginShareListResponse__PluginSharePrincipal = Schema.Struct({ - name: Schema.String, - principalId: Schema.String, - principalType: V2PluginShareListResponse__PluginSharePrincipalType, - role: V2PluginShareListResponse__PluginSharePrincipalRole, +export const ToolRequestUserInputParams__ToolRequestUserInputQuestion = Schema.Struct({ + header: Schema.String, + id: Schema.String, + isOther: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + isSecret: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + options: Schema.optionalKey( + Schema.Union([ + Schema.Array(ToolRequestUserInputParams__ToolRequestUserInputOption), + Schema.Null, + ]), + ), + question: Schema.String, +}).annotate({ + description: "EXPERIMENTAL. Represents one request_user_input question and its required options.", + identifier: "ToolRequestUserInputParams__ToolRequestUserInputQuestion", }); -export type V2PluginShareSaveParams__PluginShareTarget = { - readonly principalId: string; - readonly principalType: V2PluginShareSaveParams__PluginSharePrincipalType; - readonly role: V2PluginShareSaveParams__PluginShareTargetRole; +export type V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot = { + readonly credits?: V2AccountRateLimitsUpdatedNotification__CreditsSnapshot | null; + readonly individualLimit?: V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot | null; + readonly limitId?: string | null; + readonly limitName?: string | null; + readonly normalModelSlug?: string | null; + readonly planType?: V2AccountRateLimitsUpdatedNotification__PlanType | null; + readonly primary?: V2AccountRateLimitsUpdatedNotification__RateLimitWindow | null; + readonly rateLimitReachedType?: V2AccountRateLimitsUpdatedNotification__RateLimitReachedType | null; + readonly secondary?: V2AccountRateLimitsUpdatedNotification__RateLimitWindow | null; + readonly spendControlReached?: boolean | null; }; -export const V2PluginShareSaveParams__PluginShareTarget = Schema.Struct({ - principalId: Schema.String, - principalType: V2PluginShareSaveParams__PluginSharePrincipalType, - role: V2PluginShareSaveParams__PluginShareTargetRole, -}); +export const V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot = Schema.Struct({ + credits: Schema.optionalKey( + Schema.Union([V2AccountRateLimitsUpdatedNotification__CreditsSnapshot, Schema.Null]), + ), + individualLimit: Schema.optionalKey( + Schema.Union([V2AccountRateLimitsUpdatedNotification__SpendControlLimitSnapshot, Schema.Null]), + ), + limitId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + limitName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + normalModelSlug: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Normal model whose display name and reasoning options describe this quota alias.", + }), + Schema.Null, + ]), + ), + planType: Schema.optionalKey( + Schema.Union([V2AccountRateLimitsUpdatedNotification__PlanType, Schema.Null]), + ), + 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]), + ), + spendControlReached: Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + description: + "Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2AccountRateLimitsUpdatedNotification__RateLimitSnapshot" }); -export type V2PluginShareUpdateTargetsParams__PluginShareTarget = { - readonly principalId: string; - readonly principalType: V2PluginShareUpdateTargetsParams__PluginSharePrincipalType; - readonly role: V2PluginShareUpdateTargetsParams__PluginShareTargetRole; +export type V2AppListUpdatedNotification__AppMetadata = { + readonly categories?: ReadonlyArray | null; + readonly developer?: string | null; + readonly firstPartyRequiresInstall?: boolean | null; + readonly review?: V2AppListUpdatedNotification__AppReview | null; + readonly screenshots?: ReadonlyArray | null; + readonly seoDescription?: string | null; + readonly showInComposerWhenUnlinked?: boolean | null; + readonly subCategories?: ReadonlyArray | null; + readonly version?: string | null; + readonly versionId?: string | null; + readonly versionNotes?: string | null; }; -export const V2PluginShareUpdateTargetsParams__PluginShareTarget = Schema.Struct({ - principalId: Schema.String, - principalType: V2PluginShareUpdateTargetsParams__PluginSharePrincipalType, - role: V2PluginShareUpdateTargetsParams__PluginShareTargetRole, -}); +export const V2AppListUpdatedNotification__AppMetadata = Schema.Struct({ + categories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + firstPartyRequiresInstall: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + review: Schema.optionalKey(Schema.Union([V2AppListUpdatedNotification__AppReview, Schema.Null])), + screenshots: Schema.optionalKey( + Schema.Union([Schema.Array(V2AppListUpdatedNotification__AppScreenshot), Schema.Null]), + ), + seoDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + showInComposerWhenUnlinked: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + subCategories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + version: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + versionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + versionNotes: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2AppListUpdatedNotification__AppMetadata" }); -export type V2PluginShareUpdateTargetsResponse__PluginSharePrincipal = { +export type V2AppsListResponse__AppMetadata = { + readonly categories?: ReadonlyArray | null; + readonly developer?: string | null; + readonly firstPartyRequiresInstall?: boolean | null; + readonly review?: V2AppsListResponse__AppReview | null; + readonly screenshots?: ReadonlyArray | null; + readonly seoDescription?: string | null; + readonly showInComposerWhenUnlinked?: boolean | null; + readonly subCategories?: ReadonlyArray | null; + readonly version?: string | null; + readonly versionId?: string | null; + readonly versionNotes?: string | null; +}; +export const V2AppsListResponse__AppMetadata = Schema.Struct({ + categories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + developer: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + firstPartyRequiresInstall: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + review: Schema.optionalKey(Schema.Union([V2AppsListResponse__AppReview, Schema.Null])), + screenshots: Schema.optionalKey( + Schema.Union([Schema.Array(V2AppsListResponse__AppScreenshot), Schema.Null]), + ), + seoDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + showInComposerWhenUnlinked: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + subCategories: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + version: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + versionId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + versionNotes: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2AppsListResponse__AppMetadata" }); + +export type V2AppsReadResponse__ConnectorMetadata = { + readonly description?: string | null; + readonly distributionChannel?: string | null; + readonly iconUrl?: string | null; + readonly iconUrlDark?: string | null; + readonly id: string; + readonly installUrl?: string | null; readonly name: string; - readonly principalId: string; - readonly principalType: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType; - readonly role: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalRole; + readonly pluginDisplayNames?: ReadonlyArray; + readonly toolSummaries?: ReadonlyArray | null; }; -export const V2PluginShareUpdateTargetsResponse__PluginSharePrincipal = Schema.Struct({ +export const V2AppsReadResponse__ConnectorMetadata = Schema.Struct({ + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + distributionChannel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + iconUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + iconUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + id: Schema.String, + installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), name: Schema.String, - principalId: Schema.String, - principalType: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType, - role: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalRole, + pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + toolSummaries: Schema.optionalKey( + Schema.Union([Schema.Array(V2AppsReadResponse__AppToolSummary), Schema.Null]), + ), +}).annotate({ + description: "EXPERIMENTAL - metadata returned by app/read.", + identifier: "V2AppsReadResponse__ConnectorMetadata", }); -export type V2RawResponseItemCompletedNotification__ContentItem = - | { readonly text: string; readonly type: "input_text" } +export type V2CommandExecParams__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { readonly networkAccess?: boolean; readonly type: "readOnly" } | { - readonly detail?: V2RawResponseItemCompletedNotification__ImageDetail | null; - readonly image_url: string; - readonly type: "input_image"; + readonly networkAccess?: V2CommandExecParams__NetworkAccess; + readonly type: "externalSandbox"; } - | { readonly audio_url: string; readonly type: "input_audio" } - | { 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({ - audio_url: Schema.String, - type: Schema.Literal("input_audio").annotate({ title: "InputAudioContentItemType" }), - }).annotate({ title: "InputAudioContentItem" }), - 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" } | { - readonly detail?: V2RawResponseItemCompletedNotification__ImageDetail | null; - readonly image_url: string; - readonly type: "input_image"; - } - | { readonly audio_url: string; readonly type: "input_audio" } - | { readonly encrypted_content: string; readonly type: "encrypted_content" }; -export const V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem = Schema.Union( + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const V2CommandExecParams__SandboxPolicy = Schema.Union( [ Schema.Struct({ - text: Schema.String, - type: Schema.Literal("input_text").annotate({ - title: "InputTextFunctionCallOutputContentItemType", + type: Schema.Literal("dangerFullAccess").annotate({ + title: "DangerFullAccessSandboxPolicyType", }), - }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + }).annotate({ title: "DangerFullAccessSandboxPolicy" }), Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2RawResponseItemCompletedNotification__ImageDetail, Schema.Null]), - ), - image_url: Schema.String, - type: Schema.Literal("input_image").annotate({ - title: "InputImageFunctionCallOutputContentItemType", - }), - }).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), Schema.Struct({ - audio_url: Schema.String, - type: Schema.Literal("input_audio").annotate({ - title: "InputAudioFunctionCallOutputContentItemType", + networkAccess: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2CommandExecParams__NetworkAccess, + ).annotate({ default: "restricted" }), + ), + type: Schema.Literal("externalSandbox").annotate({ + title: "ExternalSandboxSandboxPolicyType", }), - }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + }).annotate({ title: "ExternalSandboxSandboxPolicy" }), Schema.Struct({ - encrypted_content: Schema.String, - type: Schema.Literal("encrypted_content").annotate({ - title: "EncryptedContentFunctionCallOutputContentItemType", - }), - }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + excludeSlashTmp: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + excludeTmpdirEnvVar: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(V2CommandExecParams__AbsolutePathBuf).annotate({ default: [] }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), ], { mode: "oneOf" }, -).annotate({ - description: - "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.", -}); +).annotate({ identifier: "V2CommandExecParams__SandboxPolicy" }); -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 V2ConfigBatchWriteParams__ConfigEdit = { + readonly keyPath: string; + readonly mergeStrategy: V2ConfigBatchWriteParams__MergeStrategy; + readonly value: Schema.Json; +}; +export const V2ConfigBatchWriteParams__ConfigEdit = Schema.Struct({ + keyPath: Schema.String, + mergeStrategy: V2ConfigBatchWriteParams__MergeStrategy, + value: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ identifier: "V2ConfigBatchWriteParams__ConfigEdit" }); -export type V2ReviewStartResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ReviewStartResponse__CollabAgentStatus; +export type V2ConfigReadResponse__BrowserUseOriginPolicyConfig = { + readonly access?: V2ConfigReadResponse__AllowDenyRequirement | null; + readonly downloads?: V2ConfigReadResponse__AllowDenyRequirement | null; + readonly full_cdp_access?: V2ConfigReadResponse__AllowDenyRequirement | null; + readonly uploads?: V2ConfigReadResponse__AllowDenyRequirement | null; }; -export const V2ReviewStartResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ReviewStartResponse__CollabAgentStatus, -}); +export const V2ConfigReadResponse__BrowserUseOriginPolicyConfig = Schema.Struct({ + access: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AllowDenyRequirement, Schema.Null]), + ), + downloads: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AllowDenyRequirement, Schema.Null]), + ), + full_cdp_access: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AllowDenyRequirement, Schema.Null]), + ), + uploads: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AllowDenyRequirement, Schema.Null]), + ), +}).annotate({ identifier: "V2ConfigReadResponse__BrowserUseOriginPolicyConfig" }); -export type V2ReviewStartResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +export type V2ConfigReadResponse__ComputerUseMacosConfig = { + readonly bundle_ids?: { readonly [x: string]: V2ConfigReadResponse__AllowDenyRequirement } | null; }; -export const V2ReviewStartResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ReviewStartResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); +export const V2ConfigReadResponse__ComputerUseMacosConfig = Schema.Struct({ + bundle_ids: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, V2ConfigReadResponse__AllowDenyRequirement), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2ConfigReadResponse__ComputerUseMacosConfig" }); -export type V2ReviewStartResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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 } } +export type V2ConfigReadResponse__ComputerUseWindowsExeConfig = { + readonly access: V2ConfigReadResponse__AllowDenyRequirement; + readonly binary_name?: string | null; + readonly product_name: string; + readonly publisher_name: string; +}; +export const V2ConfigReadResponse__ComputerUseWindowsExeConfig = Schema.Struct({ + access: V2ConfigReadResponse__AllowDenyRequirement, + binary_name: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + product_name: Schema.String, + publisher_name: Schema.String, +}).annotate({ identifier: "V2ConfigReadResponse__ComputerUseWindowsExeConfig" }); + +export type V2ConfigReadResponse__WebSearchToolConfig = { + readonly allowed_domains?: ReadonlyArray | null; + readonly context_size?: V2ConfigReadResponse__WebSearchContextSize | null; + readonly location?: V2ConfigReadResponse__WebSearchLocation | null; +}; +export const V2ConfigReadResponse__WebSearchToolConfig = Schema.Struct({ + allowed_domains: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + context_size: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__WebSearchContextSize, Schema.Null]), + ), + location: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__WebSearchLocation, Schema.Null]), + ), +}).annotate({ identifier: "V2ConfigReadResponse__WebSearchToolConfig" }); + +export type V2ConfigReadResponse__ConfigLayerSource = + | { readonly file: V2ConfigReadResponse__AbsolutePathBuf; readonly type: "packagedDefaults" } + | { readonly domain: string; readonly key: string; readonly type: "mdm" } + | { readonly file: V2ConfigReadResponse__AbsolutePathBuf; readonly type: "system" } + | { readonly id: string; readonly name: string; readonly type: "enterpriseManaged" } | { - readonly activeTurnNotSteerable: { - readonly turnKind: V2ReviewStartResponse__NonSteerableTurnKind; - }; - }; -export const V2ReviewStartResponse__CodexErrorInfo = Schema.Union( + readonly file: V2ConfigReadResponse__AbsolutePathBuf; + readonly profile?: string | null; + readonly type: "user"; + } + | { readonly dotCodexFolder: V2ConfigReadResponse__AbsolutePathBuf; readonly type: "project" } + | { readonly type: "sessionFlags" } + | { + readonly file: V2ConfigReadResponse__AbsolutePathBuf; + readonly type: "legacyManagedConfigTomlFromFile"; + } + | { readonly type: "legacyManagedConfigTomlFromMdm" }; +export const V2ConfigReadResponse__ConfigLayerSource = Schema.Union( [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "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, - ]), - ), + file: Schema.suspend( + (): Schema.Codec => + V2ConfigReadResponse__AbsolutePathBuf, + ).annotate({ description: "Path to the packaged default configuration file." }), + type: Schema.Literal("packagedDefaults").annotate({ + title: "PackagedDefaultsConfigLayerSourceType", }), }).annotate({ - title: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", + title: "PackagedDefaultsConfigLayerSource", + description: "Default configuration supplied with the installed Codex package.", }), Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), + domain: Schema.String, + key: Schema.String, + type: Schema.Literal("mdm").annotate({ title: "MdmConfigLayerSourceType" }), }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", + title: "MdmConfigLayerSource", + description: "Managed preferences layer delivered by MDM (macOS only).", }), Schema.Struct({ - responseTooManyFailedAttempts: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), + file: Schema.suspend( + (): Schema.Codec => + V2ConfigReadResponse__AbsolutePathBuf, + ).annotate({ + description: + "This is the path to the system config.toml file, though it is not guaranteed to exist.", }), + type: Schema.Literal("system").annotate({ title: "SystemConfigLayerSourceType" }), }).annotate({ - title: "ResponseTooManyFailedAttemptsCodexErrorInfo", - description: "Reached the retry limit for responses.", + title: "SystemConfigLayerSource", + description: "Managed config layer from a file (usually `managed_config.toml`).", }), Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ReviewStartResponse__NonSteerableTurnKind, + id: Schema.String.annotate({ description: "Stable identifier for the delivered layer." }), + name: Schema.String.annotate({ + description: + "Admin-facing name for the delivered layer. This is surfaced in diagnostics so users know which cloud layer needs administrator attention.", + }), + type: Schema.Literal("enterpriseManaged").annotate({ + title: "EnterpriseManagedConfigLayerSourceType", }), }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", + title: "EnterpriseManagedConfigLayerSource", + description: "Enterprise-managed config layer delivered by the cloud config bundle.", }), - ], - { mode: "oneOf" }, -).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 V2ReviewStartResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ReviewStartResponse__PatchChangeKind; - readonly path: string; -}; -export const V2ReviewStartResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ReviewStartResponse__PatchChangeKind, - path: Schema.String, -}); - -export type V2ReviewStartResponse__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } - | { - readonly detail?: V2ReviewStartResponse__ImageDetail | null; - readonly type: "image"; - readonly url: string; - } - | { - readonly detail?: V2ReviewStartResponse__ImageDetail | null; - readonly path: string; - readonly type: "localImage"; - } - | { readonly type: "audio"; readonly url: string } - | { readonly path: string; readonly type: "localAudio" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ReviewStartResponse__UserInput = Schema.Union( - [ Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(V2ReviewStartResponse__TextElement).annotate({ - description: "UI-defined spans within `text` used to render or persist special elements.", - default: [], - }), + file: Schema.suspend( + (): Schema.Codec => + V2ConfigReadResponse__AbsolutePathBuf, + ).annotate({ + description: + "This is the path to the user's config.toml file, though it is not guaranteed to exist.", + }), + profile: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.", + }), + Schema.Null, + ]), ), - type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), - }).annotate({ title: "TextUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__ImageDetail, Schema.Null])), - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), + type: Schema.Literal("user").annotate({ title: "UserConfigLayerSourceType" }), + }).annotate({ + title: "UserConfigLayerSource", + description: + "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", + }), Schema.Struct({ - type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), - url: Schema.String, - }).annotate({ title: "AudioUserInput" }), + dotCodexFolder: V2ConfigReadResponse__AbsolutePathBuf, + type: Schema.Literal("project").annotate({ title: "ProjectConfigLayerSourceType" }), + }).annotate({ + title: "ProjectConfigLayerSource", + description: + "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + }), Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), + type: Schema.Literal("sessionFlags").annotate({ title: "SessionFlagsConfigLayerSourceType" }), + }).annotate({ + title: "SessionFlagsConfigLayerSource", + description: "Session-layer overrides supplied via `-c`/`--config`.", + }), Schema.Struct({ - name: Schema.String, - path: Schema.String, - type: Schema.Literal("skill").annotate({ title: "SkillUserInputType" }), - }).annotate({ title: "SkillUserInput" }), + file: V2ConfigReadResponse__AbsolutePathBuf, + type: Schema.Literal("legacyManagedConfigTomlFromFile").annotate({ + title: "LegacyManagedConfigTomlFromFileConfigLayerSourceType", + }), + }).annotate({ + title: "LegacyManagedConfigTomlFromFileConfigLayerSource", + description: + '`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a "best effort" while we phase out `managed_config.toml` in favor of `requirements.toml`.', + }), Schema.Struct({ - name: Schema.String, - path: Schema.String, - type: Schema.Literal("mention").annotate({ title: "MentionUserInputType" }), - }).annotate({ title: "MentionUserInput" }), + type: Schema.Literal("legacyManagedConfigTomlFromMdm").annotate({ + title: "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", + }), + }).annotate({ title: "LegacyManagedConfigTomlFromMdmConfigLayerSource" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ConfigReadResponse__ConfigLayerSource" }); -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 type V2ConfigReadResponse__AppsDefaultConfig = { + readonly approvals_reviewer?: V2ConfigReadResponse__ApprovalsReviewer | null; + readonly default_tools_approval_mode?: V2ConfigReadResponse__AppToolApproval | null; + readonly destructive_enabled?: boolean; + readonly enabled?: boolean; + readonly open_world_enabled?: boolean; }; -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 const V2ConfigReadResponse__AppsDefaultConfig = Schema.Struct({ + approvals_reviewer: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__ApprovalsReviewer, Schema.Null]), + ), + default_tools_approval_mode: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AppToolApproval, Schema.Null]), + ), + destructive_enabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), + enabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), + open_world_enabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), +}).annotate({ identifier: "V2ConfigReadResponse__AppsDefaultConfig" }); -export type V2SkillsListResponse__SkillDependencies = { - readonly tools: ReadonlyArray; +export type V2ConfigRequirementsReadResponse__ComputerUseMacosRequirements = { + readonly bundleIds?: { + readonly [x: string]: V2ConfigRequirementsReadResponse__AllowDenyRequirement; + } | null; }; -export const V2SkillsListResponse__SkillDependencies = Schema.Struct({ - tools: Schema.Array(V2SkillsListResponse__SkillToolDependency), -}); +export const V2ConfigRequirementsReadResponse__ComputerUseMacosRequirements = Schema.Struct({ + bundleIds: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, V2ConfigRequirementsReadResponse__AllowDenyRequirement), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ComputerUseMacosRequirements" }); -export type V2ThreadForkResponse__CommandAction = +export type V2ConfigRequirementsReadResponse__ComputerUseWindowsExeRequirement = { + readonly access: V2ConfigRequirementsReadResponse__AllowDenyRequirement; + readonly binaryName?: string | null; + readonly productName: string; + readonly publisherName: string; +}; +export const V2ConfigRequirementsReadResponse__ComputerUseWindowsExeRequirement = Schema.Struct({ + access: V2ConfigRequirementsReadResponse__AllowDenyRequirement, + binaryName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + productName: Schema.String, + publisherName: Schema.String, +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ComputerUseWindowsExeRequirement" }); + +export type V2ConfigRequirementsReadResponse__BrowserUseOriginPolicy = { + readonly access?: V2ConfigRequirementsReadResponse__AllowDenyRequirement | null; + readonly accessApprovalLifetime?: V2ConfigRequirementsReadResponse__BrowserUseAccessApprovalLifetime | null; + readonly autoReview?: V2ConfigRequirementsReadResponse__AllowDenyRequirement | null; + readonly downloads?: V2ConfigRequirementsReadResponse__AllowDenyRequirement | null; + readonly fullCdpAccess?: V2ConfigRequirementsReadResponse__AllowDenyRequirement | null; + readonly persistentApproval?: boolean | null; + readonly uploads?: V2ConfigRequirementsReadResponse__AllowDenyRequirement | null; +}; +export const V2ConfigRequirementsReadResponse__BrowserUseOriginPolicy = Schema.Struct({ + access: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__AllowDenyRequirement, Schema.Null]), + ), + accessApprovalLifetime: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__BrowserUseAccessApprovalLifetime, Schema.Null]), + ), + autoReview: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__AllowDenyRequirement, Schema.Null]), + ), + downloads: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__AllowDenyRequirement, Schema.Null]), + ), + fullCdpAccess: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__AllowDenyRequirement, Schema.Null]), + ), + persistentApproval: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + uploads: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__AllowDenyRequirement, Schema.Null]), + ), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__BrowserUseOriginPolicy" }); + +export type V2ConfigRequirementsReadResponse__NewThreadModelDefaults = { + readonly model?: string | null; + readonly modelReasoningEffort?: V2ConfigRequirementsReadResponse__ReasoningEffort | null; + readonly serviceTier?: string | null; +}; +export const V2ConfigRequirementsReadResponse__NewThreadModelDefaults = Schema.Struct({ + model: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + modelReasoningEffort: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__ReasoningEffort, Schema.Null]), + ), + serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__NewThreadModelDefaults" }); + +export type V2ConfigRequirementsReadResponse__ApplicationNetworkRequirements = { + readonly domains: { + readonly [x: string]: V2ConfigRequirementsReadResponse__NetworkDomainPermission; + }; + readonly enabled: boolean; +}; +export const V2ConfigRequirementsReadResponse__ApplicationNetworkRequirements = Schema.Struct({ + domains: Schema.Record(Schema.String, V2ConfigRequirementsReadResponse__NetworkDomainPermission), + enabled: Schema.Boolean.annotate({ + description: "When enabled, only explicitly allowed exact domains may be contacted.", + }), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ApplicationNetworkRequirements" }); + +export type V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup = { + readonly hooks: ReadonlyArray; + readonly matcher?: string | null; +}; +export const V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup = Schema.Struct({ + hooks: Schema.Array(V2ConfigRequirementsReadResponse__ConfiguredHookHandler), + matcher: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup" }); + +export type V2ConfigWarningNotification__TextRange = { + readonly end: V2ConfigWarningNotification__TextPosition; + readonly start: V2ConfigWarningNotification__TextPosition; +}; +export const V2ConfigWarningNotification__TextRange = Schema.Struct({ + end: V2ConfigWarningNotification__TextPosition, + start: V2ConfigWarningNotification__TextPosition, +}).annotate({ identifier: "V2ConfigWarningNotification__TextRange" }); + +export type V2ConfigWriteResponse__ConfigLayerSource = + | { readonly file: V2ConfigWriteResponse__AbsolutePathBuf; readonly type: "packagedDefaults" } + | { readonly domain: string; readonly key: string; readonly type: "mdm" } + | { readonly file: V2ConfigWriteResponse__AbsolutePathBuf; readonly type: "system" } + | { readonly id: string; readonly name: string; readonly type: "enterpriseManaged" } | { - readonly command: string; - readonly name: string; - readonly path: V2ThreadForkResponse__AbsolutePathBuf; - readonly type: "read"; + readonly file: V2ConfigWriteResponse__AbsolutePathBuf; + readonly profile?: string | null; + readonly type: "user"; } - | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } + | { readonly dotCodexFolder: V2ConfigWriteResponse__AbsolutePathBuf; readonly type: "project" } + | { readonly type: "sessionFlags" } | { - readonly command: string; - readonly path?: string | null; - readonly query?: string | null; - readonly type: "search"; + readonly file: V2ConfigWriteResponse__AbsolutePathBuf; + readonly type: "legacyManagedConfigTomlFromFile"; } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadForkResponse__CommandAction = Schema.Union( + | { readonly type: "legacyManagedConfigTomlFromMdm" }; +export const V2ConfigWriteResponse__ConfigLayerSource = Schema.Union( [ Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: V2ThreadForkResponse__AbsolutePathBuf, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), + file: Schema.suspend( + (): Schema.Codec => + V2ConfigWriteResponse__AbsolutePathBuf, + ).annotate({ description: "Path to the packaged default configuration file." }), + type: Schema.Literal("packagedDefaults").annotate({ + title: "PackagedDefaultsConfigLayerSourceType", + }), + }).annotate({ + title: "PackagedDefaultsConfigLayerSource", + description: "Default configuration supplied with the installed Codex package.", + }), Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), + domain: Schema.String, + key: Schema.String, + type: Schema.Literal("mdm").annotate({ title: "MdmConfigLayerSourceType" }), + }).annotate({ + title: "MdmConfigLayerSource", + description: "Managed preferences layer delivered by MDM (macOS only).", + }), 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" }), + file: Schema.suspend( + (): Schema.Codec => + V2ConfigWriteResponse__AbsolutePathBuf, + ).annotate({ + description: + "This is the path to the system config.toml file, though it is not guaranteed to exist.", + }), + type: Schema.Literal("system").annotate({ title: "SystemConfigLayerSourceType" }), + }).annotate({ + title: "SystemConfigLayerSource", + description: "Managed config layer from a file (usually `managed_config.toml`).", + }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + id: Schema.String.annotate({ description: "Stable identifier for the delivered layer." }), + name: Schema.String.annotate({ + description: + "Admin-facing name for the delivered layer. This is surfaced in diagnostics so users know which cloud layer needs administrator attention.", + }), + type: Schema.Literal("enterpriseManaged").annotate({ + title: "EnterpriseManagedConfigLayerSourceType", + }), + }).annotate({ + title: "EnterpriseManagedConfigLayerSource", + description: "Enterprise-managed config layer delivered by the cloud config bundle.", + }), + Schema.Struct({ + file: Schema.suspend( + (): Schema.Codec => + V2ConfigWriteResponse__AbsolutePathBuf, + ).annotate({ + description: + "This is the path to the user's config.toml file, though it is not guaranteed to exist.", + }), + profile: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Name of the selected profile-v2 config layered on top of the base user config, when this layer represents one.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("user").annotate({ title: "UserConfigLayerSourceType" }), + }).annotate({ + title: "UserConfigLayerSource", + description: + "User config layer from $CODEX_HOME/config.toml. This layer is special in that it is expected to be: - writable by the user - generally outside the workspace directory", + }), + Schema.Struct({ + dotCodexFolder: V2ConfigWriteResponse__AbsolutePathBuf, + type: Schema.Literal("project").annotate({ title: "ProjectConfigLayerSourceType" }), + }).annotate({ + title: "ProjectConfigLayerSource", + description: + "Path to a .codex/ folder within a project. There could be multiple of these between `cwd` and the project/repo root.", + }), + Schema.Struct({ + type: Schema.Literal("sessionFlags").annotate({ title: "SessionFlagsConfigLayerSourceType" }), + }).annotate({ + title: "SessionFlagsConfigLayerSource", + description: "Session-layer overrides supplied via `-c`/`--config`.", + }), + Schema.Struct({ + file: V2ConfigWriteResponse__AbsolutePathBuf, + type: Schema.Literal("legacyManagedConfigTomlFromFile").annotate({ + title: "LegacyManagedConfigTomlFromFileConfigLayerSourceType", + }), + }).annotate({ + title: "LegacyManagedConfigTomlFromFileConfigLayerSource", + description: + '`managed_config.toml` was designed to be a config that was loaded as the last layer on top of everything else. This scheme did not quite work out as intended, but we keep this variant as a "best effort" while we phase out `managed_config.toml` in favor of `requirements.toml`.', + }), + Schema.Struct({ + type: Schema.Literal("legacyManagedConfigTomlFromMdm").annotate({ + title: "LegacyManagedConfigTomlFromMdmConfigLayerSourceType", + }), + }).annotate({ title: "LegacyManagedConfigTomlFromMdmConfigLayerSource" }), ], { mode: "oneOf" }, -); - -export type V2ThreadForkResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadForkResponse__CollabAgentStatus; -}; -export const V2ThreadForkResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadForkResponse__CollabAgentStatus, -}); - -export type V2ThreadForkResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const V2ThreadForkResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadForkResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); +).annotate({ identifier: "V2ConfigWriteResponse__ConfigLayerSource" }); -export type V2ThreadForkResponse__CodexErrorInfo = +export type V2ErrorNotification__CodexErrorInfo = | "contextWindowExceeded" | "sessionBudgetExceeded" | "usageLimitExceeded" + | "rateLimitExceeded" | "serverOverloaded" | "cyberPolicy" + | "misalignmentPolicyViolation" | "internalServerError" | "unauthorized" | "badRequest" | "threadRollbackFailed" | "sandboxError" - | "rateLimitExceeded" - | "misalignmentPolicyViolation" | "other" | { readonly httpConnectionFailed: { readonly httpStatusCode?: number | null } } | { readonly responseStreamConnectionFailed: { readonly httpStatusCode?: number | null } } @@ -15956,24 +20013,24 @@ export type V2ThreadForkResponse__CodexErrorInfo = | { readonly responseTooManyFailedAttempts: { readonly httpStatusCode?: number | null } } | { readonly activeTurnNotSteerable: { - readonly turnKind: V2ThreadForkResponse__NonSteerableTurnKind; + readonly turnKind: V2ErrorNotification__NonSteerableTurnKind; }; }; -export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( +export const V2ErrorNotification__CodexErrorInfo = Schema.Union( [ Schema.Literals([ "contextWindowExceeded", "sessionBudgetExceeded", "usageLimitExceeded", + "rateLimitExceeded", "serverOverloaded", "cyberPolicy", + "misalignmentPolicyViolation", "internalServerError", "unauthorized", "badRequest", "threadRollbackFailed", "sandboxError", - "rateLimitExceeded", - "misalignmentPolicyViolation", "other", ]), Schema.Struct({ @@ -15981,8 +20038,12 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -15993,8 +20054,12 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -16008,8 +20073,12 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -16024,8 +20093,12 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( httpStatusCode: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -16036,7 +20109,7 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( }), Schema.Struct({ activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadForkResponse__NonSteerableTurnKind, + turnKind: V2ErrorNotification__NonSteerableTurnKind, }), }).annotate({ title: "ActiveTurnNotSteerableCodexErrorInfo", @@ -16048,190 +20121,8588 @@ export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( ).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.", + identifier: "V2ErrorNotification__CodexErrorInfo", }); -export type V2ThreadForkResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadForkResponse__PatchChangeKind; - readonly path: string; +export type V2ErrorNotification__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ErrorNotification__MisalignmentSteer | null; }; -export const V2ThreadForkResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadForkResponse__PatchChangeKind, - path: Schema.String, -}); - -export type V2ThreadForkResponse__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } - | { - readonly detail?: V2ThreadForkResponse__ImageDetail | null; - readonly type: "image"; - readonly url: string; - } - | { - readonly detail?: V2ThreadForkResponse__ImageDetail | null; - readonly path: string; - readonly type: "localImage"; - } - | { readonly type: "audio"; readonly url: string } - | { readonly path: string; readonly type: "localAudio" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadForkResponse__UserInput = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(V2ThreadForkResponse__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({ - detail: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__ImageDetail, Schema.Null])), - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), - Schema.Struct({ - type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), - url: Schema.String, - }).annotate({ title: "AudioUserInput" }), - Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), - 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 V2ThreadForkResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" - | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadForkResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadForkResponse__ThreadId; - }; - } - | { readonly other: string }; -export const V2ThreadForkResponse__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([V2ThreadForkResponse__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: V2ThreadForkResponse__ThreadId, +export const V2ErrorNotification__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadGoalGetResponse__ThreadGoal = { - readonly createdAt: number; - readonly objective: string; - readonly status: V2ThreadGoalGetResponse__ThreadGoalStatus; - readonly threadId: string; - readonly timeUsedSeconds: number; - readonly tokenBudget?: number | null; - readonly tokensUsed: number; - readonly updatedAt: number; -}; -export const V2ThreadGoalGetResponse__ThreadGoal = Schema.Struct({ - createdAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - objective: Schema.String, - status: V2ThreadGoalGetResponse__ThreadGoalStatus, - threadId: Schema.String, - timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - tokenBudget: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Null, + ]), ), - tokensUsed: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - updatedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -}); - -export type V2ThreadGoalSetResponse__ThreadGoal = { - readonly createdAt: number; - readonly objective: string; - readonly status: V2ThreadGoalSetResponse__ThreadGoalStatus; - readonly threadId: string; - readonly timeUsedSeconds: number; - readonly tokenBudget?: number | null; - readonly tokensUsed: number; - readonly updatedAt: number; -}; -export const V2ThreadGoalSetResponse__ThreadGoal = Schema.Struct({ - createdAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - objective: Schema.String, - status: V2ThreadGoalSetResponse__ThreadGoalStatus, - threadId: Schema.String, - timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - tokenBudget: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), ), - tokensUsed: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - updatedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), -}); + steer: Schema.optionalKey( + Schema.Union([V2ErrorNotification__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ErrorNotification__MisalignmentErrorDetails" }); -export type V2ThreadGoalUpdatedNotification__ThreadGoal = { - readonly createdAt: number; - readonly objective: string; - readonly status: V2ThreadGoalUpdatedNotification__ThreadGoalStatus; - readonly threadId: string; - readonly timeUsedSeconds: number; - readonly tokenBudget?: number | null; - readonly tokensUsed: number; - readonly updatedAt: number; +export type V2ExperimentalFeatureListResponse__ExperimentalFeature = { + readonly announcement?: string | null; + readonly defaultEnabled: boolean; + readonly description?: string | null; + readonly displayName?: string | null; + readonly enabled: boolean; + readonly name: string; + readonly stage: V2ExperimentalFeatureListResponse__ExperimentalFeatureStage; }; -export const V2ThreadGoalUpdatedNotification__ThreadGoal = Schema.Struct({ - createdAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - objective: Schema.String, - status: V2ThreadGoalUpdatedNotification__ThreadGoalStatus, +export const V2ExperimentalFeatureListResponse__ExperimentalFeature = Schema.Struct({ + announcement: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Announcement copy shown to users when the feature is introduced. Null when this feature is not in beta.", + }), + Schema.Null, + ]), + ), + defaultEnabled: Schema.Boolean.annotate({ + description: "Whether this feature is enabled by default.", + }), + description: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Short summary describing what the feature does. Null when this feature is not in beta.", + }), + Schema.Null, + ]), + ), + displayName: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "User-facing display name shown in the experimental features UI. Null when this feature is not in beta.", + }), + Schema.Null, + ]), + ), + enabled: Schema.Boolean.annotate({ + description: "Whether this feature is currently enabled in the loaded config.", + }), + name: Schema.String.annotate({ + description: "Stable key used in config.toml and CLI flag toggles.", + }), + stage: Schema.suspend( + (): Schema.Codec => + V2ExperimentalFeatureListResponse__ExperimentalFeatureStage, + ).annotate({ description: "Lifecycle stage of this feature flag." }), +}).annotate({ identifier: "V2ExperimentalFeatureListResponse__ExperimentalFeature" }); + +export type V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorCandidate = { + readonly name: string; + readonly sessionCount: number; + readonly source: V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorSource; +}; +export const V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorCandidate = + Schema.Struct({ + name: Schema.String, + sessionCount: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + source: V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorSource, + }).annotate({ + identifier: "V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorCandidate", + }); + +export type V2ExternalAgentConfigDetectResponse__MigrationDetails = { + readonly commands?: ReadonlyArray; + readonly hooks?: ReadonlyArray; + readonly mcpServers?: ReadonlyArray; + readonly memory?: ReadonlyArray; + readonly plugins?: ReadonlyArray; + readonly sessions?: ReadonlyArray; + readonly skills?: ReadonlyArray; + readonly subagents?: ReadonlyArray; +}; +export const V2ExternalAgentConfigDetectResponse__MigrationDetails = Schema.Struct({ + commands: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigDetectResponse__CommandMigration).annotate({ default: [] }), + ), + hooks: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigDetectResponse__HookMigration).annotate({ default: [] }), + ), + mcpServers: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigDetectResponse__McpServerMigration).annotate({ default: [] }), + ), + memory: Schema.optionalKey(Schema.Array(Schema.String)), + plugins: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigDetectResponse__PluginsMigration).annotate({ default: [] }), + ), + sessions: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigDetectResponse__SessionMigration).annotate({ default: [] }), + ), + skills: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigDetectResponse__SkillMigration).annotate({ default: [] }), + ), + subagents: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigDetectResponse__SubagentMigration).annotate({ default: [] }), + ), +}).annotate({ identifier: "V2ExternalAgentConfigDetectResponse__MigrationDetails" }); + +export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeFailure = + { + readonly cwd?: string | null; + readonly errorType?: string | null; + readonly failureStage: string; + readonly itemType: V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType; + readonly message: string; + readonly source?: string | null; + readonly subErrorType?: string | null; + }; +export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeFailure = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + failureStage: Schema.String, + itemType: + V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType, + message: Schema.String, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeFailure", + }); + +export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeSuccess = + { + readonly cwd?: string | null; + readonly itemType: V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType; + readonly source?: string | null; + readonly target?: string | null; + readonly title?: string | null; + }; +export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeSuccess = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + itemType: + V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Original title for an imported session; null for other item types.", + }), + Schema.Null, + ]), + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeSuccess", + }); + +export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorCandidate = + { + readonly name: string; + readonly sessionCount: number; + readonly source: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorSource; + }; +export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorCandidate = + Schema.Struct({ + name: Schema.String, + sessionCount: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + source: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorSource, + }).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentImportedConnectorCandidate", + }); + +export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeFailure = + { + readonly cwd?: string | null; + readonly errorType?: string | null; + readonly failureStage: string; + readonly itemType: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType; + readonly message: string; + readonly source?: string | null; + readonly subErrorType?: string | null; + }; +export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeFailure = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + failureStage: Schema.String, + itemType: + V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType, + message: Schema.String, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeFailure", + }); + +export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeSuccess = + { + readonly cwd?: string | null; + readonly itemType: V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType; + readonly source?: string | null; + readonly target?: string | null; + readonly title?: string | null; + }; +export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeSuccess = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + itemType: + V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigMigrationItemType, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Original title for an imported session; null for other item types.", + }), + Schema.Null, + ]), + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeSuccess", + }); + +export type V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportItemTypeFailure = + { + readonly cwd?: string | null; + readonly errorType?: string | null; + readonly failureStage: string; + readonly itemType: V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType; + readonly message: string; + readonly source?: string | null; + readonly subErrorType?: string | null; + }; +export const V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportItemTypeFailure = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + failureStage: Schema.String, + itemType: V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType, + message: Schema.String, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportItemTypeFailure", + }); + +export type V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordSuccessParams = + { + readonly cwd?: string | null; + readonly itemType: V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType; + readonly source?: string | null; + readonly target?: string | null; + readonly title?: string | null; + }; +export const V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordSuccessParams = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + itemType: V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Original title for an imported session, when available.", + }), + Schema.Null, + ]), + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordSuccessParams", + }); + +export type V2ExternalAgentConfigImportParams__MigrationDetails = { + readonly commands?: ReadonlyArray; + readonly hooks?: ReadonlyArray; + readonly mcpServers?: ReadonlyArray; + readonly memory?: ReadonlyArray; + readonly plugins?: ReadonlyArray; + readonly sessions?: ReadonlyArray; + readonly skills?: ReadonlyArray; + readonly subagents?: ReadonlyArray; +}; +export const V2ExternalAgentConfigImportParams__MigrationDetails = Schema.Struct({ + commands: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigImportParams__CommandMigration).annotate({ default: [] }), + ), + hooks: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigImportParams__HookMigration).annotate({ default: [] }), + ), + mcpServers: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigImportParams__McpServerMigration).annotate({ default: [] }), + ), + memory: Schema.optionalKey(Schema.Array(Schema.String)), + plugins: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigImportParams__PluginsMigration).annotate({ default: [] }), + ), + sessions: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigImportParams__SessionMigration).annotate({ default: [] }), + ), + skills: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigImportParams__SkillMigration).annotate({ default: [] }), + ), + subagents: Schema.optionalKey( + Schema.Array(V2ExternalAgentConfigImportParams__SubagentMigration).annotate({ default: [] }), + ), +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__MigrationDetails" }); + +export type V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeFailure = + { + readonly cwd?: string | null; + readonly errorType?: string | null; + readonly failureStage: string; + readonly itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType; + readonly message: string; + readonly source?: string | null; + readonly subErrorType?: string | null; + }; +export const V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeFailure = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + errorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + failureStage: Schema.String, + itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType, + message: Schema.String, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + subErrorType: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeFailure", + }); + +export type V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeSuccess = + { + readonly cwd?: string | null; + readonly itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType; + readonly source?: string | null; + readonly target?: string | null; + readonly title?: string | null; + }; +export const V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeSuccess = + Schema.Struct({ + cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType, + source: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + target: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + title: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Original title for an imported session; null for other item types.", + }), + Schema.Null, + ]), + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeSuccess", + }); + +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, +}).annotate({ identifier: "V2FileChangePatchUpdatedNotification__FileUpdateChange" }); + +export type V2GetAccountRateLimitsResponse__RateLimitResetCredit = { + readonly description?: string | null; + readonly expiresAt?: number | null; + readonly grantedAt: number; + readonly id: string; + readonly resetType: V2GetAccountRateLimitsResponse__RateLimitResetType; + readonly status: V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus; + readonly title?: string | null; +}; +export const V2GetAccountRateLimitsResponse__RateLimitResetCredit = Schema.Struct({ + description: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Backend-provided display description for this credit, or `null` when unavailable.", + }), + Schema.Null, + ]), + ), + expiresAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Unix timestamp in seconds when the credit expires, or `null` if it does not expire.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + grantedAt: Schema.Number.annotate({ + description: "Unix timestamp in seconds when the credit was granted.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + id: Schema.String.annotate({ description: "Opaque backend identifier for this reset credit." }), + resetType: V2GetAccountRateLimitsResponse__RateLimitResetType, + status: V2GetAccountRateLimitsResponse__RateLimitResetCreditStatus, + title: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Backend-provided display title for this credit, or `null` when unavailable.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2GetAccountRateLimitsResponse__RateLimitResetCredit" }); + +export type V2GetAccountRateLimitsResponse__RateLimitSnapshot = { + readonly credits?: V2GetAccountRateLimitsResponse__CreditsSnapshot | null; + readonly individualLimit?: V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot | null; + readonly limitId?: string | null; + readonly limitName?: string | null; + readonly normalModelSlug?: string | null; + readonly planType?: V2GetAccountRateLimitsResponse__PlanType | null; + readonly primary?: V2GetAccountRateLimitsResponse__RateLimitWindow | null; + readonly rateLimitReachedType?: V2GetAccountRateLimitsResponse__RateLimitReachedType | null; + readonly secondary?: V2GetAccountRateLimitsResponse__RateLimitWindow | null; + readonly spendControlReached?: boolean | null; +}; +export const V2GetAccountRateLimitsResponse__RateLimitSnapshot = Schema.Struct({ + credits: Schema.optionalKey( + Schema.Union([V2GetAccountRateLimitsResponse__CreditsSnapshot, Schema.Null]), + ), + individualLimit: Schema.optionalKey( + Schema.Union([V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot, Schema.Null]), + ), + limitId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + limitName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + normalModelSlug: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Normal model whose display name and reasoning options describe this quota alias.", + }), + Schema.Null, + ]), + ), + planType: Schema.optionalKey( + Schema.Union([V2GetAccountRateLimitsResponse__PlanType, Schema.Null]), + ), + 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]), + ), + spendControlReached: Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + description: + "Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2GetAccountRateLimitsResponse__RateLimitSnapshot" }); + +export type V2GetAccountResponse__Account = + | { readonly type: "apiKey" } + | { + readonly email: string | null; + readonly planType: V2GetAccountResponse__PlanType; + readonly type: "chatgpt"; + } + | { readonly type: "amazonBedrock"; readonly usesCodexManagedCredentials?: boolean }; +export const V2GetAccountResponse__Account = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("apiKey").annotate({ title: "ApiKeyAccountType" }), + }).annotate({ title: "ApiKeyAccount" }), + Schema.Struct({ + email: Schema.Union([Schema.String, Schema.Null]), + planType: V2GetAccountResponse__PlanType, + type: Schema.Literal("chatgpt").annotate({ title: "ChatgptAccountType" }), + }).annotate({ title: "ChatgptAccount" }), + Schema.Struct({ + type: Schema.Literal("amazonBedrock").annotate({ title: "AmazonBedrockAccountType" }), + usesCodexManagedCredentials: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + }).annotate({ title: "AmazonBedrockAccount" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2GetAccountResponse__Account" }); + +export type V2GetAccountTokenUsageResponse__ThreadUsage = { + readonly estimatedUsageCreditsMicros: number; + readonly estimatedUsageUsdMicros?: number | null; + readonly groups: ReadonlyArray; + readonly threadId: string; +}; +export const V2GetAccountTokenUsageResponse__ThreadUsage = Schema.Struct({ + estimatedUsageCreditsMicros: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + estimatedUsageUsdMicros: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + groups: Schema.Array(V2GetAccountTokenUsageResponse__ThreadUsageBreakdownGroup), threadId: Schema.String, - timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - tokenBudget: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), +}).annotate({ identifier: "V2GetAccountTokenUsageResponse__ThreadUsage" }); + +export type V2GetWorkspaceMessagesResponse__WorkspaceMessage = { + readonly archivedAt?: number | null; + readonly createdAt?: number | null; + readonly messageBody: string; + readonly messageId: string; + readonly messageType: V2GetWorkspaceMessagesResponse__WorkspaceMessageType; +}; +export const V2GetWorkspaceMessagesResponse__WorkspaceMessage = Schema.Struct({ + archivedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp (in seconds) when the message was archived.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + createdAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp (in seconds) when the message was created.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), ), - tokensUsed: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - updatedAt: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + messageBody: Schema.String, + messageId: Schema.String, + messageType: V2GetWorkspaceMessagesResponse__WorkspaceMessageType, +}).annotate({ identifier: "V2GetWorkspaceMessagesResponse__WorkspaceMessage" }); + +export type V2HookCompletedNotification__HookOutputEntry = { + readonly kind: V2HookCompletedNotification__HookOutputEntryKind; + readonly text: string; +}; +export const V2HookCompletedNotification__HookOutputEntry = Schema.Struct({ + kind: V2HookCompletedNotification__HookOutputEntryKind, + text: Schema.String, +}).annotate({ identifier: "V2HookCompletedNotification__HookOutputEntry" }); + +export type V2HooksListResponse__HookMetadata = + | { + readonly additionalContextLimit?: number | null; + readonly currentHash: string; + readonly displayOrder: number; + readonly enabled: boolean; + readonly eventName: V2HooksListResponse__HookEventName; + readonly isManaged: boolean; + readonly key: string; + readonly matcher?: string | null; + readonly pluginId?: string | null; + readonly source: V2HooksListResponse__HookSource; + readonly sourcePath: V2HooksListResponse__AbsolutePathBuf; + readonly statusMessage?: string | null; + readonly timeoutSec: number; + readonly trustStatus: V2HooksListResponse__HookTrustStatus; + readonly async?: boolean; + readonly command: string; + readonly handlerType: "command"; + } + | { + readonly additionalContextLimit?: number | null; + readonly currentHash: string; + readonly displayOrder: number; + readonly enabled: boolean; + readonly eventName: V2HooksListResponse__HookEventName; + readonly isManaged: boolean; + readonly key: string; + readonly matcher?: string | null; + readonly pluginId?: string | null; + readonly source: V2HooksListResponse__HookSource; + readonly sourcePath: V2HooksListResponse__AbsolutePathBuf; + readonly statusMessage?: string | null; + readonly timeoutSec: number; + readonly trustStatus: V2HooksListResponse__HookTrustStatus; + readonly handlerType: "mcpTool"; + readonly server: string; + readonly tool: string; + } + | { + readonly additionalContextLimit?: number | null; + readonly currentHash: string; + readonly displayOrder: number; + readonly enabled: boolean; + readonly eventName: V2HooksListResponse__HookEventName; + readonly isManaged: boolean; + readonly key: string; + readonly matcher?: string | null; + readonly pluginId?: string | null; + readonly source: V2HooksListResponse__HookSource; + readonly sourcePath: V2HooksListResponse__AbsolutePathBuf; + readonly statusMessage?: string | null; + readonly timeoutSec: number; + readonly trustStatus: V2HooksListResponse__HookTrustStatus; + readonly handlerType: "prompt"; + } + | { + readonly additionalContextLimit?: number | null; + readonly currentHash: string; + readonly displayOrder: number; + readonly enabled: boolean; + readonly eventName: V2HooksListResponse__HookEventName; + readonly isManaged: boolean; + readonly key: string; + readonly matcher?: string | null; + readonly pluginId?: string | null; + readonly source: V2HooksListResponse__HookSource; + readonly sourcePath: V2HooksListResponse__AbsolutePathBuf; + readonly statusMessage?: string | null; + readonly timeoutSec: number; + readonly trustStatus: V2HooksListResponse__HookTrustStatus; + readonly handlerType: "agent"; + }; +export const V2HooksListResponse__HookMetadata = Schema.Union( + [ + Schema.Struct({ + additionalContextLimit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Configured `additionalContext` spill threshold. `null` uses 2,500 tokens; `0` disables spilling.", + format: "uint", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + currentHash: Schema.String, + displayOrder: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + enabled: Schema.Boolean, + eventName: V2HooksListResponse__HookEventName, + isManaged: Schema.Boolean, + key: Schema.String, + matcher: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + source: V2HooksListResponse__HookSource, + sourcePath: V2HooksListResponse__AbsolutePathBuf, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + timeoutSec: Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + trustStatus: V2HooksListResponse__HookTrustStatus, + async: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + command: Schema.String, + handlerType: Schema.Literal("command"), + }), + Schema.Struct({ + additionalContextLimit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Configured `additionalContext` spill threshold. `null` uses 2,500 tokens; `0` disables spilling.", + format: "uint", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + currentHash: Schema.String, + displayOrder: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + enabled: Schema.Boolean, + eventName: V2HooksListResponse__HookEventName, + isManaged: Schema.Boolean, + key: Schema.String, + matcher: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + source: V2HooksListResponse__HookSource, + sourcePath: V2HooksListResponse__AbsolutePathBuf, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + timeoutSec: Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + trustStatus: V2HooksListResponse__HookTrustStatus, + handlerType: Schema.Literal("mcpTool"), + server: Schema.String, + tool: Schema.String, + }), + Schema.Struct({ + additionalContextLimit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Configured `additionalContext` spill threshold. `null` uses 2,500 tokens; `0` disables spilling.", + format: "uint", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + currentHash: Schema.String, + displayOrder: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + enabled: Schema.Boolean, + eventName: V2HooksListResponse__HookEventName, + isManaged: Schema.Boolean, + key: Schema.String, + matcher: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + source: V2HooksListResponse__HookSource, + sourcePath: V2HooksListResponse__AbsolutePathBuf, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + timeoutSec: Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + trustStatus: V2HooksListResponse__HookTrustStatus, + handlerType: Schema.Literal("prompt"), + }).annotate({ title: "PromptHookMetadata" }), + Schema.Struct({ + additionalContextLimit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Configured `additionalContext` spill threshold. `null` uses 2,500 tokens; `0` disables spilling.", + format: "uint", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + currentHash: Schema.String, + displayOrder: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + enabled: Schema.Boolean, + eventName: V2HooksListResponse__HookEventName, + isManaged: Schema.Boolean, + key: Schema.String, + matcher: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + source: V2HooksListResponse__HookSource, + sourcePath: V2HooksListResponse__AbsolutePathBuf, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + timeoutSec: Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + trustStatus: V2HooksListResponse__HookTrustStatus, + handlerType: Schema.Literal("agent"), + }).annotate({ title: "AgentHookMetadata" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2HooksListResponse__HookMetadata" }); + +export type V2HookStartedNotification__HookOutputEntry = { + readonly kind: V2HookStartedNotification__HookOutputEntryKind; + readonly text: string; +}; +export const V2HookStartedNotification__HookOutputEntry = Schema.Struct({ + kind: V2HookStartedNotification__HookOutputEntryKind, + text: Schema.String, +}).annotate({ identifier: "V2HookStartedNotification__HookOutputEntry" }); + +export type V2ItemCompletedNotification__TextElement = { + readonly byteRange: V2ItemCompletedNotification__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ItemCompletedNotification__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => + V2ItemCompletedNotification__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ItemCompletedNotification__TextElement" }); + +export type V2ItemCompletedNotification__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ItemCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ItemCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ItemCompletedNotification__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ItemCompletedNotification__FunctionCallOutputContentItem", +}); + +export type V2ItemCompletedNotification__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ItemCompletedNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ItemCompletedNotification__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ItemCompletedNotification__MemoryCitation" }); + +export type V2ItemCompletedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ItemCompletedNotification__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ItemCompletedNotification__CommandAction" }); + +export type V2ItemCompletedNotification__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ItemCompletedNotification__PatchChangeKind; + readonly path: string; +}; +export const V2ItemCompletedNotification__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ItemCompletedNotification__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ItemCompletedNotification__FileUpdateChange" }); + +export type V2ItemCompletedNotification__McpAppUi = { + readonly preferredModelDisplayMode: V2ItemCompletedNotification__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ItemCompletedNotification__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ItemCompletedNotification__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ItemCompletedNotification__McpAppUi", +}); + +export type V2ItemCompletedNotification__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ItemCompletedNotification__CollabAgentStatus; +}; +export const V2ItemCompletedNotification__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ItemCompletedNotification__CollabAgentStatus, +}).annotate({ identifier: "V2ItemCompletedNotification__CollabAgentState" }); + +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { + readonly kind: "project_roots"; + readonly subpath?: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString | null; + } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { + readonly kind: "unknown"; + readonly path: string; + readonly subpath?: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString | 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("project_roots"), + subpath: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, + 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([ + V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, + Schema.Null, + ]), + ), + }), + ], + { mode: "oneOf" }, + ).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath", + }); + +export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReview = { + readonly rationale?: string | null; + readonly riskLevel?: V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel | null; + readonly status: V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus; + readonly userAuthorization?: V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization | null; +}; +export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReview = + Schema.Struct({ + rationale: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + riskLevel: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewCompletedNotification__GuardianRiskLevel, + Schema.Null, + ]), + ), + status: V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewStatus, + userAuthorization: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewCompletedNotification__GuardianUserAuthorization, + Schema.Null, + ]), + ), + }).annotate({ + description: + "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReview", + }); + +export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath = + | { readonly kind: "root" } + | { readonly kind: "minimal" } + | { + readonly kind: "project_roots"; + readonly subpath?: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString | null; + } + | { readonly kind: "tmpdir" } + | { readonly kind: "slash_tmp" } + | { + readonly kind: "unknown"; + readonly path: string; + readonly subpath?: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString | 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("project_roots"), + subpath: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, + 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([ + V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, + Schema.Null, + ]), + ), + }), + ], + { mode: "oneOf" }, +).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath", +}); + +export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReview = { + readonly rationale?: string | null; + readonly riskLevel?: V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel | null; + readonly status: V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus; + readonly userAuthorization?: V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization | null; +}; +export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReview = + Schema.Struct({ + rationale: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + riskLevel: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewStartedNotification__GuardianRiskLevel, + Schema.Null, + ]), + ), + status: V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewStatus, + userAuthorization: Schema.optionalKey( + Schema.Union([ + V2ItemGuardianApprovalReviewStartedNotification__GuardianUserAuthorization, + Schema.Null, + ]), + ), + }).annotate({ + description: + "[UNSTABLE] Temporary approval auto-review payload used by `item/autoApprovalReview/*` notifications. This shape is expected to change soon.", + identifier: "V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReview", + }); + +export type V2ItemStartedNotification__TextElement = { + readonly byteRange: V2ItemStartedNotification__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ItemStartedNotification__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ItemStartedNotification__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ItemStartedNotification__TextElement" }); + +export type V2ItemStartedNotification__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ItemStartedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ItemStartedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ItemStartedNotification__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ItemStartedNotification__FunctionCallOutputContentItem", +}); + +export type V2ItemStartedNotification__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ItemStartedNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ItemStartedNotification__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ItemStartedNotification__MemoryCitation" }); + +export type V2ItemStartedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ItemStartedNotification__LegacyAppPathString; + 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: V2ItemStartedNotification__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ItemStartedNotification__CommandAction" }); + +export type V2ItemStartedNotification__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ItemStartedNotification__PatchChangeKind; + readonly path: string; +}; +export const V2ItemStartedNotification__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ItemStartedNotification__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ItemStartedNotification__FileUpdateChange" }); + +export type V2ItemStartedNotification__McpAppUi = { + readonly preferredModelDisplayMode: V2ItemStartedNotification__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ItemStartedNotification__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ItemStartedNotification__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ItemStartedNotification__McpAppUi", +}); + +export type V2ItemStartedNotification__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ItemStartedNotification__CollabAgentStatus; +}; +export const V2ItemStartedNotification__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ItemStartedNotification__CollabAgentStatus, +}).annotate({ identifier: "V2ItemStartedNotification__CollabAgentState" }); + +export type V2ListMcpServerStatusResponse__McpServerStatus = { + readonly authStatus: V2ListMcpServerStatusResponse__McpAuthStatus; + readonly name: string; + readonly pluginId?: string | null; + readonly resourceTemplates: ReadonlyArray; + readonly resources: ReadonlyArray; + readonly runtimeStatus?: V2ListMcpServerStatusResponse__McpServerConnectionStatus | null; + readonly serverCapabilities?: Schema.Json; + readonly serverInfo?: V2ListMcpServerStatusResponse__McpServerInfo | null; + readonly tools: { readonly [x: string]: V2ListMcpServerStatusResponse__Tool }; + readonly toolsError?: string | null; +}; +export const V2ListMcpServerStatusResponse__McpServerStatus = Schema.Struct({ + authStatus: V2ListMcpServerStatusResponse__McpAuthStatus, + name: Schema.String, + pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + resourceTemplates: Schema.Array(V2ListMcpServerStatusResponse__ResourceTemplate), + resources: Schema.Array(V2ListMcpServerStatusResponse__Resource), + runtimeStatus: Schema.optionalKey( + Schema.Union([V2ListMcpServerStatusResponse__McpServerConnectionStatus, Schema.Null]).annotate({ + description: + "Current thread-runtime connection state; null when unavailable or the configuration changed.", + }), + ), + serverCapabilities: Schema.optionalKey( + Schema.Json.annotate({ + expected: "JSON value", + description: "Capabilities advertised by the initialized MCP server; null when unavailable.", + }), + ), + serverInfo: Schema.optionalKey( + Schema.Union([V2ListMcpServerStatusResponse__McpServerInfo, Schema.Null]), + ), + tools: Schema.Record(Schema.String, V2ListMcpServerStatusResponse__Tool), + toolsError: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Tool discovery failed and no catalog was returned. Null when a catalog is returned, including cached or empty catalogs.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2ListMcpServerStatusResponse__McpServerStatus" }); + +export type V2ModelListResponse__ModelAccessPrograms = { + readonly cyber: ReadonlyArray; +}; +export const V2ModelListResponse__ModelAccessPrograms = Schema.Struct({ + cyber: Schema.Array(V2ModelListResponse__CyberAccessProgram).annotate({ + description: "Accepted explicit selections.", + }), +}).annotate({ + description: "Caller-specific explicit access programs advertised by model discovery.", + identifier: "V2ModelListResponse__ModelAccessPrograms", +}); + +export type V2ModelListResponse__ReasoningEffortOption = { + readonly description: string; + readonly reasoningEffort: V2ModelListResponse__ReasoningEffort; +}; +export const V2ModelListResponse__ReasoningEffortOption = Schema.Struct({ + description: Schema.String, + reasoningEffort: V2ModelListResponse__ReasoningEffort, +}).annotate({ identifier: "V2ModelListResponse__ReasoningEffortOption" }); + +export type V2PluginInstalledResponse__MarketplaceLoadErrorInfo = { + readonly marketplacePath: V2PluginInstalledResponse__AbsolutePathBuf; + readonly message: string; +}; +export const V2PluginInstalledResponse__MarketplaceLoadErrorInfo = Schema.Struct({ + marketplacePath: V2PluginInstalledResponse__AbsolutePathBuf, + message: Schema.String, +}).annotate({ identifier: "V2PluginInstalledResponse__MarketplaceLoadErrorInfo" }); + +export type V2PluginInstalledResponse__PluginInterface = { + readonly brandColor?: string | null; + readonly capabilities: ReadonlyArray; + readonly category?: string | null; + readonly composerIcon?: V2PluginInstalledResponse__AbsolutePathBuf | null; + readonly composerIconUrl?: string | null; + readonly defaultPrompt?: ReadonlyArray | null; + readonly developerName?: string | null; + readonly displayName?: string | null; + readonly logo?: V2PluginInstalledResponse__AbsolutePathBuf | null; + readonly logoDark?: V2PluginInstalledResponse__AbsolutePathBuf | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly longDescription?: string | null; + readonly privacyPolicyUrl?: string | null; + readonly screenshotUrls: ReadonlyArray; + readonly screenshots: ReadonlyArray; + readonly shortDescription?: string | null; + readonly termsOfServiceUrl?: string | null; + readonly websiteUrl?: string | null; +}; +export const V2PluginInstalledResponse__PluginInterface = Schema.Struct({ + brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + capabilities: Schema.Array(Schema.String), + category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + composerIcon: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__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([ + Schema.Array(Schema.String).annotate({ + description: + "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", + }), + Schema.Null, + ]), + ), + developerName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + logo: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local logo path, resolved from the installed plugin package.", + }), + ), + logoDark: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local dark-mode 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, + ]), + ), + logoUrlDark: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote dark-mode 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])), + screenshotUrls: Schema.Array(Schema.String).annotate({ + description: "Remote screenshot URLs from the plugin catalog.", + }), + screenshots: Schema.Array(V2PluginInstalledResponse__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])), +}).annotate({ identifier: "V2PluginInstalledResponse__PluginInterface" }); + +export type V2PluginInstalledResponse__PluginSource = + | { readonly path: V2PluginInstalledResponse__AbsolutePathBuf; readonly type: "local" } + | { + readonly path?: string | null; + readonly refName?: string | null; + readonly sha?: string | null; + readonly type: "git"; + readonly url: string; + } + | { + readonly package: string; + readonly registry?: string | null; + readonly type: "npm"; + readonly version?: string | null; + } + | { readonly type: "remote" }; +export const V2PluginInstalledResponse__PluginSource = Schema.Union( + [ + Schema.Struct({ + path: V2PluginInstalledResponse__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({ + package: Schema.String, + registry: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional HTTPS registry URL. Authentication stays in the user's npm config.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), + version: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Optional npm version or version range." }), + Schema.Null, + ]), + ), + }).annotate({ title: "NpmPluginSource" }), + 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" }, +).annotate({ identifier: "V2PluginInstalledResponse__PluginSource" }); + +export type V2PluginInstalledResponse__PluginSharePrincipal = { + readonly name: string; + readonly principalId: string; + readonly principalType: V2PluginInstalledResponse__PluginSharePrincipalType; + readonly role: V2PluginInstalledResponse__PluginSharePrincipalRole; +}; +export const V2PluginInstalledResponse__PluginSharePrincipal = Schema.Struct({ + name: Schema.String, + principalId: Schema.String, + principalType: V2PluginInstalledResponse__PluginSharePrincipalType, + role: V2PluginInstalledResponse__PluginSharePrincipalRole, +}).annotate({ identifier: "V2PluginInstalledResponse__PluginSharePrincipal" }); + +export type V2PluginListResponse__MarketplaceLoadErrorInfo = { + readonly marketplacePath: V2PluginListResponse__AbsolutePathBuf; + readonly message: string; +}; +export const V2PluginListResponse__MarketplaceLoadErrorInfo = Schema.Struct({ + marketplacePath: V2PluginListResponse__AbsolutePathBuf, + message: Schema.String, +}).annotate({ identifier: "V2PluginListResponse__MarketplaceLoadErrorInfo" }); + +export type V2PluginListResponse__PluginInterface = { + readonly brandColor?: string | null; + 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 logoDark?: V2PluginListResponse__AbsolutePathBuf | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly longDescription?: string | null; + readonly privacyPolicyUrl?: string | null; + readonly screenshotUrls: ReadonlyArray; + readonly screenshots: ReadonlyArray; + readonly shortDescription?: string | null; + readonly termsOfServiceUrl?: string | null; + readonly websiteUrl?: string | null; +}; +export const V2PluginListResponse__PluginInterface = Schema.Struct({ + brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + capabilities: Schema.Array(Schema.String), + category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + composerIcon: Schema.optionalKey( + 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([ + Schema.Array(Schema.String).annotate({ + description: + "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", + }), + Schema.Null, + ]), + ), + 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]).annotate({ + description: "Local logo path, resolved from the installed plugin package.", + }), + ), + logoDark: Schema.optionalKey( + Schema.Union([V2PluginListResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local dark-mode 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, + ]), + ), + logoUrlDark: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote dark-mode 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])), + 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])), +}).annotate({ identifier: "V2PluginListResponse__PluginInterface" }); + +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 package: string; + readonly registry?: string | null; + readonly type: "npm"; + readonly version?: string | null; + } + | { 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({ + package: Schema.String, + registry: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional HTTPS registry URL. Authentication stays in the user's npm config.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), + version: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Optional npm version or version range." }), + Schema.Null, + ]), + ), + }).annotate({ title: "NpmPluginSource" }), + 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" }, +).annotate({ identifier: "V2PluginListResponse__PluginSource" }); + +export type V2PluginListResponse__PluginSharePrincipal = { + readonly name: string; + readonly principalId: string; + readonly principalType: V2PluginListResponse__PluginSharePrincipalType; + readonly role: V2PluginListResponse__PluginSharePrincipalRole; +}; +export const V2PluginListResponse__PluginSharePrincipal = Schema.Struct({ + name: Schema.String, + principalId: Schema.String, + principalType: V2PluginListResponse__PluginSharePrincipalType, + role: V2PluginListResponse__PluginSharePrincipalRole, +}).annotate({ identifier: "V2PluginListResponse__PluginSharePrincipal" }); + +export type V2PluginReadResponse__AppTemplateSummary = { + readonly canonicalConnectorId?: string | null; + readonly category?: string | null; + readonly description?: string | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly materializedAppIds: ReadonlyArray; + readonly name: string; + readonly reason?: V2PluginReadResponse__AppTemplateUnavailableReason | null; + readonly templateId: string; +}; +export const V2PluginReadResponse__AppTemplateSummary = Schema.Struct({ + canonicalConnectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + materializedAppIds: Schema.Array(Schema.String), + name: Schema.String, + reason: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__AppTemplateUnavailableReason, Schema.Null]), + ), + templateId: Schema.String, +}).annotate({ identifier: "V2PluginReadResponse__AppTemplateSummary" }); + +export type V2PluginReadResponse__PluginHookSummary = { + readonly eventName: V2PluginReadResponse__HookEventName; + readonly key: string; +}; +export const V2PluginReadResponse__PluginHookSummary = Schema.Struct({ + eventName: V2PluginReadResponse__HookEventName, + key: Schema.String, +}).annotate({ identifier: "V2PluginReadResponse__PluginHookSummary" }); + +export type V2PluginReadResponse__SkillInterface = { + readonly brandColor?: string | null; + readonly defaultPrompt?: string | null; + readonly displayName?: string | null; + readonly iconLarge?: V2PluginReadResponse__AbsolutePathBuf | null; + readonly iconLargeUrl?: string | null; + readonly iconSmall?: V2PluginReadResponse__AbsolutePathBuf | null; + readonly iconSmallUrl?: 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([V2PluginReadResponse__AbsolutePathBuf, Schema.Null])), + iconLargeUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote large icon URL from the plugin catalog." }), + Schema.Null, + ]), + ), + iconSmall: Schema.optionalKey(Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null])), + iconSmallUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote small icon URL from the plugin catalog." }), + Schema.Null, + ]), + ), + shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2PluginReadResponse__SkillInterface" }); + +export type V2PluginReadResponse__PluginInterface = { + readonly brandColor?: string | null; + 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 logoDark?: V2PluginReadResponse__AbsolutePathBuf | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly longDescription?: string | null; + readonly privacyPolicyUrl?: string | null; + readonly screenshotUrls: ReadonlyArray; + readonly screenshots: ReadonlyArray; + readonly shortDescription?: string | null; + readonly termsOfServiceUrl?: string | null; + readonly websiteUrl?: string | null; +}; +export const V2PluginReadResponse__PluginInterface = Schema.Struct({ + brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + capabilities: Schema.Array(Schema.String), + category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + composerIcon: Schema.optionalKey( + 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([ + Schema.Array(Schema.String).annotate({ + description: + "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", + }), + Schema.Null, + ]), + ), + 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]).annotate({ + description: "Local logo path, resolved from the installed plugin package.", + }), + ), + logoDark: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local dark-mode 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, + ]), + ), + logoUrlDark: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote dark-mode 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])), + 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])), +}).annotate({ identifier: "V2PluginReadResponse__PluginInterface" }); + +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 package: string; + readonly registry?: string | null; + readonly type: "npm"; + readonly version?: string | null; + } + | { 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({ + package: Schema.String, + registry: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional HTTPS registry URL. Authentication stays in the user's npm config.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), + version: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Optional npm version or version range." }), + Schema.Null, + ]), + ), + }).annotate({ title: "NpmPluginSource" }), + 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" }, +).annotate({ identifier: "V2PluginReadResponse__PluginSource" }); + +export type V2PluginReadResponse__ScheduledTaskSchedule = + | { + readonly days?: ReadonlyArray | null; + readonly intervalHours: number; + readonly type: "hourly"; + } + | { readonly time: string; readonly type: "daily" } + | { readonly time: string; readonly type: "weekdays" } + | { + readonly days: ReadonlyArray; + readonly time: string; + readonly type: "weekly"; + }; +export const V2PluginReadResponse__ScheduledTaskSchedule = Schema.Union( + [ + Schema.Struct({ + days: Schema.optionalKey( + Schema.Union([Schema.Array(V2PluginReadResponse__ScheduledTaskWeekday), Schema.Null]), + ), + intervalHours: Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + type: Schema.Literal("hourly").annotate({ title: "HourlyScheduledTaskScheduleType" }), + }).annotate({ title: "HourlyScheduledTaskSchedule" }), + Schema.Struct({ + time: Schema.String, + type: Schema.Literal("daily").annotate({ title: "DailyScheduledTaskScheduleType" }), + }).annotate({ title: "DailyScheduledTaskSchedule" }), + Schema.Struct({ + time: Schema.String, + type: Schema.Literal("weekdays").annotate({ title: "WeekdaysScheduledTaskScheduleType" }), + }).annotate({ title: "WeekdaysScheduledTaskSchedule" }), + Schema.Struct({ + days: Schema.Array(V2PluginReadResponse__ScheduledTaskWeekday), + time: Schema.String, + type: Schema.Literal("weekly").annotate({ title: "WeeklyScheduledTaskScheduleType" }), + }).annotate({ title: "WeeklyScheduledTaskSchedule" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2PluginReadResponse__ScheduledTaskSchedule" }); + +export type V2PluginReadResponse__PluginSharePrincipal = { + readonly name: string; + readonly principalId: string; + readonly principalType: V2PluginReadResponse__PluginSharePrincipalType; + readonly role: V2PluginReadResponse__PluginSharePrincipalRole; +}; +export const V2PluginReadResponse__PluginSharePrincipal = Schema.Struct({ + name: Schema.String, + principalId: Schema.String, + principalType: V2PluginReadResponse__PluginSharePrincipalType, + role: V2PluginReadResponse__PluginSharePrincipalRole, +}).annotate({ identifier: "V2PluginReadResponse__PluginSharePrincipal" }); + +export type V2PluginShareListResponse__PluginInterface = { + readonly brandColor?: string | null; + readonly capabilities: ReadonlyArray; + readonly category?: string | null; + readonly composerIcon?: V2PluginShareListResponse__AbsolutePathBuf | null; + readonly composerIconUrl?: string | null; + readonly defaultPrompt?: ReadonlyArray | null; + readonly developerName?: string | null; + readonly displayName?: string | null; + readonly logo?: V2PluginShareListResponse__AbsolutePathBuf | null; + readonly logoDark?: V2PluginShareListResponse__AbsolutePathBuf | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly longDescription?: string | null; + readonly privacyPolicyUrl?: string | null; + readonly screenshotUrls: ReadonlyArray; + readonly screenshots: ReadonlyArray; + readonly shortDescription?: string | null; + readonly termsOfServiceUrl?: string | null; + readonly websiteUrl?: string | null; +}; +export const V2PluginShareListResponse__PluginInterface = Schema.Struct({ + brandColor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + capabilities: Schema.Array(Schema.String), + category: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + composerIcon: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__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([ + Schema.Array(Schema.String).annotate({ + description: + "Starter prompts for the plugin. Capped at 3 entries with a maximum of 128 characters per entry.", + }), + Schema.Null, + ]), + ), + developerName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + displayName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + logo: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local logo path, resolved from the installed plugin package.", + }), + ), + logoDark: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__AbsolutePathBuf, Schema.Null]).annotate({ + description: "Local dark-mode 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, + ]), + ), + logoUrlDark: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote dark-mode 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])), + screenshotUrls: Schema.Array(Schema.String).annotate({ + description: "Remote screenshot URLs from the plugin catalog.", + }), + screenshots: Schema.Array(V2PluginShareListResponse__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])), +}).annotate({ identifier: "V2PluginShareListResponse__PluginInterface" }); + +export type V2PluginShareListResponse__PluginSource = + | { readonly path: V2PluginShareListResponse__AbsolutePathBuf; readonly type: "local" } + | { + readonly path?: string | null; + readonly refName?: string | null; + readonly sha?: string | null; + readonly type: "git"; + readonly url: string; + } + | { + readonly package: string; + readonly registry?: string | null; + readonly type: "npm"; + readonly version?: string | null; + } + | { readonly type: "remote" }; +export const V2PluginShareListResponse__PluginSource = Schema.Union( + [ + Schema.Struct({ + path: V2PluginShareListResponse__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({ + package: Schema.String, + registry: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional HTTPS registry URL. Authentication stays in the user's npm config.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("npm").annotate({ title: "NpmPluginSourceType" }), + version: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Optional npm version or version range." }), + Schema.Null, + ]), + ), + }).annotate({ title: "NpmPluginSource" }), + 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" }, +).annotate({ identifier: "V2PluginShareListResponse__PluginSource" }); + +export type V2PluginShareListResponse__PluginSharePrincipal = { + readonly name: string; + readonly principalId: string; + readonly principalType: V2PluginShareListResponse__PluginSharePrincipalType; + readonly role: V2PluginShareListResponse__PluginSharePrincipalRole; +}; +export const V2PluginShareListResponse__PluginSharePrincipal = Schema.Struct({ + name: Schema.String, + principalId: Schema.String, + principalType: V2PluginShareListResponse__PluginSharePrincipalType, + role: V2PluginShareListResponse__PluginSharePrincipalRole, +}).annotate({ identifier: "V2PluginShareListResponse__PluginSharePrincipal" }); + +export type V2PluginShareSaveParams__PluginShareTarget = { + readonly principalId: string; + readonly principalType: V2PluginShareSaveParams__PluginSharePrincipalType; + readonly role: V2PluginShareSaveParams__PluginShareTargetRole; +}; +export const V2PluginShareSaveParams__PluginShareTarget = Schema.Struct({ + principalId: Schema.String, + principalType: V2PluginShareSaveParams__PluginSharePrincipalType, + role: V2PluginShareSaveParams__PluginShareTargetRole, +}).annotate({ identifier: "V2PluginShareSaveParams__PluginShareTarget" }); + +export type V2PluginShareUpdateTargetsParams__PluginShareTarget = { + readonly principalId: string; + readonly principalType: V2PluginShareUpdateTargetsParams__PluginSharePrincipalType; + readonly role: V2PluginShareUpdateTargetsParams__PluginShareTargetRole; +}; +export const V2PluginShareUpdateTargetsParams__PluginShareTarget = Schema.Struct({ + principalId: Schema.String, + principalType: V2PluginShareUpdateTargetsParams__PluginSharePrincipalType, + role: V2PluginShareUpdateTargetsParams__PluginShareTargetRole, +}).annotate({ identifier: "V2PluginShareUpdateTargetsParams__PluginShareTarget" }); + +export type V2PluginShareUpdateTargetsResponse__PluginSharePrincipal = { + readonly name: string; + readonly principalId: string; + readonly principalType: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType; + readonly role: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalRole; +}; +export const V2PluginShareUpdateTargetsResponse__PluginSharePrincipal = Schema.Struct({ + name: Schema.String, + principalId: Schema.String, + principalType: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalType, + role: V2PluginShareUpdateTargetsResponse__PluginSharePrincipalRole, +}).annotate({ identifier: "V2PluginShareUpdateTargetsResponse__PluginSharePrincipal" }); + +export type V2RawResponseItemCompletedNotification__ContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2RawResponseItemCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2RawResponseItemCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { 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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2RawResponseItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2RawResponseItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + file_id: Schema.String, + }).annotate({ title: "FileIdContentItem" }), + ]).annotate({ title: "InputImageContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ title: "InputAudioContentItemType" }), + }).annotate({ title: "InputAudioContentItem" }), + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), + }).annotate({ title: "OutputTextContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2RawResponseItemCompletedNotification__ContentItem" }); + +export type V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2RawResponseItemCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2RawResponseItemCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2RawResponseItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2RawResponseItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem", +}); + +export type V2RawResponseItemCompletedNotification__ConfigurationReasoning = { + readonly effort: V2RawResponseItemCompletedNotification__ReasoningEffort; +}; +export const V2RawResponseItemCompletedNotification__ConfigurationReasoning = Schema.Struct({ + effort: V2RawResponseItemCompletedNotification__ReasoningEffort, +}).annotate({ + description: "Reasoning settings interpreted by the backend for the routed model.", + identifier: "V2RawResponseItemCompletedNotification__ConfigurationReasoning", +}); + +export type V2ReviewStartResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ReviewStartResponse__NonSteerableTurnKind; + }; + }; +export const V2ReviewStartResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ReviewStartResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ReviewStartResponse__CodexErrorInfo", +}); + +export type V2ReviewStartResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ReviewStartResponse__MisalignmentSteer | null; +}; +export const V2ReviewStartResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ReviewStartResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ReviewStartResponse__MisalignmentErrorDetails" }); + +export type V2ReviewStartResponse__TextElement = { + readonly byteRange: V2ReviewStartResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ReviewStartResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ReviewStartResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ReviewStartResponse__TextElement" }); + +export type V2ReviewStartResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ReviewStartResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ReviewStartResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ReviewStartResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ReviewStartResponse__FunctionCallOutputContentItem", +}); + +export type V2ReviewStartResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ReviewStartResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ReviewStartResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ReviewStartResponse__MemoryCitation" }); + +export type V2ReviewStartResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ReviewStartResponse__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ReviewStartResponse__CommandAction" }); + +export type V2ReviewStartResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ReviewStartResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ReviewStartResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ReviewStartResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ReviewStartResponse__FileUpdateChange" }); + +export type V2ReviewStartResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ReviewStartResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ReviewStartResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ReviewStartResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ReviewStartResponse__McpAppUi", +}); + +export type V2ReviewStartResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ReviewStartResponse__CollabAgentStatus; +}; +export const V2ReviewStartResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ReviewStartResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ReviewStartResponse__CollabAgentState" }); + +export type V2SkillsListResponse__SkillDependencies = { + readonly tools: ReadonlyArray; +}; +export const V2SkillsListResponse__SkillDependencies = Schema.Struct({ + tools: Schema.Array(V2SkillsListResponse__SkillToolDependency), +}).annotate({ identifier: "V2SkillsListResponse__SkillDependencies" }); + +export type V2SkillsListResponse__SkillInterface = { + readonly brandColor?: string | null; + readonly defaultPrompt?: string | null; + readonly displayName?: string | null; + readonly iconLarge?: V2SkillsListResponse__AbsolutePathBuf | null; + readonly iconLargeUrl?: string | null; + readonly iconSmall?: V2SkillsListResponse__AbsolutePathBuf | null; + readonly iconSmallUrl?: 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([V2SkillsListResponse__AbsolutePathBuf, Schema.Null])), + iconLargeUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote large icon URL from the plugin catalog." }), + Schema.Null, + ]), + ), + iconSmall: Schema.optionalKey(Schema.Union([V2SkillsListResponse__AbsolutePathBuf, Schema.Null])), + iconSmallUrl: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Remote small icon URL from the plugin catalog." }), + Schema.Null, + ]), + ), + shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2SkillsListResponse__SkillInterface" }); + +export type V2ThreadForkResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadForkResponse__LegacyAppPathString; + 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: V2ThreadForkResponse__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadForkResponse__CommandAction" }); + +export type V2ThreadForkResponse__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { readonly networkAccess?: boolean; readonly type: "readOnly" } + | { + readonly networkAccess?: V2ThreadForkResponse__NetworkAccess; + readonly type: "externalSandbox"; + } + | { + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const V2ThreadForkResponse__SandboxPolicy = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("dangerFullAccess").annotate({ + title: "DangerFullAccessSandboxPolicyType", + }), + }).annotate({ title: "DangerFullAccessSandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadForkResponse__NetworkAccess, + ).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 })), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(V2ThreadForkResponse__AbsolutePathBuf).annotate({ default: [] }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadForkResponse__SandboxPolicy" }); + +export type V2ThreadForkResponse__ThreadSection = { + readonly appearance?: V2ThreadForkResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadForkResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadForkResponse__ThreadSection", +}); + +export type V2ThreadForkResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadForkResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadForkResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadForkResponse__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([V2ThreadForkResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadForkResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadForkResponse__SubAgentSource" }); + +export type V2ThreadForkResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadForkResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadForkResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadForkResponse__ThreadStatus" }); + +export type V2ThreadForkResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadForkResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadForkResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadForkResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadForkResponse__CodexErrorInfo", +}); + +export type V2ThreadForkResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadForkResponse__MisalignmentSteer | null; +}; +export const V2ThreadForkResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadForkResponse__MisalignmentErrorDetails" }); + +export type V2ThreadForkResponse__TextElement = { + readonly byteRange: V2ThreadForkResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadForkResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadForkResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadForkResponse__TextElement" }); + +export type V2ThreadForkResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadForkResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadForkResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadForkResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadForkResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadForkResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadForkResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadForkResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadForkResponse__MemoryCitation" }); + +export type V2ThreadForkResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadForkResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadForkResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadForkResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadForkResponse__FileUpdateChange" }); + +export type V2ThreadForkResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadForkResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadForkResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadForkResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadForkResponse__McpAppUi", +}); + +export type V2ThreadForkResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadForkResponse__CollabAgentStatus; +}; +export const V2ThreadForkResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadForkResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadForkResponse__CollabAgentState" }); + +export type V2ThreadGoalGetResponse__ThreadGoal = { + readonly createdAt: number; + readonly objective: string; + readonly status: V2ThreadGoalGetResponse__ThreadGoalStatus; + readonly threadId: string; + readonly timeUsedSeconds: number; + readonly tokenBudget?: number | null; + readonly tokensUsed: number; + readonly updatedAt: number; +}; +export const V2ThreadGoalGetResponse__ThreadGoal = Schema.Struct({ + createdAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + objective: Schema.String, + status: V2ThreadGoalGetResponse__ThreadGoalStatus, + threadId: Schema.String, + timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + tokenBudget: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + tokensUsed: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + updatedAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +}).annotate({ identifier: "V2ThreadGoalGetResponse__ThreadGoal" }); + +export type V2ThreadGoalSetResponse__ThreadGoal = { + readonly createdAt: number; + readonly objective: string; + readonly status: V2ThreadGoalSetResponse__ThreadGoalStatus; + readonly threadId: string; + readonly timeUsedSeconds: number; + readonly tokenBudget?: number | null; + readonly tokensUsed: number; + readonly updatedAt: number; +}; +export const V2ThreadGoalSetResponse__ThreadGoal = Schema.Struct({ + createdAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + objective: Schema.String, + status: V2ThreadGoalSetResponse__ThreadGoalStatus, + threadId: Schema.String, + timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + tokenBudget: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + tokensUsed: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + updatedAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +}).annotate({ identifier: "V2ThreadGoalSetResponse__ThreadGoal" }); + +export type V2ThreadGoalUpdatedNotification__ThreadGoal = { + readonly createdAt: number; + readonly objective: string; + readonly status: V2ThreadGoalUpdatedNotification__ThreadGoalStatus; + readonly threadId: string; + readonly timeUsedSeconds: number; + readonly tokenBudget?: number | null; + readonly tokensUsed: number; + readonly updatedAt: number; +}; +export const V2ThreadGoalUpdatedNotification__ThreadGoal = Schema.Struct({ + createdAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + objective: Schema.String, + status: V2ThreadGoalUpdatedNotification__ThreadGoalStatus, + threadId: Schema.String, + timeUsedSeconds: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + tokenBudget: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + tokensUsed: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + updatedAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), +}).annotate({ identifier: "V2ThreadGoalUpdatedNotification__ThreadGoal" }); + +export type V2ThreadItemsListResponse__TextElement = { + readonly byteRange: V2ThreadItemsListResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadItemsListResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadItemsListResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadItemsListResponse__TextElement" }); + +export type V2ThreadItemsListResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadItemsListResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadItemsListResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadItemsListResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadItemsListResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadItemsListResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadItemsListResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadItemsListResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadItemsListResponse__MemoryCitation" }); + +export type V2ThreadItemsListResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadItemsListResponse__LegacyAppPathString; + 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 V2ThreadItemsListResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadItemsListResponse__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadItemsListResponse__CommandAction" }); + +export type V2ThreadItemsListResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadItemsListResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadItemsListResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadItemsListResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadItemsListResponse__FileUpdateChange" }); + +export type V2ThreadItemsListResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadItemsListResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadItemsListResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadItemsListResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadItemsListResponse__McpAppUi", +}); + +export type V2ThreadItemsListResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadItemsListResponse__CollabAgentStatus; +}; +export const V2ThreadItemsListResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadItemsListResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadItemsListResponse__CollabAgentState" }); + +export type V2ThreadListResponse__ThreadSection = { + readonly appearance?: V2ThreadListResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadListResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadListResponse__ThreadSection", +}); + +export type V2ThreadListResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadListResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadListResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadListResponse__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([V2ThreadListResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadListResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadListResponse__SubAgentSource" }); + +export type V2ThreadListResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadListResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadListResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadListResponse__ThreadStatus" }); + +export type V2ThreadListResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadListResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadListResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadListResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadListResponse__CodexErrorInfo", +}); + +export type V2ThreadListResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadListResponse__MisalignmentSteer | null; +}; +export const V2ThreadListResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadListResponse__MisalignmentErrorDetails" }); + +export type V2ThreadListResponse__TextElement = { + readonly byteRange: V2ThreadListResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadListResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadListResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadListResponse__TextElement" }); + +export type V2ThreadListResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadListResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadListResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadListResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadListResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadListResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadListResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadListResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadListResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadListResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadListResponse__MemoryCitation" }); + +export type V2ThreadListResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadListResponse__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadListResponse__CommandAction" }); + +export type V2ThreadListResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadListResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadListResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadListResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadListResponse__FileUpdateChange" }); + +export type V2ThreadListResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadListResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadListResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadListResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadListResponse__McpAppUi", +}); + +export type V2ThreadListResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadListResponse__CollabAgentStatus; +}; +export const V2ThreadListResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadListResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadListResponse__CollabAgentState" }); + +export type V2ThreadMetadataUpdateResponse__ThreadSection = { + readonly appearance?: V2ThreadMetadataUpdateResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadMetadataUpdateResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadMetadataUpdateResponse__ThreadSection", +}); + +export type V2ThreadMetadataUpdateResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadMetadataUpdateResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadMetadataUpdateResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadMetadataUpdateResponse__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([V2ThreadMetadataUpdateResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadMetadataUpdateResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__SubAgentSource" }); + +export type V2ThreadMetadataUpdateResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadMetadataUpdateResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadMetadataUpdateResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__ThreadStatus" }); + +export type V2ThreadMetadataUpdateResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadMetadataUpdateResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadMetadataUpdateResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadMetadataUpdateResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadMetadataUpdateResponse__CodexErrorInfo", +}); + +export type V2ThreadMetadataUpdateResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadMetadataUpdateResponse__MisalignmentSteer | null; +}; +export const V2ThreadMetadataUpdateResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__MisalignmentErrorDetails" }); + +export type V2ThreadMetadataUpdateResponse__TextElement = { + readonly byteRange: V2ThreadMetadataUpdateResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadMetadataUpdateResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__TextElement" }); + +export type V2ThreadMetadataUpdateResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadMetadataUpdateResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadMetadataUpdateResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadMetadataUpdateResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadMetadataUpdateResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadMetadataUpdateResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadMetadataUpdateResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadMetadataUpdateResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__MemoryCitation" }); + +export type V2ThreadMetadataUpdateResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadMetadataUpdateResponse__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__CommandAction" }); + +export type V2ThreadMetadataUpdateResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadMetadataUpdateResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadMetadataUpdateResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadMetadataUpdateResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__FileUpdateChange" }); + +export type V2ThreadMetadataUpdateResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadMetadataUpdateResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadMetadataUpdateResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadMetadataUpdateResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadMetadataUpdateResponse__McpAppUi", +}); + +export type V2ThreadMetadataUpdateResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadMetadataUpdateResponse__CollabAgentStatus; +}; +export const V2ThreadMetadataUpdateResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadMetadataUpdateResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__CollabAgentState" }); + +export type V2ThreadReadResponse__ThreadSection = { + readonly appearance?: V2ThreadReadResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadReadResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadReadResponse__ThreadSection", +}); + +export type V2ThreadReadResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadReadResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadReadResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadReadResponse__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([V2ThreadReadResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadReadResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadReadResponse__SubAgentSource" }); + +export type V2ThreadReadResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadReadResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadReadResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadReadResponse__ThreadStatus" }); + +export type V2ThreadReadResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadReadResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadReadResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadReadResponse__CodexErrorInfo", +}); + +export type V2ThreadReadResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadReadResponse__MisalignmentSteer | null; +}; +export const V2ThreadReadResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadReadResponse__MisalignmentErrorDetails" }); + +export type V2ThreadReadResponse__TextElement = { + readonly byteRange: V2ThreadReadResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadReadResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadReadResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadReadResponse__TextElement" }); + +export type V2ThreadReadResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadReadResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadReadResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadReadResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadReadResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadReadResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadReadResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadReadResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadReadResponse__MemoryCitation" }); + +export type V2ThreadReadResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadReadResponse__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadReadResponse__CommandAction" }); + +export type V2ThreadReadResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadReadResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadReadResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadReadResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadReadResponse__FileUpdateChange" }); + +export type V2ThreadReadResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadReadResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadReadResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadReadResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadReadResponse__McpAppUi", +}); + +export type V2ThreadReadResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadReadResponse__CollabAgentStatus; +}; +export const V2ThreadReadResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadReadResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadReadResponse__CollabAgentState" }); + +export type V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeItem = + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly type: "realtimeSessionStarted"; + } + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly role: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeTranscriptRole; + readonly text: string; + readonly type: "transcriptSegment"; + } + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly item_id: string; + readonly presentation: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeBemItemPresentation; + readonly turn_id: string; + readonly type: "bemItemPromoted"; + } + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly outcome: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeSessionOutcome; + readonly type: "realtimeSessionClosed"; + }; +export const V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeItem = Schema.Union( + [ + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + type: Schema.Literal("realtimeSessionStarted").annotate({ + title: "RealtimeSessionStartedThreadRealtimeItemType", + }), + }).annotate({ title: "RealtimeSessionStartedThreadRealtimeItem" }), + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + role: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeTranscriptRole, + text: Schema.String, + type: Schema.Literal("transcriptSegment").annotate({ + title: "TranscriptSegmentThreadRealtimeItemType", + }), + }).annotate({ title: "TranscriptSegmentThreadRealtimeItem" }), + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + item_id: Schema.String, + presentation: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeBemItemPresentation, + turn_id: Schema.String, + type: Schema.Literal("bemItemPromoted").annotate({ + title: "BemItemPromotedThreadRealtimeItemType", + }), + }).annotate({ title: "BemItemPromotedThreadRealtimeItem" }), + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + outcome: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeSessionOutcome, + type: Schema.Literal("realtimeSessionClosed").annotate({ + title: "RealtimeSessionClosedThreadRealtimeItemType", + }), + }).annotate({ title: "RealtimeSessionClosedThreadRealtimeItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: "EXPERIMENTAL - a thread-scoped realtime item in the canonical timeline.", + identifier: "V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeItem", +}); + +export type V2ThreadRealtimeItemStartedNotification__ThreadRealtimeItem = + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly type: "realtimeSessionStarted"; + } + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly role: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeTranscriptRole; + readonly text: string; + readonly type: "transcriptSegment"; + } + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly item_id: string; + readonly presentation: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeBemItemPresentation; + readonly turn_id: string; + readonly type: "bemItemPromoted"; + } + | { + readonly id: string; + readonly realtimeSessionId: string; + readonly outcome: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeSessionOutcome; + readonly type: "realtimeSessionClosed"; + }; +export const V2ThreadRealtimeItemStartedNotification__ThreadRealtimeItem = Schema.Union( + [ + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + type: Schema.Literal("realtimeSessionStarted").annotate({ + title: "RealtimeSessionStartedThreadRealtimeItemType", + }), + }).annotate({ title: "RealtimeSessionStartedThreadRealtimeItem" }), + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + role: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeTranscriptRole, + text: Schema.String, + type: Schema.Literal("transcriptSegment").annotate({ + title: "TranscriptSegmentThreadRealtimeItemType", + }), + }).annotate({ title: "TranscriptSegmentThreadRealtimeItem" }), + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + item_id: Schema.String, + presentation: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeBemItemPresentation, + turn_id: Schema.String, + type: Schema.Literal("bemItemPromoted").annotate({ + title: "BemItemPromotedThreadRealtimeItemType", + }), + }).annotate({ title: "BemItemPromotedThreadRealtimeItem" }), + Schema.Struct({ + id: Schema.String, + realtimeSessionId: Schema.String, + outcome: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeSessionOutcome, + type: Schema.Literal("realtimeSessionClosed").annotate({ + title: "RealtimeSessionClosedThreadRealtimeItemType", + }), + }).annotate({ title: "RealtimeSessionClosedThreadRealtimeItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: "EXPERIMENTAL - a thread-scoped realtime item in the canonical timeline.", + identifier: "V2ThreadRealtimeItemStartedNotification__ThreadRealtimeItem", +}); + +export type V2ThreadResumeParams__ConfigurationReasoning = { + readonly effort: V2ThreadResumeParams__ReasoningEffort; +}; +export const V2ThreadResumeParams__ConfigurationReasoning = Schema.Struct({ + effort: V2ThreadResumeParams__ReasoningEffort, +}).annotate({ + description: "Reasoning settings interpreted by the backend for the routed model.", + identifier: "V2ThreadResumeParams__ConfigurationReasoning", +}); + +export type V2ThreadResumeParams__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadResumeParams__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadResumeParams__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadResumeParams__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadResumeParams__FunctionCallOutputContentItem", +}); + +export type V2ThreadResumeParams__ContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadResumeParams__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadResumeParams__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { 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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ title: "InputImageContentItemType" }), + file_id: Schema.String, + }).annotate({ title: "FileIdContentItem" }), + ]).annotate({ title: "InputImageContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ title: "InputAudioContentItemType" }), + }).annotate({ title: "InputAudioContentItem" }), + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), + }).annotate({ title: "OutputTextContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeParams__ContentItem" }); + +export type V2ThreadResumeResponse__Settings = { + readonly developer_instructions?: string | null; + readonly model: string; + readonly reasoning_effort?: V2ThreadResumeResponse__ReasoningEffort | null; +}; +export const V2ThreadResumeResponse__Settings = Schema.Struct({ + developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + model: Schema.String, + reasoning_effort: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ReasoningEffort, Schema.Null]), + ), +}).annotate({ + description: "Settings for a collaboration mode.", + identifier: "V2ThreadResumeResponse__Settings", +}); + +export type V2ThreadResumeResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadResumeResponse__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadResumeResponse__CommandAction" }); + +export type V2ThreadResumeResponse__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { readonly networkAccess?: boolean; readonly type: "readOnly" } + | { + readonly networkAccess?: V2ThreadResumeResponse__NetworkAccess; + readonly type: "externalSandbox"; + } + | { + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const V2ThreadResumeResponse__SandboxPolicy = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("dangerFullAccess").annotate({ + title: "DangerFullAccessSandboxPolicyType", + }), + }).annotate({ title: "DangerFullAccessSandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__NetworkAccess, + ).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 })), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(V2ThreadResumeResponse__AbsolutePathBuf).annotate({ default: [] }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeResponse__SandboxPolicy" }); + +export type V2ThreadResumeResponse__ThreadSection = { + readonly appearance?: V2ThreadResumeResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadResumeResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadResumeResponse__ThreadSection", +}); + +export type V2ThreadResumeResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadResumeResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadResumeResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadResumeResponse__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([V2ThreadResumeResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadResumeResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeResponse__SubAgentSource" }); + +export type V2ThreadResumeResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadResumeResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadResumeResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadResumeResponse__ThreadStatus" }); + +export type V2ThreadResumeResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadResumeResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadResumeResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadResumeResponse__CodexErrorInfo", +}); + +export type V2ThreadResumeResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadResumeResponse__MisalignmentSteer | null; +}; +export const V2ThreadResumeResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadResumeResponse__MisalignmentErrorDetails" }); + +export type V2ThreadResumeResponse__TextElement = { + readonly byteRange: V2ThreadResumeResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadResumeResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadResumeResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadResumeResponse__TextElement" }); + +export type V2ThreadResumeResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadResumeResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadResumeResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadResumeResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadResumeResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadResumeResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadResumeResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadResumeResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadResumeResponse__MemoryCitation" }); + +export type V2ThreadResumeResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadResumeResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadResumeResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadResumeResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadResumeResponse__FileUpdateChange" }); + +export type V2ThreadResumeResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadResumeResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadResumeResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadResumeResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadResumeResponse__McpAppUi", +}); + +export type V2ThreadResumeResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadResumeResponse__CollabAgentStatus; +}; +export const V2ThreadResumeResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadResumeResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadResumeResponse__CollabAgentState" }); + +export type V2ThreadRevertResponse__ThreadSection = { + readonly appearance?: V2ThreadRevertResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadRevertResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadRevertResponse__ThreadSection", +}); + +export type V2ThreadRevertResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadRevertResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadRevertResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadRevertResponse__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([V2ThreadRevertResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadRevertResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadRevertResponse__SubAgentSource" }); + +export type V2ThreadRevertResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadRevertResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadRevertResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadRevertResponse__ThreadStatus" }); + +export type V2ThreadRevertResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadRevertResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadRevertResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadRevertResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadRevertResponse__CodexErrorInfo", +}); + +export type V2ThreadRevertResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadRevertResponse__MisalignmentSteer | null; +}; +export const V2ThreadRevertResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadRevertResponse__MisalignmentErrorDetails" }); + +export type V2ThreadRevertResponse__TextElement = { + readonly byteRange: V2ThreadRevertResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadRevertResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadRevertResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadRevertResponse__TextElement" }); + +export type V2ThreadRevertResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadRevertResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadRevertResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadRevertResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadRevertResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadRevertResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadRevertResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadRevertResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadRevertResponse__MemoryCitation" }); + +export type V2ThreadRevertResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadRevertResponse__LegacyAppPathString; + 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 V2ThreadRevertResponse__CommandAction = Schema.Union( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadRevertResponse__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadRevertResponse__CommandAction" }); + +export type V2ThreadRevertResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadRevertResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadRevertResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadRevertResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadRevertResponse__FileUpdateChange" }); + +export type V2ThreadRevertResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadRevertResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadRevertResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadRevertResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadRevertResponse__McpAppUi", +}); + +export type V2ThreadRevertResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadRevertResponse__CollabAgentStatus; +}; +export const V2ThreadRevertResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadRevertResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadRevertResponse__CollabAgentState" }); + +export type V2ThreadSectionCreateResponse__ThreadSection = { + readonly appearance?: V2ThreadSectionCreateResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadSectionCreateResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadSectionCreateResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadSectionCreateResponse__ThreadSection", +}); + +export type V2ThreadSectionListResponse__ThreadSection = { + readonly appearance?: V2ThreadSectionListResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadSectionListResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadSectionListResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadSectionListResponse__ThreadSection", +}); + +export type V2ThreadSectionUpdateResponse__ThreadSection = { + readonly appearance?: V2ThreadSectionUpdateResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadSectionUpdateResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadSectionUpdateResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadSectionUpdateResponse__ThreadSection", +}); + +export type V2ThreadSettingsUpdatedNotification__Settings = { + readonly developer_instructions?: string | null; + readonly model: string; + readonly reasoning_effort?: V2ThreadSettingsUpdatedNotification__ReasoningEffort | null; +}; +export const V2ThreadSettingsUpdatedNotification__Settings = Schema.Struct({ + developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + model: Schema.String, + reasoning_effort: Schema.optionalKey( + Schema.Union([V2ThreadSettingsUpdatedNotification__ReasoningEffort, Schema.Null]), + ), +}).annotate({ + description: "Settings for a collaboration mode.", + identifier: "V2ThreadSettingsUpdatedNotification__Settings", +}); + +export type V2ThreadSettingsUpdatedNotification__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { readonly networkAccess?: boolean; readonly type: "readOnly" } + | { + readonly networkAccess?: V2ThreadSettingsUpdatedNotification__NetworkAccess; + readonly type: "externalSandbox"; + } + | { + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const V2ThreadSettingsUpdatedNotification__SandboxPolicy = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("dangerFullAccess").annotate({ + title: "DangerFullAccessSandboxPolicyType", + }), + }).annotate({ title: "DangerFullAccessSandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadSettingsUpdatedNotification__NetworkAccess, + ).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 })), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(V2ThreadSettingsUpdatedNotification__AbsolutePathBuf).annotate({ + default: [], + }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadSettingsUpdatedNotification__SandboxPolicy" }); + +export type V2ThreadStartedNotification__ThreadSection = { + readonly appearance?: V2ThreadStartedNotification__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadStartedNotification__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadStartedNotification__ThreadSection", +}); + +export type V2ThreadStartedNotification__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadStartedNotification__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadStartedNotification__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadStartedNotification__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([V2ThreadStartedNotification__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadStartedNotification__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartedNotification__SubAgentSource" }); + +export type V2ThreadStartedNotification__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadStartedNotification__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadStartedNotification__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartedNotification__ThreadStatus" }); + +export type V2ThreadStartedNotification__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadStartedNotification__NonSteerableTurnKind; + }; + }; +export const V2ThreadStartedNotification__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadStartedNotification__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadStartedNotification__CodexErrorInfo", +}); + +export type V2ThreadStartedNotification__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadStartedNotification__MisalignmentSteer | null; +}; +export const V2ThreadStartedNotification__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadStartedNotification__MisalignmentErrorDetails" }); + +export type V2ThreadStartedNotification__TextElement = { + readonly byteRange: V2ThreadStartedNotification__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadStartedNotification__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadStartedNotification__TextElement" }); + +export type V2ThreadStartedNotification__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadStartedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadStartedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadStartedNotification__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadStartedNotification__FunctionCallOutputContentItem", +}); + +export type V2ThreadStartedNotification__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadStartedNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadStartedNotification__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadStartedNotification__MemoryCitation" }); + +export type V2ThreadStartedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadStartedNotification__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadStartedNotification__CommandAction" }); + +export type V2ThreadStartedNotification__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadStartedNotification__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadStartedNotification__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadStartedNotification__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadStartedNotification__FileUpdateChange" }); + +export type V2ThreadStartedNotification__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadStartedNotification__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadStartedNotification__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadStartedNotification__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadStartedNotification__McpAppUi", +}); + +export type V2ThreadStartedNotification__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadStartedNotification__CollabAgentStatus; +}; +export const V2ThreadStartedNotification__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadStartedNotification__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadStartedNotification__CollabAgentState" }); + +export type V2ThreadStartResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadStartResponse__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadStartResponse__CommandAction" }); + +export type V2ThreadStartResponse__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { readonly networkAccess?: boolean; readonly type: "readOnly" } + | { + readonly networkAccess?: V2ThreadStartResponse__NetworkAccess; + readonly type: "externalSandbox"; + } + | { + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + readonly type: "workspaceWrite"; + readonly writableRoots?: ReadonlyArray; + }; +export const V2ThreadStartResponse__SandboxPolicy = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("dangerFullAccess").annotate({ + title: "DangerFullAccessSandboxPolicyType", + }), + }).annotate({ title: "DangerFullAccessSandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__NetworkAccess, + ).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 })), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(V2ThreadStartResponse__AbsolutePathBuf).annotate({ default: [] }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartResponse__SandboxPolicy" }); + +export type V2ThreadStartResponse__ThreadSection = { + readonly appearance?: V2ThreadStartResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadStartResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadStartResponse__ThreadSection", +}); + +export type V2ThreadStartResponse__SubAgentSource = + | "review" + | "compact" + | "memory_consolidation" + | { + readonly thread_spawn: { + readonly agent_nickname?: string | null; + readonly agent_path?: V2ThreadStartResponse__AgentPath | null; + readonly agent_role?: string | null; + readonly depth: number; + readonly parent_thread_id: V2ThreadStartResponse__ThreadId; + }; + } + | { readonly other: string }; +export const V2ThreadStartResponse__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([V2ThreadStartResponse__AgentPath, Schema.Null]), + ), + agent_role: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + depth: Schema.Number.annotate({ format: "int32" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadStartResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartResponse__SubAgentSource" }); + +export type V2ThreadStartResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadStartResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadStartResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStartResponse__ThreadStatus" }); + +export type V2ThreadStartResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadStartResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadStartResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadStartResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadStartResponse__CodexErrorInfo", +}); + +export type V2ThreadStartResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadStartResponse__MisalignmentSteer | null; +}; +export const V2ThreadStartResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadStartResponse__MisalignmentErrorDetails" }); + +export type V2ThreadStartResponse__TextElement = { + readonly byteRange: V2ThreadStartResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadStartResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadStartResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadStartResponse__TextElement" }); + +export type V2ThreadStartResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadStartResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadStartResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadStartResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadStartResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadStartResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadStartResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadStartResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadStartResponse__MemoryCitation" }); + +export type V2ThreadStartResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadStartResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadStartResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadStartResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadStartResponse__FileUpdateChange" }); + +export type V2ThreadStartResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadStartResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadStartResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadStartResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadStartResponse__McpAppUi", +}); + +export type V2ThreadStartResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadStartResponse__CollabAgentStatus; +}; +export const V2ThreadStartResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadStartResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadStartResponse__CollabAgentState" }); + +export type V2ThreadStatusChangedNotification__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadStatusChangedNotification__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadStatusChangedNotification__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadStatusChangedNotification__ThreadStatus" }); + +export type V2ThreadTokenUsageUpdatedNotification__ThreadTokenUsage = { + readonly last: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown; + readonly modelContextWindow?: number | null; + readonly total: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown; +}; +export const V2ThreadTokenUsageUpdatedNotification__ThreadTokenUsage = Schema.Struct({ + last: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown, + modelContextWindow: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + total: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown, +}).annotate({ identifier: "V2ThreadTokenUsageUpdatedNotification__ThreadTokenUsage" }); + +export type V2ThreadTurnsListResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadTurnsListResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadTurnsListResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadTurnsListResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadTurnsListResponse__CodexErrorInfo", +}); + +export type V2ThreadTurnsListResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadTurnsListResponse__MisalignmentSteer | null; +}; +export const V2ThreadTurnsListResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadTurnsListResponse__MisalignmentErrorDetails" }); + +export type V2ThreadTurnsListResponse__TextElement = { + readonly byteRange: V2ThreadTurnsListResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadTurnsListResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadTurnsListResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadTurnsListResponse__TextElement" }); + +export type V2ThreadTurnsListResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadTurnsListResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadTurnsListResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadTurnsListResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadTurnsListResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadTurnsListResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadTurnsListResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadTurnsListResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadTurnsListResponse__MemoryCitation" }); + +export type V2ThreadTurnsListResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadTurnsListResponse__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadTurnsListResponse__CommandAction" }); + +export type V2ThreadTurnsListResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadTurnsListResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadTurnsListResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadTurnsListResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadTurnsListResponse__FileUpdateChange" }); + +export type V2ThreadTurnsListResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadTurnsListResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadTurnsListResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadTurnsListResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadTurnsListResponse__McpAppUi", +}); + +export type V2ThreadTurnsListResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadTurnsListResponse__CollabAgentStatus; +}; +export const V2ThreadTurnsListResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadTurnsListResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadTurnsListResponse__CollabAgentState" }); + +export type V2ThreadUnarchiveResponse__ThreadSection = { + readonly appearance?: V2ThreadUnarchiveResponse__ThreadSectionAppearance | null; + readonly id: string; + readonly name: string; +}; +export const V2ThreadUnarchiveResponse__ThreadSection = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Optional appearance synchronized across clients.", + }), + ), + id: Schema.String.annotate({ + description: "Opaque UUIDv7 identity that remains stable when the section is renamed.", + }), + name: Schema.String.annotate({ description: "The current user-visible section name." }), +}).annotate({ + description: "An independently persisted, user-visible thread section.", + identifier: "V2ThreadUnarchiveResponse__ThreadSection", +}); + +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().annotate({ expected: "an integer" }), + ), + parent_thread_id: V2ThreadUnarchiveResponse__ThreadId, + }), + }).annotate({ title: "ThreadSpawnSubAgentSource" }), + Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadUnarchiveResponse__SubAgentSource" }); + +export type V2ThreadUnarchiveResponse__ThreadStatus = + | { readonly type: "notLoaded" } + | { readonly type: "idle" } + | { readonly type: "systemError" } + | { + readonly activeFlags: ReadonlyArray; + readonly type: "active"; + }; +export const V2ThreadUnarchiveResponse__ThreadStatus = Schema.Union( + [ + Schema.Struct({ + type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), + }).annotate({ title: "NotLoadedThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), + }).annotate({ title: "IdleThreadStatus" }), + Schema.Struct({ + type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), + }).annotate({ title: "SystemErrorThreadStatus" }), + Schema.Struct({ + activeFlags: Schema.Array(V2ThreadUnarchiveResponse__ThreadActiveFlag), + type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), + }).annotate({ title: "ActiveThreadStatus" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadUnarchiveResponse__ThreadStatus" }); + +export type V2ThreadUnarchiveResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2ThreadUnarchiveResponse__NonSteerableTurnKind; + }; + }; +export const V2ThreadUnarchiveResponse__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2ThreadUnarchiveResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2ThreadUnarchiveResponse__CodexErrorInfo", +}); + +export type V2ThreadUnarchiveResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2ThreadUnarchiveResponse__MisalignmentSteer | null; +}; +export const V2ThreadUnarchiveResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__MisalignmentErrorDetails" }); + +export type V2ThreadUnarchiveResponse__TextElement = { + readonly byteRange: V2ThreadUnarchiveResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2ThreadUnarchiveResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2ThreadUnarchiveResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__TextElement" }); + +export type V2ThreadUnarchiveResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2ThreadUnarchiveResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2ThreadUnarchiveResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2ThreadUnarchiveResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2ThreadUnarchiveResponse__FunctionCallOutputContentItem", +}); + +export type V2ThreadUnarchiveResponse__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2ThreadUnarchiveResponse__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2ThreadUnarchiveResponse__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__MemoryCitation" }); + +export type V2ThreadUnarchiveResponse__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2ThreadUnarchiveResponse__LegacyAppPathString; + 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( + [ + Schema.Struct({ + command: Schema.String, + name: Schema.String, + path: V2ThreadUnarchiveResponse__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2ThreadUnarchiveResponse__CommandAction" }); + +export type V2ThreadUnarchiveResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2ThreadUnarchiveResponse__PatchChangeKind; + readonly path: string; +}; +export const V2ThreadUnarchiveResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2ThreadUnarchiveResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2ThreadUnarchiveResponse__FileUpdateChange" }); + +export type V2ThreadUnarchiveResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2ThreadUnarchiveResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2ThreadUnarchiveResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2ThreadUnarchiveResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2ThreadUnarchiveResponse__McpAppUi", +}); + +export type V2ThreadUnarchiveResponse__CollabAgentState = { + readonly message?: string | null; + readonly status: V2ThreadUnarchiveResponse__CollabAgentStatus; +}; +export const V2ThreadUnarchiveResponse__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2ThreadUnarchiveResponse__CollabAgentStatus, +}).annotate({ identifier: "V2ThreadUnarchiveResponse__CollabAgentState" }); + +export type V2TurnCompletedNotification__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2TurnCompletedNotification__NonSteerableTurnKind; + }; + }; +export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2TurnCompletedNotification__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2TurnCompletedNotification__CodexErrorInfo", +}); + +export type V2TurnCompletedNotification__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2TurnCompletedNotification__MisalignmentSteer | null; +}; +export const V2TurnCompletedNotification__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2TurnCompletedNotification__MisalignmentErrorDetails" }); + +export type V2TurnCompletedNotification__TextElement = { + readonly byteRange: V2TurnCompletedNotification__ByteRange; + readonly placeholder?: string | null; +}; +export const V2TurnCompletedNotification__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => + V2TurnCompletedNotification__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2TurnCompletedNotification__TextElement" }); + +export type V2TurnCompletedNotification__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2TurnCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2TurnCompletedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2TurnCompletedNotification__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2TurnCompletedNotification__FunctionCallOutputContentItem", +}); + +export type V2TurnCompletedNotification__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2TurnCompletedNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2TurnCompletedNotification__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2TurnCompletedNotification__MemoryCitation" }); + +export type V2TurnCompletedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2TurnCompletedNotification__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2TurnCompletedNotification__CommandAction" }); + +export type V2TurnCompletedNotification__FileUpdateChange = { + readonly diff: string; + readonly kind: V2TurnCompletedNotification__PatchChangeKind; + readonly path: string; +}; +export const V2TurnCompletedNotification__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2TurnCompletedNotification__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2TurnCompletedNotification__FileUpdateChange" }); + +export type V2TurnCompletedNotification__McpAppUi = { + readonly preferredModelDisplayMode: V2TurnCompletedNotification__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2TurnCompletedNotification__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2TurnCompletedNotification__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2TurnCompletedNotification__McpAppUi", +}); + +export type V2TurnCompletedNotification__CollabAgentState = { + readonly message?: string | null; + readonly status: V2TurnCompletedNotification__CollabAgentStatus; +}; +export const V2TurnCompletedNotification__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2TurnCompletedNotification__CollabAgentStatus, +}).annotate({ identifier: "V2TurnCompletedNotification__CollabAgentState" }); + +export type V2TurnPlanUpdatedNotification__TurnPlanStep = { + readonly status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus; + readonly step: string; +}; +export const V2TurnPlanUpdatedNotification__TurnPlanStep = Schema.Struct({ + status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus, + step: Schema.String, +}).annotate({ identifier: "V2TurnPlanUpdatedNotification__TurnPlanStep" }); + +export type V2TurnStartedNotification__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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: V2TurnStartedNotification__NonSteerableTurnKind; + }; + }; +export const V2TurnStartedNotification__CodexErrorInfo = Schema.Union( + [ + Schema.Literals([ + "contextWindowExceeded", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2TurnStartedNotification__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2TurnStartedNotification__CodexErrorInfo", +}); + +export type V2TurnStartedNotification__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2TurnStartedNotification__MisalignmentSteer | null; +}; +export const V2TurnStartedNotification__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2TurnStartedNotification__MisalignmentErrorDetails" }); + +export type V2TurnStartedNotification__TextElement = { + readonly byteRange: V2TurnStartedNotification__ByteRange; + readonly placeholder?: string | null; +}; +export const V2TurnStartedNotification__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2TurnStartedNotification__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2TurnStartedNotification__TextElement" }); + +export type V2TurnStartedNotification__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2TurnStartedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2TurnStartedNotification__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2TurnStartedNotification__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2TurnStartedNotification__FunctionCallOutputContentItem", +}); + +export type V2TurnStartedNotification__MemoryCitation = { + readonly entries: ReadonlyArray; + readonly threadIds: ReadonlyArray; +}; +export const V2TurnStartedNotification__MemoryCitation = Schema.Struct({ + entries: Schema.Array(V2TurnStartedNotification__MemoryCitationEntry), + threadIds: Schema.Array(Schema.String), +}).annotate({ identifier: "V2TurnStartedNotification__MemoryCitation" }); + +export type V2TurnStartedNotification__CommandAction = + | { + readonly command: string; + readonly name: string; + readonly path: V2TurnStartedNotification__LegacyAppPathString; + 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: V2TurnStartedNotification__LegacyAppPathString, + 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" }, +).annotate({ identifier: "V2TurnStartedNotification__CommandAction" }); + +export type V2TurnStartedNotification__FileUpdateChange = { + readonly diff: string; + readonly kind: V2TurnStartedNotification__PatchChangeKind; + readonly path: string; +}; +export const V2TurnStartedNotification__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2TurnStartedNotification__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2TurnStartedNotification__FileUpdateChange" }); + +export type V2TurnStartedNotification__McpAppUi = { + readonly preferredModelDisplayMode: V2TurnStartedNotification__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2TurnStartedNotification__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2TurnStartedNotification__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2TurnStartedNotification__McpAppUi", +}); + +export type V2TurnStartedNotification__CollabAgentState = { + readonly message?: string | null; + readonly status: V2TurnStartedNotification__CollabAgentStatus; +}; +export const V2TurnStartedNotification__CollabAgentState = Schema.Struct({ + message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: V2TurnStartedNotification__CollabAgentStatus, +}).annotate({ identifier: "V2TurnStartedNotification__CollabAgentState" }); + +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.", + identifier: "V2TurnStartParams__Settings", +}); + +export type V2TurnStartParams__TextElement = { + readonly byteRange: V2TurnStartParams__ByteRange; + readonly placeholder?: string | null; +}; +export const V2TurnStartParams__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2TurnStartParams__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2TurnStartParams__TextElement" }); + +export type V2TurnStartParams__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2TurnStartParams__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2TurnStartParams__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2TurnStartParams__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartParams__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartParams__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2TurnStartParams__FunctionCallOutputContentItem", +}); + +export type V2TurnStartParams__SandboxPolicy = + | { readonly type: "dangerFullAccess" } + | { readonly networkAccess?: boolean; readonly type: "readOnly" } + | { readonly networkAccess?: V2TurnStartParams__NetworkAccess; readonly type: "externalSandbox" } + | { + readonly excludeSlashTmp?: boolean; + readonly excludeTmpdirEnvVar?: boolean; + readonly networkAccess?: boolean; + 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({ + networkAccess: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + type: Schema.Literal("readOnly").annotate({ title: "ReadOnlySandboxPolicyType" }), + }).annotate({ title: "ReadOnlySandboxPolicy" }), + Schema.Struct({ + networkAccess: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => V2TurnStartParams__NetworkAccess, + ).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 })), + type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), + writableRoots: Schema.optionalKey( + Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), + ), + }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2TurnStartParams__SandboxPolicy" }); + +export type V2TurnStartResponse__CodexErrorInfo = + | "contextWindowExceeded" + | "sessionBudgetExceeded" + | "usageLimitExceeded" + | "rateLimitExceeded" + | "serverOverloaded" + | "cyberPolicy" + | "misalignmentPolicyViolation" + | "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", + "sessionBudgetExceeded", + "usageLimitExceeded", + "rateLimitExceeded", + "serverOverloaded", + "cyberPolicy", + "misalignmentPolicyViolation", + "internalServerError", + "unauthorized", + "badRequest", + "threadRollbackFailed", + "sandboxError", + "other", + ]), + Schema.Struct({ + httpConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ title: "HttpConnectionFailedCodexErrorInfo" }), + Schema.Struct({ + responseStreamConnectionFailed: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamConnectionFailedCodexErrorInfo", + description: "Failed to connect to the response SSE stream.", + }), + Schema.Struct({ + responseStreamDisconnected: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseStreamDisconnectedCodexErrorInfo", + description: + "The response SSE stream disconnected in the middle of a turn before completion.", + }), + Schema.Struct({ + responseTooManyFailedAttempts: Schema.Struct({ + httpStatusCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint16" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + }), + }).annotate({ + title: "ResponseTooManyFailedAttemptsCodexErrorInfo", + description: "Reached the retry limit for responses.", + }), + Schema.Struct({ + activeTurnNotSteerable: Schema.Struct({ + turnKind: V2TurnStartResponse__NonSteerableTurnKind, + }), + }).annotate({ + title: "ActiveTurnNotSteerableCodexErrorInfo", + description: + "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: + "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.", + identifier: "V2TurnStartResponse__CodexErrorInfo", +}); + +export type V2TurnStartResponse__MisalignmentErrorDetails = { + readonly detailedExplanation?: string | null; + readonly errorType?: string | null; + readonly steer?: V2TurnStartResponse__MisalignmentSteer | null; +}; +export const V2TurnStartResponse__MisalignmentErrorDetails = Schema.Struct({ + detailedExplanation: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "A substantive localized explanation is required before offering continuation.", + }), + Schema.Null, + ]), + ), + errorType: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Open-ended classification; clients must accept categories added by Responses.", + }), + Schema.Null, + ]), + ), + steer: Schema.optionalKey( + Schema.Union([V2TurnStartResponse__MisalignmentSteer, Schema.Null]).annotate({ + description: + "Instruction to submit as the next turn's user input if continuation is confirmed.", + }), + ), +}).annotate({ identifier: "V2TurnStartResponse__MisalignmentErrorDetails" }); + +export type V2TurnStartResponse__TextElement = { + readonly byteRange: V2TurnStartResponse__ByteRange; + readonly placeholder?: string | null; +}; +export const V2TurnStartResponse__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2TurnStartResponse__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2TurnStartResponse__TextElement" }); + +export type V2TurnStartResponse__FunctionCallOutputContentItem = + | { readonly text: string; readonly type: "input_text" } + | { + readonly detail?: V2TurnStartResponse__ImageDetail | null; + readonly type: "input_image"; + readonly image_url: string; + } + | { + readonly detail?: V2TurnStartResponse__ImageDetail | null; + readonly type: "input_image"; + readonly file_id: string; + } + | { readonly audio_url: string; readonly type: "input_audio" } + | { readonly encrypted_content: string; readonly type: "encrypted_content" }; +export const V2TurnStartResponse__FunctionCallOutputContentItem = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + type: Schema.Literal("input_text").annotate({ + title: "InputTextFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + image_url: Schema.String, + }).annotate({ title: "ImageUrlFunctionCallOutputContentItem" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("input_image").annotate({ + title: "InputImageFunctionCallOutputContentItemType", + }), + file_id: Schema.String, + }).annotate({ title: "FileIdFunctionCallOutputContentItem" }), + ]).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + Schema.Struct({ + audio_url: Schema.String, + type: Schema.Literal("input_audio").annotate({ + title: "InputAudioFunctionCallOutputContentItemType", + }), + }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + type: Schema.Literal("encrypted_content").annotate({ + title: "EncryptedContentFunctionCallOutputContentItemType", + }), + }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "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.", + identifier: "V2TurnStartResponse__FunctionCallOutputContentItem", }); -export type V2ThreadListResponse__CommandAction = +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), +}).annotate({ identifier: "V2TurnStartResponse__MemoryCitation" }); + +export type V2TurnStartResponse__CommandAction = | { readonly command: string; readonly name: string; - readonly path: V2ThreadListResponse__AbsolutePathBuf; + readonly path: V2TurnStartResponse__LegacyAppPathString; readonly type: "read"; } | { readonly command: string; readonly path?: string | null; readonly type: "listFiles" } @@ -16242,12 +28713,12 @@ export type V2ThreadListResponse__CommandAction = readonly type: "search"; } | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadListResponse__CommandAction = Schema.Union( +export const V2TurnStartResponse__CommandAction = Schema.Union( [ Schema.Struct({ command: Schema.String, name: Schema.String, - path: V2ThreadListResponse__AbsolutePathBuf, + path: V2TurnStartResponse__LegacyAppPathString, type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), }).annotate({ title: "ReadCommandAction" }), Schema.Struct({ @@ -16256,171 +28727,804 @@ export const V2ThreadListResponse__CommandAction = Schema.Union( 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" }), + 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" }, +).annotate({ identifier: "V2TurnStartResponse__CommandAction" }); + +export type V2TurnStartResponse__FileUpdateChange = { + readonly diff: string; + readonly kind: V2TurnStartResponse__PatchChangeKind; + readonly path: string; +}; +export const V2TurnStartResponse__FileUpdateChange = Schema.Struct({ + diff: Schema.String, + kind: V2TurnStartResponse__PatchChangeKind, + path: Schema.String, +}).annotate({ identifier: "V2TurnStartResponse__FileUpdateChange" }); + +export type V2TurnStartResponse__McpAppUi = { + readonly preferredModelDisplayMode: V2TurnStartResponse__McpAppDisplayMode; + readonly resourceUri: string; +}; +export const V2TurnStartResponse__McpAppUi = Schema.Struct({ + preferredModelDisplayMode: V2TurnStartResponse__McpAppDisplayMode, + resourceUri: Schema.String, +}).annotate({ + description: + "UI resource and display preference for model invocations, captured from the tool descriptor.", + identifier: "V2TurnStartResponse__McpAppUi", +}); + +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, +}).annotate({ identifier: "V2TurnStartResponse__CollabAgentState" }); + +export type V2TurnSteerParams__TextElement = { + readonly byteRange: V2TurnSteerParams__ByteRange; + readonly placeholder?: string | null; +}; +export const V2TurnSteerParams__TextElement = Schema.Struct({ + byteRange: Schema.suspend( + (): Schema.Codec => V2TurnSteerParams__ByteRange, + ).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, + ]), + ), +}).annotate({ identifier: "V2TurnSteerParams__TextElement" }); + +export type ApplyPatchApprovalResponse__ReviewDecision = + | "approved" + | { + readonly approved_execpolicy_amendment: { + readonly proposed_execpolicy_amendment: ReadonlyArray; + }; + } + | "approved_for_session" + | "approved_mcp_policy_amendment" + | { + readonly network_policy_amendment: { + readonly network_policy_amendment: ApplyPatchApprovalResponse__NetworkPolicyAmendment; + }; + } + | { readonly denied: { readonly rejection: string } } + | "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.Literal("approved_mcp_policy_amendment").annotate({ + description: + "User has approved this MCP tool call and wants to amend its policy so matching future calls are automatically approved across sessions.", + }), + 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.Struct({ denied: Schema.Struct({ rejection: Schema.String }) }).annotate({ + title: "DeniedReviewDecision", + 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.", + identifier: "ApplyPatchApprovalResponse__ReviewDecision", +}); + +export type ClientRequest__PluginShareSaveParams = { + readonly discoverability?: ClientRequest__PluginShareDiscoverability | null; + readonly pluginPath: ClientRequest__AbsolutePathBuf; + readonly remotePluginId?: string | null; + readonly shareTargets?: ReadonlyArray | null; +}; +export const ClientRequest__PluginShareSaveParams = Schema.Struct({ + discoverability: Schema.optionalKey( + Schema.Union([ClientRequest__PluginShareDiscoverability, Schema.Null]), + ), + pluginPath: ClientRequest__AbsolutePathBuf, + remotePluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + shareTargets: Schema.optionalKey( + Schema.Union([Schema.Array(ClientRequest__PluginShareTarget), Schema.Null]), + ), +}).annotate({ identifier: "ClientRequest__PluginShareSaveParams" }); + +export type ClientRequest__PluginShareUpdateTargetsParams = { + readonly discoverability: ClientRequest__PluginShareUpdateDiscoverability; + readonly remotePluginId: string; + readonly shareTargets: ReadonlyArray; +}; +export const ClientRequest__PluginShareUpdateTargetsParams = Schema.Struct({ + discoverability: ClientRequest__PluginShareUpdateDiscoverability, + remotePluginId: Schema.String, + shareTargets: Schema.Array(ClientRequest__PluginShareTarget), +}).annotate({ identifier: "ClientRequest__PluginShareUpdateTargetsParams" }); + +export type ClientRequest__UserInput = + | { + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; + } + | { + readonly detail?: ClientRequest__ImageDetail | null; + readonly type: "image"; + readonly url: string; + } + | { + readonly detail?: ClientRequest__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: ClientRequest__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const ClientRequest__UserInput = Schema.Union( + [ + Schema.Struct({ + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(ClientRequest__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ClientRequest__ImageDetail, Schema.Null])), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), + Schema.Struct({ + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), + Schema.Struct({ + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, +).annotate({ identifier: "ClientRequest__UserInput" }); + +export type ClientRequest__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const ClientRequest__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(ClientRequest__FunctionCallOutputContentItem), +]).annotate({ identifier: "ClientRequest__FunctionCallOutputBody" }); + +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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 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. Cannot be combined with `permissionProfile`.", + }), + ), + 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( + Schema.Boolean.annotate({ + description: + "Stream stdout/stderr via `command/exec/outputDelta` notifications.\n\nStreamed bytes are not duplicated into the final response and require a client-supplied `processId`.", + }), + ), + timeoutMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Optional timeout in milliseconds.\n\nWhen omitted, the server default applies. Cannot be combined with `disableTimeout`.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + tty: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "Enable PTY mode.\n\nThis implies `streamStdin` and `streamStdoutStderr`.", + }), + ), +}).annotate({ + description: + "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.", + identifier: "ClientRequest__CommandExecParams", +}); + +export type ClientRequest__ExternalAgentConfigMigrationItem = { + readonly cwd?: string | null; + 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, +}).annotate({ identifier: "ClientRequest__ExternalAgentConfigMigrationItem" }); + +export type ClientRequest__ExternalAgentConfigImportHistoryRecordTypeResultParams = { + readonly failures: ReadonlyArray; + readonly itemType: ClientRequest__ExternalAgentConfigMigrationItemType; + readonly successes: ReadonlyArray; +}; +export const ClientRequest__ExternalAgentConfigImportHistoryRecordTypeResultParams = Schema.Struct({ + failures: Schema.Array(ClientRequest__ExternalAgentConfigImportItemTypeFailure), + itemType: ClientRequest__ExternalAgentConfigMigrationItemType, + successes: Schema.Array(ClientRequest__ExternalAgentConfigImportHistoryRecordSuccessParams), +}).annotate({ + identifier: "ClientRequest__ExternalAgentConfigImportHistoryRecordTypeResultParams", +}); + +export type ClientRequest__ConfigBatchWriteParams = { + readonly edits: ReadonlyArray; + readonly expectedVersion?: string | null; + readonly filePath?: string | null; + readonly reloadUserConfig?: boolean; +}; +export const ClientRequest__ConfigBatchWriteParams = Schema.Struct({ + edits: Schema.Array(ClientRequest__ConfigEdit), + expectedVersion: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + filePath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Path to the config file to write; defaults to the user's `config.toml` when omitted.", + }), + Schema.Null, + ]), + ), + reloadUserConfig: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When true, hot-reload updated runtime settings into loaded threads after writing. Session-static model, reasoning-effort, Plan-mode reasoning-effort, and service-tier defaults are not reloaded. The deprecated personality setting is also not reloaded.", + }), + ), +}).annotate({ identifier: "ClientRequest__ConfigBatchWriteParams" }); + +export type CommandExecutionRequestApprovalParams__FileSystemPath = + | { + readonly path: CommandExecutionRequestApprovalParams__LegacyAppPathString; + 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "CommandExecutionRequestApprovalParams__FileSystemPath" }); + +export type CommandExecutionRequestApprovalResponse__CommandExecutionApprovalDecision = + | "accept" + | "acceptForSession" + | { + readonly acceptWithExecpolicyAmendment: { + readonly execpolicy_amendment: ReadonlyArray; + }; + } + | { + readonly applyNetworkPolicyAmendment: { + readonly network_policy_amendment: CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment; + }; + } + | "decline" + | "cancel"; +export const CommandExecutionRequestApprovalResponse__CommandExecutionApprovalDecision = + Schema.Union( + [ + Schema.Literal("accept").annotate({ description: "User approved the command." }), + Schema.Literal("acceptForSession").annotate({ + description: + "User approved the command and future prompts in the same session-scoped approval cache should run without prompting.", + }), + Schema.Struct({ + acceptWithExecpolicyAmendment: Schema.Struct({ + execpolicy_amendment: Schema.Array(Schema.String), + }), + }).annotate({ + title: "AcceptWithExecpolicyAmendmentCommandExecutionApprovalDecision", + description: + "User approved the command, and wants to apply the proposed execpolicy amendment so future matching commands can run without prompting.", + }), + Schema.Struct({ + applyNetworkPolicyAmendment: Schema.Struct({ + network_policy_amendment: CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment, + }), + }).annotate({ + title: "ApplyNetworkPolicyAmendmentCommandExecutionApprovalDecision", + description: "User chose a persistent network policy rule (allow/deny) for this host.", + }), + Schema.Literal("decline").annotate({ + description: "User denied the command. The agent will continue the turn.", + }), + Schema.Literal("cancel").annotate({ + description: "User denied the command. The turn will also be immediately interrupted.", + }), + ], + { mode: "oneOf" }, + ).annotate({ + identifier: "CommandExecutionRequestApprovalResponse__CommandExecutionApprovalDecision", + }); + +export type ExecCommandApprovalResponse__ReviewDecision = + | "approved" + | { + readonly approved_execpolicy_amendment: { + readonly proposed_execpolicy_amendment: ReadonlyArray; + }; + } + | "approved_for_session" + | "approved_mcp_policy_amendment" + | { + readonly network_policy_amendment: { + readonly network_policy_amendment: ExecCommandApprovalResponse__NetworkPolicyAmendment; + }; + } + | { readonly denied: { readonly rejection: string } } + | "timed_out" + | "abort"; +export const ExecCommandApprovalResponse__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.Literal("approved_mcp_policy_amendment").annotate({ + description: + "User has approved this MCP tool call and wants to amend its policy so matching future calls are automatically approved across sessions.", + }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + network_policy_amendment: Schema.Struct({ + network_policy_amendment: ExecCommandApprovalResponse__NetworkPolicyAmendment, + }), + }).annotate({ + title: "NetworkPolicyAmendmentReviewDecision", + description: + "User chose to persist a network policy rule (allow/deny) for future requests to the same host.", + }), + Schema.Struct({ denied: Schema.Struct({ rejection: Schema.String }) }).annotate({ + title: "DeniedReviewDecision", + 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.", + identifier: "ExecCommandApprovalResponse__ReviewDecision", +}); -export type V2ThreadListResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadListResponse__CollabAgentStatus; +export type McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema = { + readonly default?: ReadonlyArray | null; + readonly description?: string | null; + readonly items: McpServerElicitationRequestParams__McpElicitationUntitledEnumItems; + readonly maxItems?: number | null; + readonly minItems?: number | null; + readonly title?: string | null; + readonly type: McpServerElicitationRequestParams__McpElicitationArrayType; }; -export const V2ThreadListResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadListResponse__CollabAgentStatus, +export const McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema = + Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + items: McpServerElicitationRequestParams__McpElicitationUntitledEnumItems, + maxItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + minItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: McpServerElicitationRequestParams__McpElicitationArrayType, + }).annotate({ + identifier: "McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema", + }); + +export type McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema = + | McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema + | McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema; +export const McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema = Schema.Union( + [ + McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema, + McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema, + ], +).annotate({ + identifier: "McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema", }); -export type V2ThreadListResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +export type McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema = { + readonly default?: ReadonlyArray | null; + readonly description?: string | null; + readonly items: McpServerElicitationRequestParams__McpElicitationTitledEnumItems; + readonly maxItems?: number | null; + readonly minItems?: number | null; + readonly title?: string | null; + readonly type: McpServerElicitationRequestParams__McpElicitationArrayType; }; -export const V2ThreadListResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadListResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); +export const McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema = + Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + items: McpServerElicitationRequestParams__McpElicitationTitledEnumItems, + maxItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + minItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: McpServerElicitationRequestParams__McpElicitationArrayType, + }).annotate({ + identifier: "McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema", + }); -export type V2ThreadListResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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 } } +export type PermissionsRequestApprovalParams__FileSystemPath = + | { readonly path: PermissionsRequestApprovalParams__LegacyAppPathString; readonly type: "path" } + | { readonly pattern: string; readonly type: "glob_pattern" } | { - readonly activeTurnNotSteerable: { - readonly turnKind: V2ThreadListResponse__NonSteerableTurnKind; - }; + readonly type: "special"; + readonly value: PermissionsRequestApprovalParams__FileSystemSpecialPath; }; -export const V2ThreadListResponse__CodexErrorInfo = Schema.Union( +export const PermissionsRequestApprovalParams__FileSystemPath = Schema.Union( [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "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" }), + path: PermissionsRequestApprovalParams__LegacyAppPathString, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), 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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: PermissionsRequestApprovalParams__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "PermissionsRequestApprovalParams__FileSystemPath" }); + +export type PermissionsRequestApprovalResponse__FileSystemPath = + | { + readonly path: PermissionsRequestApprovalResponse__LegacyAppPathString; + 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({ - 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.", - }), + path: PermissionsRequestApprovalResponse__LegacyAppPathString, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadListResponse__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", + 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" }, +).annotate({ identifier: "PermissionsRequestApprovalResponse__FileSystemPath" }); + +export type ServerNotification__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: ServerNotification__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: ServerNotification__MisalignmentErrorDetails | null; +}; +export const ServerNotification__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([ServerNotification__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([ServerNotification__MisalignmentErrorDetails, Schema.Null]).annotate({ description: - "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`.", + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "ServerNotification__TurnError" }); + +export type ServerNotification__CollaborationMode = { + readonly mode: ServerNotification__ModeKind; + readonly settings: ServerNotification__Settings; +}; +export const ServerNotification__CollaborationMode = Schema.Struct({ + mode: ServerNotification__ModeKind, + settings: ServerNotification__Settings, +}).annotate({ + description: "Collaboration mode for a Codex session.", + identifier: "ServerNotification__CollaborationMode", +}); + +export type ServerNotification__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: ServerNotification__SubAgentSource }; +export const ServerNotification__SessionSource = Schema.Union( + [ + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: ServerNotification__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", }), ], { mode: "oneOf" }, -).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.", -}); +).annotate({ identifier: "ServerNotification__SessionSource" }); -export type V2ThreadListResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadListResponse__PatchChangeKind; - readonly path: string; +export type ServerNotification__ThreadStatusChangedNotification = { + readonly status: ServerNotification__ThreadStatus; + readonly threadId: string; }; -export const V2ThreadListResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadListResponse__PatchChangeKind, - path: Schema.String, -}); +export const ServerNotification__ThreadStatusChangedNotification = Schema.Struct({ + status: ServerNotification__ThreadStatus, + threadId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadStatusChangedNotification" }); -export type V2ThreadListResponse__UserInput = +export type ServerNotification__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2ThreadListResponse__ImageDetail | null; + readonly detail?: ServerNotification__ImageDetail | null; readonly type: "image"; readonly url: string; } | { - readonly detail?: V2ThreadListResponse__ImageDetail | null; + readonly detail?: ServerNotification__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: ServerNotification__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -16428,25 +29532,32 @@ export type V2ThreadListResponse__UserInput = | { readonly path: string; readonly type: "localAudio" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadListResponse__UserInput = Schema.Union( +export const ServerNotification__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadListResponse__TextElement).annotate({ + Schema.Array(ServerNotification__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ServerNotification__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([ServerNotification__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadListResponse__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadListResponse__ImageDetail, Schema.Null])), + detail: Schema.optionalKey(Schema.Union([ServerNotification__ImageDetail, Schema.Null])), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), }).annotate({ title: "LocalImageUserInput" }), @@ -16470,521 +29581,1074 @@ export const V2ThreadListResponse__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerNotification__UserInput" }); + +export type ServerNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const ServerNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(ServerNotification__FunctionCallOutputContentItem), +]).annotate({ identifier: "ServerNotification__FunctionCallOutputBody" }); + +export type ServerNotification__FileSystemPath = + | { readonly path: ServerNotification__LegacyAppPathString; 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__LegacyAppPathString, + 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" }, +).annotate({ identifier: "ServerNotification__FileSystemPath" }); + +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, +}).annotate({ identifier: "ServerNotification__FileChangePatchUpdatedNotification" }); + +export type ServerNotification__ThreadGoalUpdatedNotification = { + readonly goal: ServerNotification__ThreadGoal; + readonly threadId: string; + readonly turnId?: string | null; +}; +export const ServerNotification__ThreadGoalUpdatedNotification = Schema.Struct({ + goal: ServerNotification__ThreadGoal, + threadId: Schema.String, + turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__ThreadGoalUpdatedNotification" }); + +export type ServerNotification__ThreadTokenUsageUpdatedNotification = { + readonly threadId: string; + readonly tokenUsage: ServerNotification__ThreadTokenUsage; + readonly turnId: string; +}; +export const ServerNotification__ThreadTokenUsageUpdatedNotification = Schema.Struct({ + threadId: Schema.String, + tokenUsage: ServerNotification__ThreadTokenUsage, + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__ThreadTokenUsageUpdatedNotification" }); + +export type ServerNotification__HookRunSummary = { + readonly completedAt?: number | null; + readonly displayOrder: number; + readonly durationMs?: number | null; + readonly entries: ReadonlyArray; + readonly eventName: ServerNotification__HookEventName; + readonly executionMode: ServerNotification__HookExecutionMode; + readonly handlerType: ServerNotification__HookHandlerType; + readonly id: string; + readonly scope: ServerNotification__HookScope; + readonly source?: ServerNotification__HookSource; + readonly sourcePath: ServerNotification__AbsolutePathBuf; + readonly startedAt: number; + readonly status: ServerNotification__HookRunStatus; + readonly statusMessage?: string | null; +}; +export const ServerNotification__HookRunSummary = Schema.Struct({ + completedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + displayOrder: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + entries: Schema.Array(ServerNotification__HookOutputEntry), + eventName: ServerNotification__HookEventName, + executionMode: ServerNotification__HookExecutionMode, + handlerType: ServerNotification__HookHandlerType, + id: Schema.String, + scope: ServerNotification__HookScope, + source: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => ServerNotification__HookSource, + ).annotate({ default: "unknown" }), + ), + sourcePath: ServerNotification__AbsolutePathBuf, + startedAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + status: ServerNotification__HookRunStatus, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__HookRunSummary" }); + +export type ServerNotification__TurnPlanUpdatedNotification = { + readonly explanation?: string | null; + readonly plan: ReadonlyArray; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerNotification__TurnPlanUpdatedNotification = Schema.Struct({ + explanation: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + plan: Schema.Array(ServerNotification__TurnPlanStep), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__TurnPlanUpdatedNotification" }); + +export type ServerNotification__AccountRateLimitsUpdatedNotification = { + readonly rateLimits: ServerNotification__RateLimitSnapshot; +}; +export const ServerNotification__AccountRateLimitsUpdatedNotification = Schema.Struct({ + rateLimits: ServerNotification__RateLimitSnapshot, +}).annotate({ + description: + "Sparse rolling rate-limit update.\n\nClients should merge available values into the most recent `account/rateLimits/read` response or refetch that snapshot. Nullable account metadata may be unavailable in a rolling update and does not clear a previously observed value.", + identifier: "ServerNotification__AccountRateLimitsUpdatedNotification", +}); + +export type ServerNotification__AppInfo = { + readonly appMetadata?: ServerNotification__AppMetadata | null; + readonly branding?: ServerNotification__AppBranding | null; + readonly description?: string | null; + readonly distributionChannel?: string | null; + readonly iconAssets?: { readonly [x: string]: string } | null; + readonly iconDarkAssets?: { readonly [x: string]: string } | null; + readonly id: string; + readonly installUrl?: string | null; + readonly isAccessible?: boolean; + readonly isEnabled?: boolean; + readonly labels?: { readonly [x: string]: string } | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly name: string; + readonly pluginDisplayNames?: ReadonlyArray; +}; +export const ServerNotification__AppInfo = Schema.Struct({ + appMetadata: Schema.optionalKey(Schema.Union([ServerNotification__AppMetadata, Schema.Null])), + branding: Schema.optionalKey(Schema.Union([ServerNotification__AppBranding, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + distributionChannel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + iconAssets: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + iconDarkAssets: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + id: Schema.String, + installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + isAccessible: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + isEnabled: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", + default: true, + }), + ), + labels: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + name: Schema.String, + pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), +}).annotate({ + description: "EXPERIMENTAL - app metadata returned by app-list APIs.", + identifier: "ServerNotification__AppInfo", +}); -export type V2ThreadListResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" - | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadListResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadListResponse__ThreadId; - }; - } - | { readonly other: string }; -export const V2ThreadListResponse__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([V2ThreadListResponse__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: V2ThreadListResponse__ThreadId, +export type ServerNotification__ExternalAgentConfigImportTypeResult = { + readonly failures: ReadonlyArray; + readonly itemType: ServerNotification__ExternalAgentConfigMigrationItemType; + readonly successes: ReadonlyArray; +}; +export const ServerNotification__ExternalAgentConfigImportTypeResult = Schema.Struct({ + failures: Schema.Array(ServerNotification__ExternalAgentConfigImportItemTypeFailure), + itemType: ServerNotification__ExternalAgentConfigMigrationItemType, + successes: Schema.Array(ServerNotification__ExternalAgentConfigImportItemTypeSuccess), +}).annotate({ identifier: "ServerNotification__ExternalAgentConfigImportTypeResult" }); + +export type ServerNotification__ConfigWarningNotification = { + readonly details?: string | null; + readonly path?: string | null; + readonly range?: ServerNotification__TextRange | null; + readonly summary: string; +}; +export const ServerNotification__ConfigWarningNotification = Schema.Struct({ + details: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Optional extra guidance or error details." }), + Schema.Null, + ]), + ), + path: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional path to the config file that triggered the warning.", }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), - ], - { mode: "oneOf" }, -); + Schema.Null, + ]), + ), + range: Schema.optionalKey( + Schema.Union([ServerNotification__TextRange, Schema.Null]).annotate({ + description: "Optional range for the error location inside the config file.", + }), + ), + summary: Schema.String.annotate({ description: "Concise summary of the warning." }), +}).annotate({ identifier: "ServerNotification__ConfigWarningNotification" }); -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( +export type ServerNotification__FuzzyFileSearchSessionUpdatedNotification = { + readonly files: ReadonlyArray; + readonly query: string; + readonly sessionId: string; +}; +export const ServerNotification__FuzzyFileSearchSessionUpdatedNotification = Schema.Struct({ + files: Schema.Array(ServerNotification__FuzzyFileSearchResult), + query: Schema.String, + sessionId: Schema.String, +}).annotate({ identifier: "ServerNotification__FuzzyFileSearchSessionUpdatedNotification" }); + +export type ServerNotification__ThreadRealtimeItemStartedNotification = { + readonly item: ServerNotification__ThreadRealtimeItem; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeItemStartedNotification = Schema.Struct({ + item: ServerNotification__ThreadRealtimeItem, + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - a realtime timeline item started before its content streams.", + identifier: "ServerNotification__ThreadRealtimeItemStartedNotification", +}); + +export type ServerNotification__ThreadRealtimeItemCompletedNotification = { + readonly item: ServerNotification__ThreadRealtimeItem; + readonly threadId: string; +}; +export const ServerNotification__ThreadRealtimeItemCompletedNotification = Schema.Struct({ + item: ServerNotification__ThreadRealtimeItem, + threadId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL - a realtime timeline item published after canonical commit.", + identifier: "ServerNotification__ThreadRealtimeItemCompletedNotification", +}); + +export type ServerRequest__FileSystemPath = + | { readonly path: ServerRequest__LegacyAppPathString; 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({ - 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" }), + path: ServerRequest__LegacyAppPathString, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), 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" }), + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: ServerRequest__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerRequest__FileSystemPath" }); -export type V2ThreadMetadataUpdateResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadMetadataUpdateResponse__CollabAgentStatus; +export type ServerRequest__CommandExecutionRequestApprovalParams = { + readonly approvalId?: string | null; + readonly command?: string | null; + readonly commandActions?: ReadonlyArray | null; + readonly cwd?: ServerRequest__LegacyAppPathString | null; + readonly environmentId?: string | null; + readonly itemId: string; + readonly kind?: ServerRequest__CommandExecutionApprovalKind; + readonly networkApprovalContext?: ServerRequest__NetworkApprovalContext | null; + readonly proposedExecpolicyAmendment?: ReadonlyArray | null; + readonly proposedNetworkPolicyAmendments?: ReadonlyArray | null; + readonly reason?: string | null; + readonly startedAtMs: number; + readonly threadId: string; + readonly turnId: string; }; -export const V2ThreadMetadataUpdateResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadMetadataUpdateResponse__CollabAgentStatus, -}); +export const ServerRequest__CommandExecutionRequestApprovalParams = Schema.Struct({ + approvalId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Unique identifier for this specific approval callback.\n\nFor regular shell/unified_exec approvals, this is null.\n\nFor zsh-exec-bridge subcommand approvals, multiple callbacks can belong to one parent `itemId`, so `approvalId` is a distinct opaque callback id (a UUID) used to disambiguate routing. Stdin approvals also use a distinct callback id; inspect `kind` to distinguish them.", + }), + Schema.Null, + ]), + ), + command: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "The command to be executed." }), + Schema.Null, + ]), + ), + commandActions: Schema.optionalKey( + Schema.Union([ + Schema.Array(ServerRequest__CommandAction).annotate({ + description: "Best-effort parsed command actions for friendly display.", + }), + Schema.Null, + ]), + ), + cwd: Schema.optionalKey( + Schema.Union([ServerRequest__LegacyAppPathString, Schema.Null]).annotate({ + description: "The command's working directory.", + }), + ), + environmentId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Environment in which the command will run." }), + Schema.Null, + ]), + ), + itemId: Schema.String, + kind: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + ServerRequest__CommandExecutionApprovalKind, + ).annotate({ + description: "Kind of action under review. Defaults to `command` for older servers.", + default: "command", + }), + ), + networkApprovalContext: Schema.optionalKey( + Schema.Union([ServerRequest__NetworkApprovalContext, Schema.Null]).annotate({ + description: "Optional context for a managed-network approval prompt.", + }), + ), + proposedExecpolicyAmendment: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + description: + "Optional proposed execpolicy amendment to allow similar commands without prompting.", + }), + Schema.Null, + ]), + ), + proposedNetworkPolicyAmendments: Schema.optionalKey( + Schema.Union([ + Schema.Array(ServerRequest__NetworkPolicyAmendment).annotate({ + description: + "Optional proposed network policy amendments (allow/deny host) for future requests.", + }), + Schema.Null, + ]), + ), + reason: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional explanatory reason (e.g. request for network access).", + }), + Schema.Null, + ]), + ), + startedAtMs: Schema.Number.annotate({ + description: "Unix timestamp (in milliseconds) when this approval request started.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerRequest__CommandExecutionRequestApprovalParams" }); -export type V2ThreadMetadataUpdateResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +export type ServerRequest__ToolRequestUserInputParams = { + readonly autoResolutionMs?: number | null; + readonly isBlocking: boolean; + readonly itemId: string; + readonly questions: ReadonlyArray; + readonly threadId: string; + readonly turnId: string; }; -export const V2ThreadMetadataUpdateResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadMetadataUpdateResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), +export const ServerRequest__ToolRequestUserInputParams = Schema.Struct({ + autoResolutionMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "@deprecated Use `isBlocking` to decide whether the request should block.", + format: "uint64", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + isBlocking: Schema.Boolean, + itemId: Schema.String, + questions: Schema.Array(ServerRequest__ToolRequestUserInputQuestion), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ + description: "EXPERIMENTAL. Params sent with a request_user_input event.", + identifier: "ServerRequest__ToolRequestUserInputParams", }); -export type V2ThreadMetadataUpdateResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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: V2ThreadMetadataUpdateResponse__NonSteerableTurnKind; - }; - }; -export const V2ThreadMetadataUpdateResponse__CodexErrorInfo = Schema.Union( - [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "internalServerError", - "unauthorized", - "badRequest", - "threadRollbackFailed", - "sandboxError", - "other", +export type ServerRequest__McpElicitationUntitledMultiSelectEnumSchema = { + readonly default?: ReadonlyArray | null; + readonly description?: string | null; + readonly items: ServerRequest__McpElicitationUntitledEnumItems; + readonly maxItems?: number | null; + readonly minItems?: number | null; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationArrayType; +}; +export const ServerRequest__McpElicitationUntitledMultiSelectEnumSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + items: ServerRequest__McpElicitationUntitledEnumItems, + maxItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, ]), - Schema.Struct({ - httpConnectionFailed: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), + ), + minItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), ), - }), - }).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, - ]), + Schema.Null, + ]), + ), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationArrayType, +}).annotate({ identifier: "ServerRequest__McpElicitationUntitledMultiSelectEnumSchema" }); + +export type ServerRequest__McpElicitationSingleSelectEnumSchema = + | ServerRequest__McpElicitationUntitledSingleSelectEnumSchema + | ServerRequest__McpElicitationTitledSingleSelectEnumSchema; +export const ServerRequest__McpElicitationSingleSelectEnumSchema = Schema.Union([ + ServerRequest__McpElicitationUntitledSingleSelectEnumSchema, + ServerRequest__McpElicitationTitledSingleSelectEnumSchema, +]).annotate({ identifier: "ServerRequest__McpElicitationSingleSelectEnumSchema" }); + +export type ServerRequest__McpElicitationTitledMultiSelectEnumSchema = { + readonly default?: ReadonlyArray | null; + readonly description?: string | null; + readonly items: ServerRequest__McpElicitationTitledEnumItems; + readonly maxItems?: number | null; + readonly minItems?: number | null; + readonly title?: string | null; + readonly type: ServerRequest__McpElicitationArrayType; +}; +export const ServerRequest__McpElicitationTitledMultiSelectEnumSchema = Schema.Struct({ + default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + items: ServerRequest__McpElicitationTitledEnumItems, + maxItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), ), - }), - }).annotate({ - title: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), + Schema.Null, + ]), + ), + minItems: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", + Schema.Null, + ]), + ), + title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: ServerRequest__McpElicitationArrayType, +}).annotate({ identifier: "ServerRequest__McpElicitationTitledMultiSelectEnumSchema" }); + +export type V2AppListUpdatedNotification__AppInfo = { + readonly appMetadata?: V2AppListUpdatedNotification__AppMetadata | null; + readonly branding?: V2AppListUpdatedNotification__AppBranding | null; + readonly description?: string | null; + readonly distributionChannel?: string | null; + readonly iconAssets?: { readonly [x: string]: string } | null; + readonly iconDarkAssets?: { readonly [x: string]: string } | null; + readonly id: string; + readonly installUrl?: string | null; + readonly isAccessible?: boolean; + readonly isEnabled?: boolean; + readonly labels?: { readonly [x: string]: string } | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly name: string; + readonly pluginDisplayNames?: ReadonlyArray; +}; +export const V2AppListUpdatedNotification__AppInfo = Schema.Struct({ + appMetadata: Schema.optionalKey( + Schema.Union([V2AppListUpdatedNotification__AppMetadata, Schema.Null]), + ), + branding: Schema.optionalKey( + Schema.Union([V2AppListUpdatedNotification__AppBranding, Schema.Null]), + ), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + distributionChannel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + iconAssets: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + iconDarkAssets: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + id: Schema.String, + installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + isAccessible: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + isEnabled: Schema.optionalKey( + Schema.Boolean.annotate({ description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - 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.", + "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", + default: true, }), - Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadMetadataUpdateResponse__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", + ), + labels: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + name: Schema.String, + pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), +}).annotate({ + description: "EXPERIMENTAL - app metadata returned by app-list APIs.", + identifier: "V2AppListUpdatedNotification__AppInfo", +}); + +export type V2AppsListResponse__AppInfo = { + readonly appMetadata?: V2AppsListResponse__AppMetadata | null; + readonly branding?: V2AppsListResponse__AppBranding | null; + readonly description?: string | null; + readonly distributionChannel?: string | null; + readonly iconAssets?: { readonly [x: string]: string } | null; + readonly iconDarkAssets?: { readonly [x: string]: string } | null; + readonly id: string; + readonly installUrl?: string | null; + readonly isAccessible?: boolean; + readonly isEnabled?: boolean; + readonly labels?: { readonly [x: string]: string } | null; + readonly logoUrl?: string | null; + readonly logoUrlDark?: string | null; + readonly name: string; + readonly pluginDisplayNames?: ReadonlyArray; +}; +export const V2AppsListResponse__AppInfo = Schema.Struct({ + appMetadata: Schema.optionalKey(Schema.Union([V2AppsListResponse__AppMetadata, Schema.Null])), + branding: Schema.optionalKey(Schema.Union([V2AppsListResponse__AppBranding, Schema.Null])), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + distributionChannel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + iconAssets: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + iconDarkAssets: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + id: Schema.String, + installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + isAccessible: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), + isEnabled: Schema.optionalKey( + Schema.Boolean.annotate({ description: - "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`.", + "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", + default: true, }), - ], - { mode: "oneOf" }, -).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.", + ), + labels: Schema.optionalKey( + Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), + ), + logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + name: Schema.String, + pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), +}).annotate({ + description: "EXPERIMENTAL - app metadata returned by app-list APIs.", + identifier: "V2AppsListResponse__AppInfo", }); -export type V2ThreadMetadataUpdateResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadMetadataUpdateResponse__PatchChangeKind; - readonly path: string; +export type V2ConfigReadResponse__BrowserUseConfig = { + readonly allow_history_access?: boolean | null; + readonly default_origin_policy?: V2ConfigReadResponse__BrowserUseOriginPolicyConfig | null; + readonly origins?: { + readonly [x: string]: V2ConfigReadResponse__BrowserUseOriginPolicyConfig; + } | null; }; -export const V2ThreadMetadataUpdateResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadMetadataUpdateResponse__PatchChangeKind, - path: Schema.String, -}); +export const V2ConfigReadResponse__BrowserUseConfig = Schema.Struct({ + allow_history_access: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + default_origin_policy: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__BrowserUseOriginPolicyConfig, Schema.Null]), + ), + origins: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, V2ConfigReadResponse__BrowserUseOriginPolicyConfig), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2ConfigReadResponse__BrowserUseConfig" }); -export type V2ThreadMetadataUpdateResponse__UserInput = - | { - readonly text: string; - readonly text_elements?: ReadonlyArray; - readonly type: "text"; - } - | { - readonly detail?: V2ThreadMetadataUpdateResponse__ImageDetail | null; - readonly type: "image"; - readonly url: string; - } - | { - readonly detail?: V2ThreadMetadataUpdateResponse__ImageDetail | null; - readonly path: string; - readonly type: "localImage"; - } - | { readonly type: "audio"; readonly url: string } - | { readonly path: string; readonly type: "localAudio" } - | { readonly name: string; readonly path: string; readonly type: "skill" } - | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadMetadataUpdateResponse__UserInput = Schema.Union( - [ - Schema.Struct({ - text: Schema.String, - text_elements: Schema.optionalKey( - Schema.Array(V2ThreadMetadataUpdateResponse__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({ - detail: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__ImageDetail, Schema.Null]), - ), - path: Schema.String, - type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), - }).annotate({ title: "LocalImageUserInput" }), - Schema.Struct({ - type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), - url: Schema.String, - }).annotate({ title: "AudioUserInput" }), - Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), - 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 V2ConfigReadResponse__ComputerUseWindowsConfig = { + readonly aumids?: { readonly [x: string]: V2ConfigReadResponse__AllowDenyRequirement } | null; + readonly exes?: ReadonlyArray | null; +}; +export const V2ConfigReadResponse__ComputerUseWindowsConfig = Schema.Struct({ + aumids: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, V2ConfigReadResponse__AllowDenyRequirement), + Schema.Null, + ]), + ), + exes: Schema.optionalKey( + Schema.Union([Schema.Array(V2ConfigReadResponse__ComputerUseWindowsExeConfig), Schema.Null]), + ), +}).annotate({ identifier: "V2ConfigReadResponse__ComputerUseWindowsConfig" }); -export type V2ThreadMetadataUpdateResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" - | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadMetadataUpdateResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadMetadataUpdateResponse__ThreadId; - }; - } - | { readonly other: string }; -export const V2ThreadMetadataUpdateResponse__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([V2ThreadMetadataUpdateResponse__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: V2ThreadMetadataUpdateResponse__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), - ], - { mode: "oneOf" }, -); +export type V2ConfigReadResponse__ToolsV2 = { + readonly web_search?: V2ConfigReadResponse__WebSearchToolConfig | null; +}; +export const V2ConfigReadResponse__ToolsV2 = Schema.Struct({ + web_search: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__WebSearchToolConfig, Schema.Null]), + ), +}).annotate({ identifier: "V2ConfigReadResponse__ToolsV2" }); -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 V2ConfigReadResponse__ConfigLayer = { + readonly config: Schema.Json; + readonly disabledReason?: string | null; + readonly name: V2ConfigReadResponse__ConfigLayerSource; + readonly version: string; +}; +export const V2ConfigReadResponse__ConfigLayer = Schema.Struct({ + config: Schema.Json.annotate({ expected: "JSON value" }), + disabledReason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + name: V2ConfigReadResponse__ConfigLayerSource, + version: Schema.String, +}).annotate({ identifier: "V2ConfigReadResponse__ConfigLayer" }); -export type V2ThreadReadResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadReadResponse__CollabAgentStatus; +export type V2ConfigReadResponse__ConfigLayerMetadata = { + readonly name: V2ConfigReadResponse__ConfigLayerSource; + readonly version: string; }; -export const V2ThreadReadResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadReadResponse__CollabAgentStatus, -}); +export const V2ConfigReadResponse__ConfigLayerMetadata = Schema.Struct({ + name: V2ConfigReadResponse__ConfigLayerSource, + version: Schema.String, +}).annotate({ identifier: "V2ConfigReadResponse__ConfigLayerMetadata" }); -export type V2ThreadReadResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +export type V2ConfigRequirementsReadResponse__ComputerUseWindowsRequirements = { + readonly aumids?: { + readonly [x: string]: V2ConfigRequirementsReadResponse__AllowDenyRequirement; + } | null; + readonly exes?: ReadonlyArray | null; }; -export const V2ThreadReadResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadReadResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); +export const V2ConfigRequirementsReadResponse__ComputerUseWindowsRequirements = Schema.Struct({ + aumids: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, V2ConfigRequirementsReadResponse__AllowDenyRequirement), + Schema.Null, + ]), + ), + exes: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ConfigRequirementsReadResponse__ComputerUseWindowsExeRequirement), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ComputerUseWindowsRequirements" }); -export type V2ThreadReadResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "internalServerError" - | "unauthorized" - | "badRequest" - | "threadRollbackFailed" - | "sandboxError" - | "rateLimitExceeded" - | "misalignmentPolicyViolation" - | "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: V2ThreadReadResponse__NonSteerableTurnKind; - }; - }; -export const V2ThreadReadResponse__CodexErrorInfo = Schema.Union( - [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "internalServerError", - "unauthorized", - "badRequest", - "threadRollbackFailed", - "sandboxError", - "rateLimitExceeded", - "misalignmentPolicyViolation", - "other", +export type V2ConfigRequirementsReadResponse__BrowserUseRequirements = { + readonly allowGlobalPersistentApproval?: boolean | null; + readonly allowHistoryAccess?: boolean | null; + readonly allowWebmcp?: boolean | null; + readonly defaultOriginPolicy?: V2ConfigRequirementsReadResponse__BrowserUseOriginPolicy | null; + readonly disableAutoReview?: boolean | null; + readonly origins?: { + readonly [x: string]: V2ConfigRequirementsReadResponse__BrowserUseOriginPolicy; + } | null; +}; +export const V2ConfigRequirementsReadResponse__BrowserUseRequirements = Schema.Struct({ + allowGlobalPersistentApproval: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + allowHistoryAccess: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + allowWebmcp: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + defaultOriginPolicy: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__BrowserUseOriginPolicy, Schema.Null]), + ), + disableAutoReview: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + origins: Schema.optionalKey( + Schema.Union([ + Schema.Record(Schema.String, V2ConfigRequirementsReadResponse__BrowserUseOriginPolicy), + Schema.Null, ]), - 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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", + ), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__BrowserUseRequirements" }); + +export type V2ConfigRequirementsReadResponse__ModelsRequirements = { + readonly newThread?: V2ConfigRequirementsReadResponse__NewThreadModelDefaults | null; +}; +export const V2ConfigRequirementsReadResponse__ModelsRequirements = Schema.Struct({ + newThread: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__NewThreadModelDefaults, Schema.Null]), + ), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ModelsRequirements" }); + +export type V2ConfigWriteResponse__ConfigLayerMetadata = { + readonly name: V2ConfigWriteResponse__ConfigLayerSource; + readonly version: string; +}; +export const V2ConfigWriteResponse__ConfigLayerMetadata = Schema.Struct({ + name: V2ConfigWriteResponse__ConfigLayerSource, + version: Schema.String, +}).annotate({ identifier: "V2ConfigWriteResponse__ConfigLayerMetadata" }); + +export type V2ErrorNotification__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ErrorNotification__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ErrorNotification__MisalignmentErrorDetails | null; +}; +export const V2ErrorNotification__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ErrorNotification__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ErrorNotification__MisalignmentErrorDetails, Schema.Null]).annotate({ description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - 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.", + "Optional public explanation and continuation instruction for a misalignment block.", }), - Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadReadResponse__NonSteerableTurnKind, + ), +}).annotate({ identifier: "V2ErrorNotification__TurnError" }); + +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.", }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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: - "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.", + Schema.Null, + ]), + ), + description: Schema.String, + details: Schema.optionalKey( + Schema.Union([V2ExternalAgentConfigDetectResponse__MigrationDetails, Schema.Null]), + ), + itemType: V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType, +}).annotate({ + identifier: "V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItem", }); -export type V2ThreadReadResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadReadResponse__PatchChangeKind; - readonly path: string; +export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportTypeResult = + { + readonly failures: ReadonlyArray; + readonly itemType: V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType; + readonly successes: ReadonlyArray; + }; +export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportTypeResult = + Schema.Struct({ + failures: Schema.Array( + V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeFailure, + ), + itemType: + V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType, + successes: Schema.Array( + V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeSuccess, + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportTypeResult", + }); + +export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportHistory = { + readonly completedAtMs: number; + readonly failures: ReadonlyArray; + readonly importId: string; + readonly providerId?: string | null; + readonly successes: ReadonlyArray; +}; +export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportHistory = + Schema.Struct({ + completedAtMs: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + failures: Schema.Array( + V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeFailure, + ), + importId: Schema.String, + providerId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + successes: Schema.Array( + V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeSuccess, + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportHistory", + }); + +export type V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordTypeResultParams = + { + readonly failures: ReadonlyArray; + readonly itemType: V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType; + readonly successes: ReadonlyArray; + }; +export const V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordTypeResultParams = + Schema.Struct({ + failures: Schema.Array( + V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportItemTypeFailure, + ), + itemType: V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigMigrationItemType, + successes: Schema.Array( + V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordSuccessParams, + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordTypeResultParams", + }); + +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, +}).annotate({ identifier: "V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItem" }); + +export type V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportTypeResult = { + readonly failures: ReadonlyArray; + readonly itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType; + readonly successes: ReadonlyArray; +}; +export const V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportTypeResult = + Schema.Struct({ + failures: Schema.Array( + V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeFailure, + ), + itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType, + successes: Schema.Array( + V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeSuccess, + ), + }).annotate({ + identifier: + "V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportTypeResult", + }); + +export type V2GetAccountRateLimitsResponse__RateLimitResetCreditsSummary = { + readonly availableCount: number; + readonly credits?: ReadonlyArray | null; +}; +export const V2GetAccountRateLimitsResponse__RateLimitResetCreditsSummary = Schema.Struct({ + availableCount: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + credits: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2GetAccountRateLimitsResponse__RateLimitResetCredit).annotate({ + description: + "Detail rows for available reset credits, when the backend provides them.\n\n`null` means only `availableCount` is known, while an empty array means details were fetched and no available credits were returned. The backend may cap this list, so its length can be less than `availableCount`.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2GetAccountRateLimitsResponse__RateLimitResetCreditsSummary" }); + +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?: V2HookCompletedNotification__HookSource; + 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().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + displayOrder: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + 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.suspend( + (): Schema.Codec => + V2HookCompletedNotification__HookSource, + ).annotate({ default: "unknown" }), + ), + sourcePath: V2HookCompletedNotification__AbsolutePathBuf, + startedAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + status: V2HookCompletedNotification__HookRunStatus, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2HookCompletedNotification__HookRunSummary" }); + +export type V2HooksListResponse__HooksListEntry = { + readonly cwd: string; + readonly errors: ReadonlyArray; + readonly hooks: ReadonlyArray; + readonly warnings: ReadonlyArray; +}; +export const V2HooksListResponse__HooksListEntry = Schema.Struct({ + cwd: Schema.String, + errors: Schema.Array(V2HooksListResponse__HookErrorInfo), + hooks: Schema.Array(V2HooksListResponse__HookMetadata), + warnings: Schema.Array(Schema.String), +}).annotate({ identifier: "V2HooksListResponse__HooksListEntry" }); + +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?: V2HookStartedNotification__HookSource; + readonly sourcePath: V2HookStartedNotification__AbsolutePathBuf; + readonly startedAt: number; + readonly status: V2HookStartedNotification__HookRunStatus; + readonly statusMessage?: string | null; }; -export const V2ThreadReadResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadReadResponse__PatchChangeKind, - path: Schema.String, -}); +export const V2HookStartedNotification__HookRunSummary = Schema.Struct({ + completedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), + ), + displayOrder: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + 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.suspend( + (): Schema.Codec => + V2HookStartedNotification__HookSource, + ).annotate({ default: "unknown" }), + ), + sourcePath: V2HookStartedNotification__AbsolutePathBuf, + startedAt: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + status: V2HookStartedNotification__HookRunStatus, + statusMessage: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2HookStartedNotification__HookRunSummary" }); -export type V2ThreadReadResponse__UserInput = +export type V2ItemCompletedNotification__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2ThreadReadResponse__ImageDetail | null; + readonly detail?: V2ItemCompletedNotification__ImageDetail | null; readonly type: "image"; readonly url: string; } | { - readonly detail?: V2ThreadReadResponse__ImageDetail | null; + readonly detail?: V2ItemCompletedNotification__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ItemCompletedNotification__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -16992,25 +30656,38 @@ export type V2ThreadReadResponse__UserInput = | { readonly path: string; readonly type: "localAudio" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadReadResponse__UserInput = Schema.Union( +export const V2ItemCompletedNotification__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadReadResponse__TextElement).annotate({ + Schema.Array(V2ItemCompletedNotification__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__ImageDetail, Schema.Null])), + detail: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__ImageDetail, Schema.Null]), + ), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), }).annotate({ title: "LocalImageUserInput" }), @@ -17034,314 +30711,605 @@ export const V2ThreadReadResponse__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ItemCompletedNotification__UserInput" }); -export type V2ThreadReadResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" +export type V2ItemCompletedNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ItemCompletedNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ItemCompletedNotification__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ItemCompletedNotification__FunctionCallOutputBody" }); + +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath = | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadReadResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadReadResponse__ThreadId; - }; + readonly path: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString; + readonly type: "path"; } - | { readonly other: string }; -export const V2ThreadReadResponse__SubAgentSource = Schema.Union( + | { readonly pattern: string; readonly type: "glob_pattern" } + | { + readonly type: "special"; + readonly value: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSpecialPath; + }; +export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath = 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([V2ThreadReadResponse__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: V2ThreadReadResponse__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + path: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, + 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" }, -); +).annotate({ identifier: "V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath" }); -export type V2ThreadResumeParams__ContentItem = - | { readonly text: string; readonly type: "input_text" } +export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath = | { - readonly detail?: V2ThreadResumeParams__ImageDetail | null; - readonly image_url: string; - readonly type: "input_image"; + readonly path: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString; + readonly type: "path"; } - | { readonly audio_url: string; readonly type: "input_audio" } - | { readonly text: string; readonly type: "output_text" }; -export const V2ThreadResumeParams__ContentItem = Schema.Union( + | { readonly pattern: string; readonly type: "glob_pattern" } + | { + readonly type: "special"; + readonly value: V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath; + }; +export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath = 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" }), + path: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, + type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), + }).annotate({ title: "PathFileSystemPath" }), Schema.Struct({ - audio_url: Schema.String, - type: Schema.Literal("input_audio").annotate({ title: "InputAudioContentItemType" }), - }).annotate({ title: "InputAudioContentItem" }), + pattern: Schema.String, + type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), + }).annotate({ title: "GlobPatternFileSystemPath" }), Schema.Struct({ - text: Schema.String, - type: Schema.Literal("output_text").annotate({ title: "OutputTextContentItemType" }), - }).annotate({ title: "OutputTextContentItem" }), + type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), + value: V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath, + }).annotate({ title: "SpecialFileSystemPath" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath" }); -export type V2ThreadResumeParams__FunctionCallOutputContentItem = - | { readonly text: string; readonly type: "input_text" } +export type V2ItemStartedNotification__UserInput = | { - readonly detail?: V2ThreadResumeParams__ImageDetail | null; - readonly image_url: string; - readonly type: "input_image"; + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; } - | { readonly audio_url: string; readonly type: "input_audio" } - | { readonly encrypted_content: string; readonly type: "encrypted_content" }; -export const V2ThreadResumeParams__FunctionCallOutputContentItem = Schema.Union( + | { + readonly detail?: V2ItemStartedNotification__ImageDetail | null; + readonly type: "image"; + readonly url: string; + } + | { + readonly detail?: V2ItemStartedNotification__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ItemStartedNotification__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2ItemStartedNotification__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, - type: Schema.Literal("input_text").annotate({ - title: "InputTextFunctionCallOutputContentItemType", - }), - }).annotate({ title: "InputTextFunctionCallOutputContentItem" }), + text_elements: Schema.optionalKey( + Schema.Array(V2ItemStartedNotification__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__ImageDetail, Schema.Null])), - image_url: Schema.String, - type: Schema.Literal("input_image").annotate({ - title: "InputImageFunctionCallOutputContentItemType", - }), - }).annotate({ title: "InputImageFunctionCallOutputContentItem" }), + detail: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__ImageDetail, Schema.Null]), + ), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), Schema.Struct({ - audio_url: Schema.String, - type: Schema.Literal("input_audio").annotate({ - title: "InputAudioFunctionCallOutputContentItemType", - }), - }).annotate({ title: "InputAudioFunctionCallOutputContentItem" }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - encrypted_content: Schema.String, - type: Schema.Literal("encrypted_content").annotate({ - title: "EncryptedContentFunctionCallOutputContentItemType", - }), - }).annotate({ title: "EncryptedContentFunctionCallOutputContentItem" }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, -).annotate({ - description: - "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.", -}); +).annotate({ identifier: "V2ItemStartedNotification__UserInput" }); -export type V2ThreadResumeResponse__CommandAction = +export type V2ItemStartedNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ItemStartedNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ItemStartedNotification__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ItemStartedNotification__FunctionCallOutputBody" }); + +export type V2ModelListResponse__Model = { + readonly additionalSpeedTiers?: ReadonlyArray; + readonly availabilityNux?: V2ModelListResponse__ModelAvailabilityNux | null; + readonly availableAccessPrograms?: V2ModelListResponse__ModelAccessPrograms | null; + readonly defaultReasoningEffort: V2ModelListResponse__ReasoningEffort; + readonly defaultServiceTier?: string | null; + readonly description: string; + readonly displayName: string; + readonly hidden: boolean; + readonly id: string; + readonly inputModalities?: ReadonlyArray; + readonly isDefault: boolean; + readonly model: string; + readonly modelSpecialty?: string | null; + readonly multiAgentVersion?: V2ModelListResponse__MultiAgentVersion | null; + readonly serviceTiers?: ReadonlyArray; + 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({ + description: "Deprecated: use `serviceTiers` instead.", + default: [], + }), + ), + availabilityNux: Schema.optionalKey( + Schema.Union([V2ModelListResponse__ModelAvailabilityNux, Schema.Null]), + ), + availableAccessPrograms: Schema.optionalKey( + Schema.Union([V2ModelListResponse__ModelAccessPrograms, Schema.Null]).annotate({ + description: "Null when the catalog does not provide access-program metadata.", + }), + ), + defaultReasoningEffort: V2ModelListResponse__ReasoningEffort, + defaultServiceTier: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Catalog default service tier id for this model, when one is configured.", + }), + Schema.Null, + ]), + ), + 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, + modelSpecialty: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + multiAgentVersion: Schema.optionalKey( + Schema.Union([V2ModelListResponse__MultiAgentVersion, Schema.Null]).annotate({ + description: "Multi-agent runtime declared by this model, when available.", + }), + ), + serviceTiers: Schema.optionalKey( + Schema.Array(V2ModelListResponse__ModelServiceTier).annotate({ default: [] }), + ), + supportedReasoningEfforts: Schema.Array(V2ModelListResponse__ReasoningEffortOption), + supportsPersonality: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "@deprecated Always false; models no longer support personality selection.", + default: false, + }), + ), + upgrade: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + upgradeInfo: Schema.optionalKey( + Schema.Union([V2ModelListResponse__ModelUpgradeInfo, Schema.Null]), + ), +}).annotate({ identifier: "V2ModelListResponse__Model" }); + +export type V2PluginInstalledResponse__PluginShareContext = { + readonly canPublishToWorkspace?: boolean | null; + readonly creatorAccountUserId?: string | null; + readonly creatorName?: string | null; + readonly discoverability?: V2PluginInstalledResponse__PluginShareDiscoverability | null; + readonly remotePluginId: string; + readonly remoteVersion?: string | null; + readonly sharePrincipals?: ReadonlyArray | null; + readonly shareUrl?: string | null; +}; +export const V2PluginInstalledResponse__PluginShareContext = Schema.Struct({ + canPublishToWorkspace: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + discoverability: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__PluginShareDiscoverability, Schema.Null]), + ), + remotePluginId: Schema.String, + remoteVersion: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Version of the remote shared plugin release when available.", + }), + Schema.Null, + ]), + ), + sharePrincipals: Schema.optionalKey( + Schema.Union([Schema.Array(V2PluginInstalledResponse__PluginSharePrincipal), Schema.Null]), + ), + shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2PluginInstalledResponse__PluginShareContext" }); + +export type V2PluginListResponse__PluginShareContext = { + readonly canPublishToWorkspace?: boolean | null; + readonly creatorAccountUserId?: string | null; + readonly creatorName?: string | null; + readonly discoverability?: V2PluginListResponse__PluginShareDiscoverability | null; + readonly remotePluginId: string; + readonly remoteVersion?: string | null; + readonly sharePrincipals?: ReadonlyArray | null; + readonly shareUrl?: string | null; +}; +export const V2PluginListResponse__PluginShareContext = Schema.Struct({ + canPublishToWorkspace: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + discoverability: Schema.optionalKey( + Schema.Union([V2PluginListResponse__PluginShareDiscoverability, Schema.Null]), + ), + remotePluginId: Schema.String, + remoteVersion: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Version of the remote shared plugin release when available.", + }), + Schema.Null, + ]), + ), + sharePrincipals: Schema.optionalKey( + Schema.Union([Schema.Array(V2PluginListResponse__PluginSharePrincipal), Schema.Null]), + ), + shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2PluginListResponse__PluginShareContext" }); + +export type V2PluginReadResponse__SkillSummary = { + readonly description: string; + readonly enabled: boolean; + readonly interface?: V2PluginReadResponse__SkillInterface | null; + readonly name: string; + readonly path?: 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.optionalKey(Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null])), + shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2PluginReadResponse__SkillSummary" }); + +export type V2PluginReadResponse__ScheduledTaskSummary = { + readonly key: string; + readonly name: string; + readonly prompt: string; + readonly schedule: V2PluginReadResponse__ScheduledTaskSchedule; +}; +export const V2PluginReadResponse__ScheduledTaskSummary = Schema.Struct({ + key: Schema.String, + name: Schema.String, + prompt: Schema.String, + schedule: V2PluginReadResponse__ScheduledTaskSchedule, +}).annotate({ identifier: "V2PluginReadResponse__ScheduledTaskSummary" }); + +export type V2PluginReadResponse__PluginShareContext = { + readonly canPublishToWorkspace?: boolean | null; + readonly creatorAccountUserId?: string | null; + readonly creatorName?: string | null; + readonly discoverability?: V2PluginReadResponse__PluginShareDiscoverability | null; + readonly remotePluginId: string; + readonly remoteVersion?: string | null; + readonly sharePrincipals?: ReadonlyArray | null; + readonly shareUrl?: string | null; +}; +export const V2PluginReadResponse__PluginShareContext = Schema.Struct({ + canPublishToWorkspace: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + discoverability: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__PluginShareDiscoverability, Schema.Null]), + ), + remotePluginId: Schema.String, + remoteVersion: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Version of the remote shared plugin release when available.", + }), + Schema.Null, + ]), + ), + sharePrincipals: Schema.optionalKey( + Schema.Union([Schema.Array(V2PluginReadResponse__PluginSharePrincipal), Schema.Null]), + ), + shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2PluginReadResponse__PluginShareContext" }); + +export type V2PluginShareListResponse__PluginShareContext = { + readonly canPublishToWorkspace?: boolean | null; + readonly creatorAccountUserId?: string | null; + readonly creatorName?: string | null; + readonly discoverability?: V2PluginShareListResponse__PluginShareDiscoverability | null; + readonly remotePluginId: string; + readonly remoteVersion?: string | null; + readonly sharePrincipals?: ReadonlyArray | null; + readonly shareUrl?: string | null; +}; +export const V2PluginShareListResponse__PluginShareContext = Schema.Struct({ + canPublishToWorkspace: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + discoverability: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__PluginShareDiscoverability, Schema.Null]), + ), + remotePluginId: Schema.String, + remoteVersion: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Version of the remote shared plugin release when available.", + }), + Schema.Null, + ]), + ), + sharePrincipals: Schema.optionalKey( + Schema.Union([Schema.Array(V2PluginShareListResponse__PluginSharePrincipal), Schema.Null]), + ), + shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2PluginShareListResponse__PluginShareContext" }); + +export type V2RawResponseItemCompletedNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2RawResponseItemCompletedNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2RawResponseItemCompletedNotification__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2RawResponseItemCompletedNotification__FunctionCallOutputBody" }); + +export type V2ReviewStartResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ReviewStartResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ReviewStartResponse__MisalignmentErrorDetails | null; +}; +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, + misalignment: Schema.optionalKey( + Schema.Union([V2ReviewStartResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ReviewStartResponse__TurnError" }); + +export type V2ReviewStartResponse__UserInput = | { - readonly command: string; - readonly name: string; - readonly path: V2ThreadResumeResponse__AbsolutePathBuf; - readonly type: "read"; + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; } - | { 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 detail?: V2ReviewStartResponse__ImageDetail | null; + readonly type: "image"; + readonly url: string; + } + | { + readonly detail?: V2ReviewStartResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ReviewStartResponse__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadResumeResponse__CommandAction = Schema.Union( + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2ReviewStartResponse__UserInput = Schema.Union( [ Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: V2ThreadResumeResponse__AbsolutePathBuf, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2ReviewStartResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), + detail: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__ImageDetail, Schema.Null])), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), 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" }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, -); +).annotate({ identifier: "V2ReviewStartResponse__UserInput" }); -export type V2ThreadResumeResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadResumeResponse__CollabAgentStatus; -}; -export const V2ThreadResumeResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadResumeResponse__CollabAgentStatus, -}); +export type V2ReviewStartResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ReviewStartResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ReviewStartResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ReviewStartResponse__FunctionCallOutputBody" }); -export type V2ThreadResumeResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +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: V2SkillsListResponse__AbsolutePathBuf; + readonly pluginId?: string | null; + readonly scope: V2SkillsListResponse__SkillScope; + readonly shortDescription?: string | null; }; -export const V2ThreadResumeResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadResumeResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); - -export type V2ThreadResumeResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "internalServerError" - | "unauthorized" - | "badRequest" - | "threadRollbackFailed" - | "sandboxError" - | "rateLimitExceeded" - | "misalignmentPolicyViolation" - | "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: V2ThreadResumeResponse__NonSteerableTurnKind; - }; - }; -export const V2ThreadResumeResponse__CodexErrorInfo = Schema.Union( - [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "internalServerError", - "unauthorized", - "badRequest", - "threadRollbackFailed", - "sandboxError", - "rateLimitExceeded", - "misalignmentPolicyViolation", - "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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - Schema.Struct({ - responseTooManyFailedAttempts: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.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: V2SkillsListResponse__AbsolutePathBuf, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Owning plugin ID, matching `PluginSummary.id`, when known.", }), - }).annotate({ - title: "ResponseTooManyFailedAttemptsCodexErrorInfo", - description: "Reached the retry limit for responses.", - }), - Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadResumeResponse__NonSteerableTurnKind, + Schema.Null, + ]), + ), + scope: V2SkillsListResponse__SkillScope, + shortDescription: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Legacy short_description from SKILL.md. Prefer SKILL.json interface.short_description.", }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2SkillsListResponse__SkillMetadata" }); + +export type V2ThreadForkResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadForkResponse__SubAgentSource }; +export const V2ThreadForkResponse__SessionSource = Schema.Union( + [ + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadForkResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", }), ], { mode: "oneOf" }, -).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.", -}); +).annotate({ identifier: "V2ThreadForkResponse__SessionSource" }); -export type V2ThreadResumeResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadResumeResponse__PatchChangeKind; - readonly path: string; +export type V2ThreadForkResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadForkResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadForkResponse__MisalignmentErrorDetails | null; }; -export const V2ThreadResumeResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadResumeResponse__PatchChangeKind, - path: Schema.String, -}); +export const V2ThreadForkResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadForkResponse__TurnError" }); -export type V2ThreadResumeResponse__UserInput = +export type V2ThreadForkResponse__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2ThreadResumeResponse__ImageDetail | null; + readonly detail?: V2ThreadForkResponse__ImageDetail | null; readonly type: "image"; readonly url: string; } | { - readonly detail?: V2ThreadResumeResponse__ImageDetail | null; + readonly detail?: V2ThreadForkResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadForkResponse__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -17349,25 +31317,32 @@ export type V2ThreadResumeResponse__UserInput = | { readonly path: string; readonly type: "localAudio" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadResumeResponse__UserInput = Schema.Union( +export const V2ThreadForkResponse__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadResumeResponse__TextElement).annotate({ + Schema.Array(V2ThreadForkResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__ImageDetail, Schema.Null])), + detail: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__ImageDetail, Schema.Null])), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), }).annotate({ title: "LocalImageUserInput" }), @@ -17391,239 +31366,163 @@ export const V2ThreadResumeResponse__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadForkResponse__UserInput" }); -export type V2ThreadResumeResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" +export type V2ThreadForkResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadForkResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadForkResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadForkResponse__FunctionCallOutputBody" }); + +export type V2ThreadItemsListResponse__UserInput = | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadResumeResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadResumeResponse__ThreadId; - }; + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; } - | { readonly other: string }; -export const V2ThreadResumeResponse__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([V2ThreadResumeResponse__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: V2ThreadResumeResponse__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadRollbackResponse__CommandAction = | { - readonly command: string; - readonly name: string; - readonly path: V2ThreadRollbackResponse__AbsolutePathBuf; - readonly type: "read"; + readonly detail?: V2ThreadItemsListResponse__ImageDetail | null; + readonly type: "image"; + readonly url: string; } - | { 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 detail?: V2ThreadItemsListResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadRollbackResponse__CommandAction = Schema.Union( + | { + readonly detail?: V2ThreadItemsListResponse__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2ThreadItemsListResponse__UserInput = Schema.Union( [ Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: V2ThreadRollbackResponse__AbsolutePathBuf, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2ThreadItemsListResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), + detail: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__ImageDetail, Schema.Null]), + ), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), 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" }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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 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, -}); +).annotate({ identifier: "V2ThreadItemsListResponse__UserInput" }); -export type V2ThreadRollbackResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const V2ThreadRollbackResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadRollbackResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); +export type V2ThreadItemsListResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadItemsListResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadItemsListResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadItemsListResponse__FunctionCallOutputBody" }); -export type V2ThreadRollbackResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "internalServerError" - | "unauthorized" - | "badRequest" - | "threadRollbackFailed" - | "sandboxError" - | "rateLimitExceeded" - | "misalignmentPolicyViolation" - | "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: V2ThreadRollbackResponse__NonSteerableTurnKind; - }; - }; -export const V2ThreadRollbackResponse__CodexErrorInfo = Schema.Union( +export type V2ThreadListResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadListResponse__SubAgentSource }; +export const V2ThreadListResponse__SessionSource = Schema.Union( [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "internalServerError", - "unauthorized", - "badRequest", - "threadRollbackFailed", - "sandboxError", - "rateLimitExceeded", - "misalignmentPolicyViolation", - "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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - 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.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadRollbackResponse__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadListResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", }), ], { mode: "oneOf" }, -).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.", -}); +).annotate({ identifier: "V2ThreadListResponse__SessionSource" }); -export type V2ThreadRollbackResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadRollbackResponse__PatchChangeKind; - readonly path: string; +export type V2ThreadListResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadListResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadListResponse__MisalignmentErrorDetails | null; }; -export const V2ThreadRollbackResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadRollbackResponse__PatchChangeKind, - path: Schema.String, -}); +export const V2ThreadListResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadListResponse__TurnError" }); -export type V2ThreadRollbackResponse__UserInput = +export type V2ThreadListResponse__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2ThreadRollbackResponse__ImageDetail | null; + readonly detail?: V2ThreadListResponse__ImageDetail | null; readonly type: "image"; readonly url: string; } | { - readonly detail?: V2ThreadRollbackResponse__ImageDetail | null; + readonly detail?: V2ThreadListResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadListResponse__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -17631,29 +31530,32 @@ export type V2ThreadRollbackResponse__UserInput = | { readonly path: string; readonly type: "localAudio" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadRollbackResponse__UserInput = Schema.Union( +export const V2ThreadListResponse__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadRollbackResponse__TextElement).annotate({ + Schema.Array(V2ThreadListResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadListResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadListResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__ImageDetail, Schema.Null]), - ), + detail: Schema.optionalKey(Schema.Union([V2ThreadListResponse__ImageDetail, Schema.Null])), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), }).annotate({ title: "LocalImageUserInput" }), @@ -17677,293 +31579,73 @@ export const V2ThreadRollbackResponse__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadListResponse__UserInput" }); -export type V2ThreadRollbackResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" - | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadRollbackResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadRollbackResponse__ThreadId; - }; - } - | { readonly other: string }; -export const V2ThreadRollbackResponse__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([V2ThreadRollbackResponse__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: V2ThreadRollbackResponse__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), - ], - { mode: "oneOf" }, -); +export type V2ThreadListResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadListResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadListResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadListResponse__FunctionCallOutputBody" }); -export type V2ThreadSettingsUpdatedNotification__SandboxPolicy = - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const V2ThreadSettingsUpdatedNotification__SandboxPolicy = Schema.Union( +export type V2ThreadMetadataUpdateResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadMetadataUpdateResponse__SubAgentSource }; +export const V2ThreadMetadataUpdateResponse__SessionSource = Schema.Union( [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(V2ThreadSettingsUpdatedNotification__AbsolutePathBuf).annotate({ - default: [], - }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadMetadataUpdateResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", + }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__SessionSource" }); -export type V2ThreadSettingsUpdatedNotification__Settings = { - readonly developer_instructions?: string | null; - readonly model: string; - readonly reasoning_effort?: V2ThreadSettingsUpdatedNotification__ReasoningEffort | null; +export type V2ThreadMetadataUpdateResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadMetadataUpdateResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadMetadataUpdateResponse__MisalignmentErrorDetails | null; }; -export const V2ThreadSettingsUpdatedNotification__Settings = Schema.Struct({ - developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - model: Schema.String, - reasoning_effort: Schema.optionalKey( - Schema.Union([V2ThreadSettingsUpdatedNotification__ReasoningEffort, Schema.Null]), +export const V2ThreadMetadataUpdateResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__CodexErrorInfo, Schema.Null]), ), -}).annotate({ description: "Settings for a collaboration mode." }); - -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; -}; -export const V2ThreadStartedNotification__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadStartedNotification__CollabAgentStatus, -}); - -export type V2ThreadStartedNotification__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const V2ThreadStartedNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadStartedNotification__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); - -export type V2ThreadStartedNotification__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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: V2ThreadStartedNotification__NonSteerableTurnKind; - }; - }; -export const V2ThreadStartedNotification__CodexErrorInfo = Schema.Union( - [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - 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.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadStartedNotification__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ description: - "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`.", + "Optional public explanation and continuation instruction for a misalignment block.", }), - ], - { mode: "oneOf" }, -).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 V2ThreadStartedNotification__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadStartedNotification__PatchChangeKind; - readonly path: string; -}; -export const V2ThreadStartedNotification__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadStartedNotification__PatchChangeKind, - path: Schema.String, -}); + ), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__TurnError" }); -export type V2ThreadStartedNotification__UserInput = +export type V2ThreadMetadataUpdateResponse__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2ThreadStartedNotification__ImageDetail | null; + readonly detail?: V2ThreadMetadataUpdateResponse__ImageDetail | null; readonly type: "image"; readonly url: string; } | { - readonly detail?: V2ThreadStartedNotification__ImageDetail | null; + readonly detail?: V2ThreadMetadataUpdateResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadMetadataUpdateResponse__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -17971,28 +31653,37 @@ export type V2ThreadStartedNotification__UserInput = | { readonly path: string; readonly type: "localAudio" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadStartedNotification__UserInput = Schema.Union( +export const V2ThreadMetadataUpdateResponse__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadStartedNotification__TextElement).annotate({ + Schema.Array(V2ThreadMetadataUpdateResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ detail: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__ImageDetail, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__ImageDetail, Schema.Null]), ), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), @@ -18002,250 +31693,88 @@ export const V2ThreadStartedNotification__UserInput = Schema.Union( url: Schema.String, }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - path: Schema.String, - type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), - }).annotate({ title: "LocalAudioUserInput" }), - 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 V2ThreadStartedNotification__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" - | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadStartedNotification__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadStartedNotification__ThreadId; - }; - } - | { readonly other: string }; -export const V2ThreadStartedNotification__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([V2ThreadStartedNotification__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: V2ThreadStartedNotification__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), - ], - { 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__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadStartResponse__CollabAgentStatus; -}; -export const V2ThreadStartResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadStartResponse__CollabAgentStatus, -}); - -export type V2ThreadStartResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const V2ThreadStartResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadStartResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); - -export type V2ThreadStartResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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: V2ThreadStartResponse__NonSteerableTurnKind; - }; - }; -export const V2ThreadStartResponse__CodexErrorInfo = Schema.Union( - [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - 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.", - }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadStartResponse__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", + 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" }, +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__UserInput" }); + +export type V2ThreadMetadataUpdateResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadMetadataUpdateResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadMetadataUpdateResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadMetadataUpdateResponse__FunctionCallOutputBody" }); + +export type V2ThreadReadResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadReadResponse__SubAgentSource }; +export const V2ThreadReadResponse__SessionSource = Schema.Union( + [ + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadReadResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", }), ], { mode: "oneOf" }, -).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.", -}); +).annotate({ identifier: "V2ThreadReadResponse__SessionSource" }); -export type V2ThreadStartResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadStartResponse__PatchChangeKind; - readonly path: string; +export type V2ThreadReadResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadReadResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadReadResponse__MisalignmentErrorDetails | null; }; -export const V2ThreadStartResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadStartResponse__PatchChangeKind, - path: Schema.String, -}); +export const V2ThreadReadResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadReadResponse__TurnError" }); -export type V2ThreadStartResponse__UserInput = +export type V2ThreadReadResponse__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2ThreadStartResponse__ImageDetail | null; + readonly detail?: V2ThreadReadResponse__ImageDetail | null; readonly type: "image"; readonly url: string; } | { - readonly detail?: V2ThreadStartResponse__ImageDetail | null; + readonly detail?: V2ThreadReadResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadReadResponse__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -18253,25 +31782,32 @@ export type V2ThreadStartResponse__UserInput = | { readonly path: string; readonly type: "localAudio" } | { readonly name: string; readonly path: string; readonly type: "skill" } | { readonly name: string; readonly path: string; readonly type: "mention" }; -export const V2ThreadStartResponse__UserInput = Schema.Union( +export const V2ThreadReadResponse__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadStartResponse__TextElement).annotate({ + Schema.Array(V2ThreadReadResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__ImageDetail, Schema.Null])), + detail: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__ImageDetail, Schema.Null])), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), }).annotate({ title: "LocalImageUserInput" }), @@ -18295,275 +31831,359 @@ export const V2ThreadStartResponse__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadReadResponse__UserInput" }); -export type V2ThreadStartResponse__SubAgentSource = - | "review" - | "compact" - | "memory_consolidation" - | { - readonly thread_spawn: { - readonly agent_nickname?: string | null; - readonly agent_path?: V2ThreadStartResponse__AgentPath | null; - readonly agent_role?: string | null; - readonly depth: number; - readonly parent_thread_id: V2ThreadStartResponse__ThreadId; - }; - } - | { readonly other: string }; -export const V2ThreadStartResponse__SubAgentSource = Schema.Union( +export type V2ThreadReadResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadReadResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadReadResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadReadResponse__FunctionCallOutputBody" }); + +export type V2ThreadResumeParams__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadResumeParams__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadResumeParams__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadResumeParams__FunctionCallOutputBody" }); + +export type V2ThreadResumeResponse__CollaborationMode = { + readonly mode: V2ThreadResumeResponse__ModeKind; + readonly settings: V2ThreadResumeResponse__Settings; +}; +export const V2ThreadResumeResponse__CollaborationMode = Schema.Struct({ + mode: V2ThreadResumeResponse__ModeKind, + settings: V2ThreadResumeResponse__Settings, +}).annotate({ + description: "Collaboration mode for a Codex session.", + identifier: "V2ThreadResumeResponse__CollaborationMode", +}); + +export type V2ThreadResumeResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadResumeResponse__SubAgentSource }; +export const V2ThreadResumeResponse__SessionSource = 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([V2ThreadStartResponse__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: V2ThreadStartResponse__ThreadId, - }), - }).annotate({ title: "ThreadSpawnSubAgentSource" }), - Schema.Struct({ other: Schema.String }).annotate({ title: "OtherSubAgentSource" }), + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadResumeResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", + }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadResumeResponse__SessionSource" }); -export type V2ThreadStatusChangedNotification__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } +export type V2ThreadResumeResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadResumeResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadResumeResponse__MisalignmentErrorDetails | null; +}; +export const V2ThreadResumeResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadResumeResponse__TurnError" }); + +export type V2ThreadResumeResponse__UserInput = | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadStatusChangedNotification__ThreadStatus = Schema.Union( + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; + } + | { + readonly detail?: V2ThreadResumeResponse__ImageDetail | null; + readonly type: "image"; + readonly url: string; + } + | { + readonly detail?: V2ThreadResumeResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadResumeResponse__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2ThreadResumeResponse__UserInput = Schema.Union( [ Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2ThreadResumeResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), + detail: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__ImageDetail, Schema.Null])), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - activeFlags: Schema.Array(V2ThreadStatusChangedNotification__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, -); +).annotate({ identifier: "V2ThreadResumeResponse__UserInput" }); -export type V2ThreadTokenUsageUpdatedNotification__ThreadTokenUsage = { - readonly last: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown; - readonly modelContextWindow?: number | null; - readonly total: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown; +export type V2ThreadResumeResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadResumeResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadResumeResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadResumeResponse__FunctionCallOutputBody" }); + +export type V2ThreadRevertResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadRevertResponse__SubAgentSource }; +export const V2ThreadRevertResponse__SessionSource = Schema.Union( + [ + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadRevertResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", + }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2ThreadRevertResponse__SessionSource" }); + +export type V2ThreadRevertResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadRevertResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadRevertResponse__MisalignmentErrorDetails | null; }; -export const V2ThreadTokenUsageUpdatedNotification__ThreadTokenUsage = Schema.Struct({ - last: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown, - modelContextWindow: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), +export const V2ThreadRevertResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__CodexErrorInfo, Schema.Null]), ), - total: V2ThreadTokenUsageUpdatedNotification__TokenUsageBreakdown, -}); + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadRevertResponse__TurnError" }); -export type V2ThreadUnarchiveResponse__CommandAction = +export type V2ThreadRevertResponse__UserInput = | { - readonly command: string; - readonly name: string; - readonly path: V2ThreadUnarchiveResponse__AbsolutePathBuf; - readonly type: "read"; + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; } - | { 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 detail?: V2ThreadRevertResponse__ImageDetail | null; + readonly type: "image"; + readonly url: string; } - | { readonly command: string; readonly type: "unknown" }; -export const V2ThreadUnarchiveResponse__CommandAction = Schema.Union( + | { + readonly detail?: V2ThreadRevertResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadRevertResponse__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2ThreadRevertResponse__UserInput = Schema.Union( [ Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: V2ThreadUnarchiveResponse__AbsolutePathBuf, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2ThreadRevertResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), + detail: Schema.optionalKey(Schema.Union([V2ThreadRevertResponse__ImageDetail, Schema.Null])), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), 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" }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, -); +).annotate({ identifier: "V2ThreadRevertResponse__UserInput" }); -export type V2ThreadUnarchiveResponse__CollabAgentState = { - readonly message?: string | null; - readonly status: V2ThreadUnarchiveResponse__CollabAgentStatus; -}; -export const V2ThreadUnarchiveResponse__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadUnarchiveResponse__CollabAgentStatus, -}); +export type V2ThreadRevertResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadRevertResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadRevertResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadRevertResponse__FunctionCallOutputBody" }); -export type V2ThreadUnarchiveResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +export type V2ThreadSettingsUpdatedNotification__CollaborationMode = { + readonly mode: V2ThreadSettingsUpdatedNotification__ModeKind; + readonly settings: V2ThreadSettingsUpdatedNotification__Settings; }; -export const V2ThreadUnarchiveResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2ThreadUnarchiveResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), +export const V2ThreadSettingsUpdatedNotification__CollaborationMode = Schema.Struct({ + mode: V2ThreadSettingsUpdatedNotification__ModeKind, + settings: V2ThreadSettingsUpdatedNotification__Settings, +}).annotate({ + description: "Collaboration mode for a Codex session.", + identifier: "V2ThreadSettingsUpdatedNotification__CollaborationMode", }); -export type V2ThreadUnarchiveResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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: V2ThreadUnarchiveResponse__NonSteerableTurnKind; - }; - }; -export const V2ThreadUnarchiveResponse__CodexErrorInfo = Schema.Union( +export type V2ThreadStartedNotification__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadStartedNotification__SubAgentSource }; +export const V2ThreadStartedNotification__SessionSource = Schema.Union( [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - 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.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2ThreadUnarchiveResponse__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadStartedNotification__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", }), ], { mode: "oneOf" }, -).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.", -}); +).annotate({ identifier: "V2ThreadStartedNotification__SessionSource" }); -export type V2ThreadUnarchiveResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2ThreadUnarchiveResponse__PatchChangeKind; - readonly path: string; +export type V2ThreadStartedNotification__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadStartedNotification__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadStartedNotification__MisalignmentErrorDetails | null; }; -export const V2ThreadUnarchiveResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2ThreadUnarchiveResponse__PatchChangeKind, - path: Schema.String, -}); +export const V2ThreadStartedNotification__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadStartedNotification__TurnError" }); -export type V2ThreadUnarchiveResponse__UserInput = +export type V2ThreadStartedNotification__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2ThreadUnarchiveResponse__ImageDetail | null; + readonly detail?: V2ThreadStartedNotification__ImageDetail | null; + readonly type: "image"; + readonly url: string; + } + | { + readonly detail?: V2ThreadStartedNotification__ImageDetail | null; readonly type: "image"; - readonly url: string; + readonly fileId: string; } | { - readonly detail?: V2ThreadUnarchiveResponse__ImageDetail | null; + readonly detail?: V2ThreadStartedNotification__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -18571,28 +32191,37 @@ export type V2ThreadUnarchiveResponse__UserInput = | { readonly path: string; readonly type: "localAudio" } | { 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 V2ThreadStartedNotification__UserInput = Schema.Union( [ Schema.Struct({ text: Schema.String, text_elements: Schema.optionalKey( - Schema.Array(V2ThreadUnarchiveResponse__TextElement).annotate({ + Schema.Array(V2ThreadStartedNotification__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ detail: Schema.optionalKey( - Schema.Union([V2ThreadUnarchiveResponse__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2ThreadUnarchiveResponse__ImageDetail, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__ImageDetail, Schema.Null]), ), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), @@ -18617,239 +32246,306 @@ export const V2ThreadUnarchiveResponse__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadStartedNotification__UserInput" }); -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( +export type V2ThreadStartedNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadStartedNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadStartedNotification__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadStartedNotification__FunctionCallOutputBody" }); + +export type V2ThreadStartResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadStartResponse__SubAgentSource }; +export const V2ThreadStartResponse__SessionSource = 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" }), + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadStartResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", + }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadStartResponse__SessionSource" }); -export type V2TurnCompletedNotification__CommandAction = +export type V2ThreadStartResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadStartResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadStartResponse__MisalignmentErrorDetails | null; +}; +export const V2ThreadStartResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadStartResponse__TurnError" }); + +export type V2ThreadStartResponse__UserInput = | { - readonly command: string; - readonly name: string; - readonly path: V2TurnCompletedNotification__AbsolutePathBuf; - readonly type: "read"; + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; } - | { 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 detail?: V2ThreadStartResponse__ImageDetail | null; + readonly type: "image"; + readonly url: string; } - | { readonly command: string; readonly type: "unknown" }; -export const V2TurnCompletedNotification__CommandAction = Schema.Union( + | { + readonly detail?: V2ThreadStartResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadStartResponse__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2ThreadStartResponse__UserInput = Schema.Union( [ Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: V2TurnCompletedNotification__AbsolutePathBuf, - type: Schema.Literal("read").annotate({ title: "ReadCommandActionType" }), - }).annotate({ title: "ReadCommandAction" }), + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2ThreadStartResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - command: Schema.String, - path: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("listFiles").annotate({ title: "ListFilesCommandActionType" }), - }).annotate({ title: "ListFilesCommandAction" }), + detail: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__ImageDetail, Schema.Null])), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), 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" }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - command: Schema.String, - type: Schema.Literal("unknown").annotate({ title: "UnknownCommandActionType" }), - }).annotate({ title: "UnknownCommandAction" }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, -); +).annotate({ identifier: "V2ThreadStartResponse__UserInput" }); -export type V2TurnCompletedNotification__CollabAgentState = { - readonly message?: string | null; - readonly status: V2TurnCompletedNotification__CollabAgentStatus; -}; -export const V2TurnCompletedNotification__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2TurnCompletedNotification__CollabAgentStatus, -}); +export type V2ThreadStartResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadStartResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadStartResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadStartResponse__FunctionCallOutputBody" }); -export type V2TurnCompletedNotification__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +export type V2ThreadTurnsListResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadTurnsListResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadTurnsListResponse__MisalignmentErrorDetails | null; }; -export const V2TurnCompletedNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2TurnCompletedNotification__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); +export const V2ThreadTurnsListResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadTurnsListResponse__TurnError" }); -export type V2TurnCompletedNotification__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "internalServerError" - | "unauthorized" - | "badRequest" - | "threadRollbackFailed" - | "sandboxError" - | "rateLimitExceeded" - | "misalignmentPolicyViolation" - | "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 } } +export type V2ThreadTurnsListResponse__UserInput = | { - readonly activeTurnNotSteerable: { - readonly turnKind: V2TurnCompletedNotification__NonSteerableTurnKind; - }; - }; -export const V2TurnCompletedNotification__CodexErrorInfo = Schema.Union( + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; + } + | { + readonly detail?: V2ThreadTurnsListResponse__ImageDetail | null; + readonly type: "image"; + readonly url: string; + } + | { + readonly detail?: V2ThreadTurnsListResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadTurnsListResponse__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2ThreadTurnsListResponse__UserInput = Schema.Union( [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "internalServerError", - "unauthorized", - "badRequest", - "threadRollbackFailed", - "sandboxError", - "rateLimitExceeded", - "misalignmentPolicyViolation", - "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, - ]), + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2ThreadTurnsListResponse__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__ImageDetail, 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, - ]), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__ImageDetail, Schema.Null]), ), - }), - }).annotate({ - title: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), + detail: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__ImageDetail, Schema.Null]), + ), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), 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.", - }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2TurnCompletedNotification__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, +).annotate({ identifier: "V2ThreadTurnsListResponse__UserInput" }); + +export type V2ThreadTurnsListResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadTurnsListResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadTurnsListResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadTurnsListResponse__FunctionCallOutputBody" }); + +export type V2ThreadUnarchiveResponse__SessionSource = + | "cli" + | "vscode" + | "exec" + | "appServer" + | "unknown" + | { readonly custom: string } + | { readonly subAgent: V2ThreadUnarchiveResponse__SubAgentSource }; +export const V2ThreadUnarchiveResponse__SessionSource = Schema.Union( + [ + Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), + Schema.Struct({ subAgent: V2ThreadUnarchiveResponse__SubAgentSource }).annotate({ + title: "SubAgentSessionSource", }), ], { mode: "oneOf" }, -).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.", -}); +).annotate({ identifier: "V2ThreadUnarchiveResponse__SessionSource" }); -export type V2TurnCompletedNotification__FileUpdateChange = { - readonly diff: string; - readonly kind: V2TurnCompletedNotification__PatchChangeKind; - readonly path: string; +export type V2ThreadUnarchiveResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2ThreadUnarchiveResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2ThreadUnarchiveResponse__MisalignmentErrorDetails | null; }; -export const V2TurnCompletedNotification__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2TurnCompletedNotification__PatchChangeKind, - path: Schema.String, -}); +export const V2ThreadUnarchiveResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__TurnError" }); -export type V2TurnCompletedNotification__UserInput = +export type V2ThreadUnarchiveResponse__UserInput = | { readonly text: string; - readonly text_elements?: ReadonlyArray; + readonly text_elements?: ReadonlyArray; readonly type: "text"; } | { - readonly detail?: V2TurnCompletedNotification__ImageDetail | null; + readonly detail?: V2ThreadUnarchiveResponse__ImageDetail | null; readonly type: "image"; readonly url: string; } | { - readonly detail?: V2TurnCompletedNotification__ImageDetail | null; + readonly detail?: V2ThreadUnarchiveResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2ThreadUnarchiveResponse__ImageDetail | null; readonly path: string; readonly type: "localImage"; } @@ -18857,28 +32553,37 @@ export type V2TurnCompletedNotification__UserInput = | { readonly path: string; readonly type: "localAudio" } | { 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: [], }), ), type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), }).annotate({ title: "TextUserInput" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ detail: Schema.optionalKey( - Schema.Union([V2TurnCompletedNotification__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2TurnCompletedNotification__ImageDetail, Schema.Null]), + Schema.Union([V2ThreadUnarchiveResponse__ImageDetail, Schema.Null]), ), path: Schema.String, type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), @@ -18903,197 +32608,145 @@ export const V2TurnCompletedNotification__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadUnarchiveResponse__UserInput" }); -export type V2TurnPlanUpdatedNotification__TurnPlanStep = { - readonly status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus; - readonly step: string; +export type V2ThreadUnarchiveResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2ThreadUnarchiveResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2ThreadUnarchiveResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2ThreadUnarchiveResponse__FunctionCallOutputBody" }); + +export type V2TurnCompletedNotification__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2TurnCompletedNotification__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2TurnCompletedNotification__MisalignmentErrorDetails | null; }; -export const V2TurnPlanUpdatedNotification__TurnPlanStep = Schema.Struct({ - status: V2TurnPlanUpdatedNotification__TurnPlanStepStatus, - step: Schema.String, -}); +export const V2TurnCompletedNotification__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2TurnCompletedNotification__TurnError" }); -export type V2TurnStartedNotification__CommandAction = +export type V2TurnCompletedNotification__UserInput = | { - readonly command: string; - readonly name: string; - readonly path: V2TurnStartedNotification__AbsolutePathBuf; - readonly type: "read"; + readonly text: string; + readonly text_elements?: ReadonlyArray; + readonly type: "text"; } - | { 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 detail?: V2TurnCompletedNotification__ImageDetail | null; + readonly type: "image"; + readonly url: string; } - | { readonly command: string; readonly type: "unknown" }; -export const V2TurnStartedNotification__CommandAction = Schema.Union( - [ - Schema.Struct({ - command: Schema.String, - name: Schema.String, - path: V2TurnStartedNotification__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 V2TurnStartedNotification__CollabAgentState = { - readonly message?: string | null; - readonly status: V2TurnStartedNotification__CollabAgentStatus; -}; -export const V2TurnStartedNotification__CollabAgentState = Schema.Struct({ - message: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2TurnStartedNotification__CollabAgentStatus, -}); - -export type V2TurnStartedNotification__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; -}; -export const V2TurnStartedNotification__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2TurnStartedNotification__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); - -export type V2TurnStartedNotification__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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: V2TurnStartedNotification__NonSteerableTurnKind; - }; - }; -export const V2TurnStartedNotification__CodexErrorInfo = Schema.Union( + readonly detail?: V2TurnCompletedNotification__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } + | { + readonly detail?: V2TurnCompletedNotification__ImageDetail | null; + readonly path: string; + readonly type: "localImage"; + } + | { readonly type: "audio"; readonly url: string } + | { readonly path: string; readonly type: "localAudio" } + | { readonly name: string; readonly path: string; readonly type: "skill" } + | { readonly name: string; readonly path: string; readonly type: "mention" }; +export const V2TurnCompletedNotification__UserInput = Schema.Union( [ - Schema.Literals([ - "contextWindowExceeded", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "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, - ]), + text: Schema.String, + text_elements: Schema.optionalKey( + Schema.Array(V2TurnCompletedNotification__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.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__ImageDetail, 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, - ]), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__ImageDetail, Schema.Null]), ), - }), - }).annotate({ - title: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), + detail: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__ImageDetail, Schema.Null]), + ), + path: Schema.String, + type: Schema.Literal("localImage").annotate({ title: "LocalImageUserInputType" }), + }).annotate({ title: "LocalImageUserInput" }), 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.", - }), + type: Schema.Literal("audio").annotate({ title: "AudioUserInputType" }), + url: Schema.String, + }).annotate({ title: "AudioUserInput" }), Schema.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2TurnStartedNotification__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", - description: - "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`.", - }), + path: Schema.String, + type: Schema.Literal("localAudio").annotate({ title: "LocalAudioUserInputType" }), + }).annotate({ title: "LocalAudioUserInput" }), + 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" }, -).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.", -}); +).annotate({ identifier: "V2TurnCompletedNotification__UserInput" }); -export type V2TurnStartedNotification__FileUpdateChange = { - readonly diff: string; - readonly kind: V2TurnStartedNotification__PatchChangeKind; - readonly path: string; +export type V2TurnCompletedNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2TurnCompletedNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2TurnCompletedNotification__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2TurnCompletedNotification__FunctionCallOutputBody" }); + +export type V2TurnStartedNotification__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2TurnStartedNotification__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2TurnStartedNotification__MisalignmentErrorDetails | null; }; -export const V2TurnStartedNotification__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2TurnStartedNotification__PatchChangeKind, - path: Schema.String, -}); +export const V2TurnStartedNotification__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__MisalignmentErrorDetails, Schema.Null]).annotate({ + description: + "Optional public explanation and continuation instruction for a misalignment block.", + }), + ), +}).annotate({ identifier: "V2TurnStartedNotification__TurnError" }); export type V2TurnStartedNotification__UserInput = | { @@ -19106,6 +32759,11 @@ export type V2TurnStartedNotification__UserInput = readonly type: "image"; readonly url: string; } + | { + readonly detail?: V2TurnStartedNotification__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } | { readonly detail?: V2TurnStartedNotification__ImageDetail | null; readonly path: string; @@ -19127,13 +32785,22 @@ export const V2TurnStartedNotification__UserInput = Schema.Union( ), type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), }).annotate({ title: "TextUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey( - Schema.Union([V2TurnStartedNotification__ImageDetail, Schema.Null]), - ), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__ImageDetail, Schema.Null]), + ), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ detail: Schema.optionalKey( Schema.Union([V2TurnStartedNotification__ImageDetail, Schema.Null]), @@ -19161,63 +32828,15 @@ export const V2TurnStartedNotification__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); - -export type V2TurnStartParams__SandboxPolicy = - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - 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({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(V2TurnStartParams__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnStartedNotification__UserInput" }); -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 V2TurnStartedNotification__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2TurnStartedNotification__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2TurnStartedNotification__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2TurnStartedNotification__FunctionCallOutputBody" }); export type V2TurnStartParams__UserInput = | { @@ -19230,6 +32849,11 @@ export type V2TurnStartParams__UserInput = readonly type: "image"; readonly url: string; } + | { + readonly detail?: V2TurnStartParams__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } | { readonly detail?: V2TurnStartParams__ImageDetail | null; readonly path: string; @@ -19251,11 +32875,18 @@ export const V2TurnStartParams__UserInput = Schema.Union( ), type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), }).annotate({ title: "TextUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2TurnStartParams__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartParams__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartParams__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ detail: Schema.optionalKey(Schema.Union([V2TurnStartParams__ImageDetail, Schema.Null])), path: Schema.String, @@ -19281,188 +32912,35 @@ export const V2TurnStartParams__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); - -export type V2TurnStartResponse__CommandAction = - | { - readonly command: string; - readonly name: string; - readonly path: V2TurnStartResponse__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 V2TurnStartResponse__CommandAction = Schema.Union( - [ - Schema.Struct({ - 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" }, -); +).annotate({ identifier: "V2TurnStartParams__UserInput" }); -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 V2TurnStartParams__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2TurnStartParams__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2TurnStartParams__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2TurnStartParams__FunctionCallOutputBody" }); -export type V2TurnStartResponse__MemoryCitation = { - readonly entries: ReadonlyArray; - readonly threadIds: ReadonlyArray; +export type V2TurnStartResponse__TurnError = { + readonly additionalDetails?: string | null; + readonly codexErrorInfo?: V2TurnStartResponse__CodexErrorInfo | null; + readonly message: string; + readonly misalignment?: V2TurnStartResponse__MisalignmentErrorDetails | null; }; -export const V2TurnStartResponse__MemoryCitation = Schema.Struct({ - entries: Schema.Array(V2TurnStartResponse__MemoryCitationEntry), - threadIds: Schema.Array(Schema.String), -}); - -export type V2TurnStartResponse__CodexErrorInfo = - | "contextWindowExceeded" - | "sessionBudgetExceeded" - | "usageLimitExceeded" - | "serverOverloaded" - | "cyberPolicy" - | "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", - "sessionBudgetExceeded", - "usageLimitExceeded", - "serverOverloaded", - "cyberPolicy", - "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: "ResponseStreamConnectionFailedCodexErrorInfo", - description: "Failed to connect to the response SSE stream.", - }), - Schema.Struct({ - responseStreamDisconnected: Schema.Struct({ - httpStatusCode: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - }), - }).annotate({ - title: "ResponseStreamDisconnectedCodexErrorInfo", - description: - "The response SSE stream disconnected in the middle of a turn before completion.", - }), - 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.Struct({ - activeTurnNotSteerable: Schema.Struct({ - turnKind: V2TurnStartResponse__NonSteerableTurnKind, - }), - }).annotate({ - title: "ActiveTurnNotSteerableCodexErrorInfo", +export const V2TurnStartResponse__TurnError = Schema.Struct({ + additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + codexErrorInfo: Schema.optionalKey( + Schema.Union([V2TurnStartResponse__CodexErrorInfo, Schema.Null]), + ), + message: Schema.String, + misalignment: Schema.optionalKey( + Schema.Union([V2TurnStartResponse__MisalignmentErrorDetails, Schema.Null]).annotate({ description: - "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`.", + "Optional public explanation and continuation instruction for a misalignment block.", }), - ], - { mode: "oneOf" }, -).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 V2TurnStartResponse__FileUpdateChange = { - readonly diff: string; - readonly kind: V2TurnStartResponse__PatchChangeKind; - readonly path: string; -}; -export const V2TurnStartResponse__FileUpdateChange = Schema.Struct({ - diff: Schema.String, - kind: V2TurnStartResponse__PatchChangeKind, - path: Schema.String, -}); + ), +}).annotate({ identifier: "V2TurnStartResponse__TurnError" }); export type V2TurnStartResponse__UserInput = | { @@ -19475,6 +32953,11 @@ export type V2TurnStartResponse__UserInput = readonly type: "image"; readonly url: string; } + | { + readonly detail?: V2TurnStartResponse__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } | { readonly detail?: V2TurnStartResponse__ImageDetail | null; readonly path: string; @@ -19496,11 +32979,18 @@ export const V2TurnStartResponse__UserInput = Schema.Union( ), type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), }).annotate({ title: "TextUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2TurnStartResponse__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnStartResponse__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ detail: Schema.optionalKey(Schema.Union([V2TurnStartResponse__ImageDetail, Schema.Null])), path: Schema.String, @@ -19526,7 +33016,15 @@ export const V2TurnStartResponse__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnStartResponse__UserInput" }); + +export type V2TurnStartResponse__FunctionCallOutputBody = + | string + | ReadonlyArray; +export const V2TurnStartResponse__FunctionCallOutputBody = Schema.Union([ + Schema.String, + Schema.Array(V2TurnStartResponse__FunctionCallOutputContentItem), +]).annotate({ identifier: "V2TurnStartResponse__FunctionCallOutputBody" }); export type V2TurnSteerParams__UserInput = | { @@ -19539,6 +33037,11 @@ export type V2TurnSteerParams__UserInput = readonly type: "image"; readonly url: string; } + | { + readonly detail?: V2TurnSteerParams__ImageDetail | null; + readonly type: "image"; + readonly fileId: string; + } | { readonly detail?: V2TurnSteerParams__ImageDetail | null; readonly path: string; @@ -19560,11 +33063,18 @@ export const V2TurnSteerParams__UserInput = Schema.Union( ), type: Schema.Literal("text").annotate({ title: "TextUserInputType" }), }).annotate({ title: "TextUserInput" }), - Schema.Struct({ - detail: Schema.optionalKey(Schema.Union([V2TurnSteerParams__ImageDetail, Schema.Null])), - type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), - url: Schema.String, - }).annotate({ title: "ImageUserInput" }), + Schema.Union([ + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnSteerParams__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + url: Schema.String, + }).annotate({ title: "UrlUserInput" }), + Schema.Struct({ + detail: Schema.optionalKey(Schema.Union([V2TurnSteerParams__ImageDetail, Schema.Null])), + type: Schema.Literal("image").annotate({ title: "ImageUserInputType" }), + fileId: Schema.String, + }).annotate({ title: "FileIdUserInput" }), + ]).annotate({ title: "ImageUserInput" }), Schema.Struct({ detail: Schema.optionalKey(Schema.Union([V2TurnSteerParams__ImageDetail, Schema.Null])), path: Schema.String, @@ -19590,848 +33100,1268 @@ export const V2TurnSteerParams__UserInput = Schema.Union( }).annotate({ title: "MentionUserInput" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnSteerParams__UserInput" }); -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__TurnSteerParams = { + readonly clientUserMessageId?: string | null; + readonly expectedTurnId: string; + readonly input: ReadonlyArray; + readonly threadId: string; +}; +export const ClientRequest__TurnSteerParams = Schema.Struct({ + clientUserMessageId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + expectedTurnId: Schema.String.annotate({ + description: + "Required active turn id precondition. The request fails when it does not match the currently active turn.", + }), + input: Schema.Array(ClientRequest__UserInput), + threadId: Schema.String, +}).annotate({ identifier: "ClientRequest__TurnSteerParams" }); -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 ClientRequest__TurnToolOutput = { + readonly name: string; + readonly namespace?: string | null; + readonly output: ClientRequest__FunctionCallOutputBody; +}; +export const ClientRequest__TurnToolOutput = Schema.Struct({ + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: ClientRequest__FunctionCallOutputBody, +}).annotate({ identifier: "ClientRequest__TurnToolOutput" }); + +export type ClientRequest__ExternalAgentConfigImportParams = { + readonly migrationItems: ReadonlyArray; + readonly migrationSource?: string | null; + readonly providerId?: string | null; + readonly source?: string | null; }; -export const ClientRequest__CommandExecParams = Schema.Struct({ - command: Schema.Array(Schema.String).annotate({ - description: "Command argv vector. Empty arrays are rejected.", - }), - cwd: Schema.optionalKey( +export const ClientRequest__ExternalAgentConfigImportParams = Schema.Struct({ + migrationItems: Schema.Array(ClientRequest__ExternalAgentConfigMigrationItem), + migrationSource: 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.", + "Migration-source selector used to produce the migration items. Pass the same value to detection and import; missing or unrecognized values use the default source.", }), Schema.Null, ]), ), - outputBytesCap: Schema.optionalKey( + providerId: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ + Schema.String.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)), + "Opaque provider identifier supplied by the caller for analytics attribution and import history display. This does not select the migration source.", + }), Schema.Null, ]), ), - processId: Schema.optionalKey( + source: 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.", + description: "Optional identifier for the product that initiated the import.", }), 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. Cannot be combined with `permissionProfile`.", - }), +}).annotate({ identifier: "ClientRequest__ExternalAgentConfigImportParams" }); + +export type ClientRequest__ExternalAgentConfigImportHistoryRecordParams = { + readonly itemTypeResults: ReadonlyArray; + readonly providerId: string; +}; +export const ClientRequest__ExternalAgentConfigImportHistoryRecordParams = Schema.Struct({ + itemTypeResults: Schema.Array( + ClientRequest__ExternalAgentConfigImportHistoryRecordTypeResultParams, + ).annotate({ description: "Completed results grouped by imported item type." }), + providerId: Schema.String.annotate({ + description: "Opaque provider identifier for the externally completed import.", + }), +}).annotate({ identifier: "ClientRequest__ExternalAgentConfigImportHistoryRecordParams" }); + +export type CommandExecutionRequestApprovalParams__FileSystemSandboxEntry = { + readonly access: CommandExecutionRequestApprovalParams__FileSystemAccessMode; + readonly path: CommandExecutionRequestApprovalParams__FileSystemPath; +}; +export const CommandExecutionRequestApprovalParams__FileSystemSandboxEntry = Schema.Struct({ + access: CommandExecutionRequestApprovalParams__FileSystemAccessMode, + path: CommandExecutionRequestApprovalParams__FileSystemPath, +}).annotate({ identifier: "CommandExecutionRequestApprovalParams__FileSystemSandboxEntry" }); + +export type McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema = + | McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema + | McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema; +export const McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema = Schema.Union([ + McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema, + McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema, +]).annotate({ + identifier: "McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema", +}); + +export type PermissionsRequestApprovalParams__FileSystemSandboxEntry = { + readonly access: PermissionsRequestApprovalParams__FileSystemAccessMode; + readonly path: PermissionsRequestApprovalParams__FileSystemPath; +}; +export const PermissionsRequestApprovalParams__FileSystemSandboxEntry = Schema.Struct({ + access: PermissionsRequestApprovalParams__FileSystemAccessMode, + path: PermissionsRequestApprovalParams__FileSystemPath, +}).annotate({ identifier: "PermissionsRequestApprovalParams__FileSystemSandboxEntry" }); + +export type PermissionsRequestApprovalResponse__FileSystemSandboxEntry = { + readonly access: PermissionsRequestApprovalResponse__FileSystemAccessMode; + readonly path: PermissionsRequestApprovalResponse__FileSystemPath; +}; +export const PermissionsRequestApprovalResponse__FileSystemSandboxEntry = Schema.Struct({ + access: PermissionsRequestApprovalResponse__FileSystemAccessMode, + path: PermissionsRequestApprovalResponse__FileSystemPath, +}).annotate({ identifier: "PermissionsRequestApprovalResponse__FileSystemSandboxEntry" }); + +export type ServerNotification__ErrorNotification = { + readonly error: ServerNotification__TurnError; + readonly threadId: string; + readonly turnId: string; + readonly willRetry: boolean; +}; +export const ServerNotification__ErrorNotification = Schema.Struct({ + error: ServerNotification__TurnError, + threadId: Schema.String, + turnId: Schema.String, + willRetry: Schema.Boolean, +}).annotate({ identifier: "ServerNotification__ErrorNotification" }); + +export type ServerNotification__ThreadSettings = { + readonly activePermissionProfile?: ServerNotification__ActivePermissionProfile | null; + readonly approvalPolicy: ServerNotification__AskForApproval; + readonly approvalsReviewer: ServerNotification__ApprovalsReviewer; + readonly collaborationMode: ServerNotification__CollaborationMode; + readonly cwd: ServerNotification__AbsolutePathBuf; + readonly disabledPluginIds?: ReadonlyArray; + readonly effort?: ServerNotification__ReasoningEffort | null; + readonly model: string; + readonly modelProvider: string; + readonly personality?: ServerNotification__Personality | null; + readonly sandboxPolicy: ServerNotification__SandboxPolicy; + readonly serviceTier?: string | null; + readonly summary?: ServerNotification__ReasoningSummary | null; +}; +export const ServerNotification__ThreadSettings = Schema.Struct({ + activePermissionProfile: Schema.optionalKey( + Schema.Union([ServerNotification__ActivePermissionProfile, Schema.Null]), ), - size: Schema.optionalKey( - Schema.Union([ClientRequest__CommandExecTerminalSize, Schema.Null]).annotate({ - description: "Optional initial PTY size in character cells. Only valid when `tty` is true.", + approvalPolicy: ServerNotification__AskForApproval, + approvalsReviewer: ServerNotification__ApprovalsReviewer, + collaborationMode: ServerNotification__CollaborationMode, + cwd: ServerNotification__AbsolutePathBuf, + disabledPluginIds: Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + description: "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.", + default: [], }), ), - streamStdin: Schema.optionalKey( - Schema.Boolean.annotate({ + effort: Schema.optionalKey(Schema.Union([ServerNotification__ReasoningEffort, Schema.Null])), + model: Schema.String, + modelProvider: Schema.String, + personality: Schema.optionalKey( + Schema.Union([ServerNotification__Personality, Schema.Null]).annotate({ description: - "Allow follow-up `command/exec/write` requests to write stdin bytes.\n\nRequires a client-supplied `processId`.", + "@deprecated Reports the saved setting; `friendly` and `pragmatic` no longer select a style.", }), ), - streamStdoutStderr: Schema.optionalKey( - Schema.Boolean.annotate({ + sandboxPolicy: ServerNotification__SandboxPolicy, + serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + summary: Schema.optionalKey(Schema.Union([ServerNotification__ReasoningSummary, Schema.Null])), +}).annotate({ identifier: "ServerNotification__ThreadSettings" }); + +export type ServerNotification__ThreadItem = + | { + readonly clientId?: string | null; + readonly content: ReadonlyArray; + readonly id: string; + readonly type: "userMessage"; + } + | { + readonly fragments: ReadonlyArray; + readonly id: string; + readonly type: "hookPrompt"; + } + | { + readonly delivery?: ServerNotification__AgentMessageDelivery | null; + readonly id: string; + readonly memoryCitation?: ServerNotification__MemoryCitation | null; + readonly phase?: ServerNotification__MessagePhase | null; + readonly questions?: ReadonlyArray | null; + readonly text: string; + readonly type: "agentMessage"; + } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: ServerNotification__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } + | { 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: ServerNotification__LegacyAppPathString; + readonly durationMs?: number | null; + readonly exitCode?: number | null; + readonly id: string; + readonly pluginId?: string | null; + readonly processId?: string | null; + readonly scriptPath?: string | null; + readonly source?: ServerNotification__CommandExecutionSource; + readonly status: ServerNotification__CommandExecutionStatus; + readonly type: "commandExecution"; + } + | { + readonly changes: ReadonlyArray; + readonly id: string; + readonly status: ServerNotification__PatchApplyStatus; + readonly type: "fileChange"; + } + | { + readonly appContext?: ServerNotification__McpToolCallAppContext | null; + readonly arguments: Schema.Json; + readonly durationMs?: number | null; + readonly error?: ServerNotification__McpToolCallError | null; + readonly id: string; + readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: ServerNotification__McpAppUi | null; + readonly pluginId?: string | null; + readonly readOnlyHint?: boolean | null; + readonly result?: ServerNotification__McpToolCallResult | null; + readonly server: string; + readonly status: ServerNotification__McpToolCallStatus; + readonly tool: string; + readonly type: "mcpToolCall"; + } + | { + readonly arguments: Schema.Json; + 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; + readonly type: "dynamicToolCall"; + } + | { + readonly agentsStates: { readonly [x: string]: ServerNotification__CollabAgentState }; + readonly id: string; + readonly model?: string | null; + readonly prompt?: string | null; + readonly reasoningEffort?: ServerNotification__ReasoningEffort | null; + readonly receiverThreadIds: ReadonlyArray; + readonly senderThreadId: string; + readonly status: ServerNotification__CollabAgentToolCallStatus; + readonly tool: ServerNotification__CollabAgentTool; + readonly type: "collabAgentToolCall"; + } + | { + readonly agentPath: string; + readonly agentThreadId: string; + readonly id: string; + readonly kind: ServerNotification__SubAgentActivityKind; + readonly type: "subAgentActivity"; + } + | { + readonly action?: ServerNotification__WebSearchAction | null; + readonly id: string; + readonly query: string; + readonly results?: ReadonlyArray | null; + readonly type: "webSearch"; + } + | { + readonly id: string; + readonly path: ServerNotification__LegacyAppPathString; + readonly type: "imageView"; + } + | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } + | { + readonly failure?: ServerNotification__ImageGenerationFailure | null; + readonly id: string; + readonly result: string; + readonly revisedPrompt?: string | null; + readonly savedPath?: ServerNotification__AbsolutePathBuf | null; + readonly status: string; + readonly transparentBackground?: boolean | null; + 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 ServerNotification__ThreadItem = Schema.Union( + [ + Schema.Struct({ + clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + content: Schema.Array(ServerNotification__UserInput), + id: Schema.String, + type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), + }).annotate({ title: "UserMessageThreadItem" }), + Schema.Struct({ + fragments: Schema.Array(ServerNotification__HookPromptFragment), + id: Schema.String, + type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), + }).annotate({ title: "HookPromptThreadItem" }), + Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([ServerNotification__AgentMessageDelivery, Schema.Null]), + ), + id: Schema.String, + memoryCitation: Schema.optionalKey( + Schema.Union([ServerNotification__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey(Schema.Union([ServerNotification__MessagePhase, Schema.Null])), + questions: Schema.optionalKey( + Schema.Union([Schema.Array(ServerNotification__AsyncUserInputQuestion), Schema.Null]), + ), + text: Schema.String, + type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), + }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: ServerNotification__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), + Schema.Struct({ + id: Schema.String, + text: Schema.String, + type: Schema.Literal("plan").annotate({ title: "PlanThreadItemType" }), + }).annotate({ + title: "PlanThreadItem", description: - "Stream stdout/stderr via `command/exec/outputDelta` notifications.\n\nStreamed bytes are not duplicated into the final response and require a client-supplied `processId`.", + "EXPERIMENTAL - proposed plan item content. The completed plan item is authoritative and may not match the concatenation of `PlanDelta` text.", }), - ), - timeoutMs: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ + 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(ServerNotification__CommandAction).annotate({ description: - "Optional timeout in milliseconds.\n\nWhen omitted, the server default applies. Cannot be combined with `disableTimeout`.", - format: "int64", - }).check(Schema.isInt()), - Schema.Null, - ]), - ), - tty: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "Enable PTY mode.\n\nThis implies `streamStdin` and `streamStdoutStderr`.", + "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.suspend( + (): Schema.Codec => + ServerNotification__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The duration of the command execution in milliseconds.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + exitCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The command's exit code.", + format: "int32", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), + processId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Identifier for the underlying PTY process (when available).", + }), + Schema.Null, + ]), + ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), + source: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + ServerNotification__CommandExecutionSource, + ).annotate({ default: "agent" }), + ), + status: ServerNotification__CommandExecutionStatus, + type: Schema.Literal("commandExecution").annotate({ + title: "CommandExecutionThreadItemType", + }), + }).annotate({ title: "CommandExecutionThreadItem" }), + Schema.Struct({ + changes: Schema.Array(ServerNotification__FileUpdateChange), + id: Schema.String, + status: ServerNotification__PatchApplyStatus, + type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), + }).annotate({ title: "FileChangeThreadItem" }), + Schema.Struct({ + appContext: Schema.optionalKey( + Schema.Union([ServerNotification__McpToolCallAppContext, Schema.Null]), + ), + arguments: Schema.Json.annotate({ expected: "JSON value" }), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The duration of the MCP tool call in milliseconds.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + error: Schema.optionalKey(Schema.Union([ServerNotification__McpToolCallError, Schema.Null])), + id: Schema.String, + mcpAppResourceUri: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", + }), + Schema.Null, + ]), + ), + mcpAppUi: Schema.optionalKey( + Schema.Union([ServerNotification__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), + pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + result: Schema.optionalKey( + Schema.Union([ServerNotification__McpToolCallResult, Schema.Null]), + ), + server: Schema.String, + status: ServerNotification__McpToolCallStatus, + tool: Schema.String, + type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), + }).annotate({ title: "McpToolCallThreadItem" }), + Schema.Struct({ + arguments: Schema.Json.annotate({ expected: "JSON value" }), + contentItems: Schema.optionalKey( + Schema.Union([ + Schema.Array(ServerNotification__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().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + 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, + type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), + }).annotate({ title: "DynamicToolCallThreadItem" }), + Schema.Struct({ + agentsStates: Schema.Record(Schema.String, ServerNotification__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([ServerNotification__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.suspend( + (): Schema.Codec => + ServerNotification__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + ServerNotification__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), + type: Schema.Literal("collabAgentToolCall").annotate({ + title: "CollabAgentToolCallThreadItemType", + }), + }).annotate({ title: "CollabAgentToolCallThreadItem" }), + Schema.Struct({ + agentPath: Schema.String, + agentThreadId: Schema.String, + id: Schema.String, + kind: ServerNotification__SubAgentActivityKind, + type: Schema.Literal("subAgentActivity").annotate({ + title: "SubAgentActivityThreadItemType", + }), + }).annotate({ title: "SubAgentActivityThreadItem" }), + Schema.Struct({ + action: Schema.optionalKey(Schema.Union([ServerNotification__WebSearchAction, Schema.Null])), + id: Schema.String, + query: Schema.String, + results: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ + description: + "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("webSearch").annotate({ title: "WebSearchThreadItemType" }), + }).annotate({ title: "WebSearchThreadItem" }), + Schema.Struct({ + id: Schema.String, + path: ServerNotification__LegacyAppPathString, + type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), + }).annotate({ title: "ImageViewThreadItem" }), + Schema.Struct({ + durationMs: Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + id: Schema.String, + type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), + }).annotate({ + title: "SleepThreadItem", + description: "Display item emitted by the interruptible `clock.sleep` tool.", }), - ), -}).annotate({ - description: - "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.", -}); + Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([ServerNotification__ImageGenerationFailure, Schema.Null]), + ), + id: Schema.String, + result: Schema.String, + revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + savedPath: Schema.optionalKey( + Schema.Union([ServerNotification__AbsolutePathBuf, Schema.Null]), + ), + status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + 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" }, +).annotate({ identifier: "ServerNotification__ThreadItem" }); -export type ClientRequest__FunctionCallOutputBody = - | string - | ReadonlyArray; -export const ClientRequest__FunctionCallOutputBody = Schema.Union([ - Schema.String, - Schema.Array(ClientRequest__FunctionCallOutputContentItem), -]); +export type ServerNotification__FileSystemSandboxEntry = { + readonly access: ServerNotification__FileSystemAccessMode; + readonly path: ServerNotification__FileSystemPath; +}; +export const ServerNotification__FileSystemSandboxEntry = Schema.Struct({ + access: ServerNotification__FileSystemAccessMode, + path: ServerNotification__FileSystemPath, +}).annotate({ identifier: "ServerNotification__FileSystemSandboxEntry" }); -export type ClientRequest__ConfigBatchWriteParams = { - readonly edits: ReadonlyArray; - readonly expectedVersion?: string | null; - readonly filePath?: string | null; - readonly reloadUserConfig?: boolean; +export type ServerNotification__HookStartedNotification = { + readonly run: ServerNotification__HookRunSummary; + readonly threadId: string; + readonly turnId?: string | null; }; -export const ClientRequest__ConfigBatchWriteParams = Schema.Struct({ - edits: Schema.Array(ClientRequest__ConfigEdit), - expectedVersion: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - filePath: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Path to the config file to write; defaults to the user's `config.toml` when omitted.", - }), - Schema.Null, - ]), - ), - reloadUserConfig: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "When true, hot-reload the updated user config into all loaded threads after writing.", - }), - ), -}); +export const ServerNotification__HookStartedNotification = Schema.Struct({ + run: ServerNotification__HookRunSummary, + threadId: Schema.String, + turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__HookStartedNotification" }); -export type ClientRequest__PluginShareSaveParams = { - readonly discoverability?: ClientRequest__PluginShareDiscoverability | null; - readonly pluginPath: ClientRequest__AbsolutePathBuf; - readonly remotePluginId?: string | null; - readonly shareTargets?: ReadonlyArray | null; +export type ServerNotification__HookCompletedNotification = { + readonly run: ServerNotification__HookRunSummary; + readonly threadId: string; + readonly turnId?: string | null; }; -export const ClientRequest__PluginShareSaveParams = Schema.Struct({ - discoverability: Schema.optionalKey( - Schema.Union([ClientRequest__PluginShareDiscoverability, Schema.Null]), - ), - pluginPath: ClientRequest__AbsolutePathBuf, - remotePluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - shareTargets: Schema.optionalKey( - Schema.Union([Schema.Array(ClientRequest__PluginShareTarget), Schema.Null]), - ), -}); +export const ServerNotification__HookCompletedNotification = Schema.Struct({ + run: ServerNotification__HookRunSummary, + threadId: Schema.String, + turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "ServerNotification__HookCompletedNotification" }); -export type ClientRequest__PluginShareUpdateTargetsParams = { - readonly discoverability: ClientRequest__PluginShareUpdateDiscoverability; - readonly remotePluginId: string; - readonly shareTargets: ReadonlyArray; +export type ServerNotification__AppListUpdatedNotification = { + readonly data: ReadonlyArray; }; -export const ClientRequest__PluginShareUpdateTargetsParams = Schema.Struct({ - discoverability: ClientRequest__PluginShareUpdateDiscoverability, - remotePluginId: Schema.String, - shareTargets: Schema.Array(ClientRequest__PluginShareTarget), +export const ServerNotification__AppListUpdatedNotification = Schema.Struct({ + data: Schema.Array(ServerNotification__AppInfo), +}).annotate({ + description: "EXPERIMENTAL - notification emitted when the app list changes.", + identifier: "ServerNotification__AppListUpdatedNotification", }); -export type ClientRequest__ExternalAgentConfigMigrationItem = { - readonly cwd?: string | null; - readonly description: string; - readonly details?: ClientRequest__MigrationDetails | null; - readonly itemType: ClientRequest__ExternalAgentConfigMigrationItemType; +export type ServerNotification__ExternalAgentConfigImportProgressNotification = { + readonly importId: string; + readonly itemTypeResults: ReadonlyArray; }; -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 const ServerNotification__ExternalAgentConfigImportProgressNotification = Schema.Struct({ + importId: Schema.String, + itemTypeResults: Schema.Array(ServerNotification__ExternalAgentConfigImportTypeResult), +}).annotate({ identifier: "ServerNotification__ExternalAgentConfigImportProgressNotification" }); -export type ClientRequest__TurnStartParams = { - readonly approvalPolicy?: ClientRequest__AskForApproval | null; - readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; - readonly clientUserMessageId?: string | 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?: string | null; - readonly summary?: ClientRequest__ReasoningSummary | null; - readonly threadId: string; +export type ServerNotification__ExternalAgentConfigImportCompletedNotification = { + readonly importId: string; + readonly itemTypeResults: ReadonlyArray; }; -export const ClientRequest__TurnStartParams = Schema.Struct({ - approvalPolicy: Schema.optionalKey( - Schema.Union([ClientRequest__AskForApproval, Schema.Null]).annotate({ - description: "Override the approval policy for this turn and subsequent turns.", - }), - ), - approvalsReviewer: Schema.optionalKey( - Schema.Union([ClientRequest__ApprovalsReviewer, Schema.Null]).annotate({ - description: - "Override where approval requests are routed for review on this turn and subsequent turns.", - }), - ), - clientUserMessageId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - cwd: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Override the working directory for this turn and subsequent turns.", - }), - Schema.Null, - ]), - ), - effort: Schema.optionalKey( - Schema.Union([ClientRequest__ReasoningEffort, Schema.Null]).annotate({ - description: "Override the reasoning effort for this turn and subsequent turns.", - }), - ), - input: Schema.Array(ClientRequest__UserInput), - model: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Override the model for this turn and subsequent turns.", - }), - Schema.Null, - ]), +export const ServerNotification__ExternalAgentConfigImportCompletedNotification = Schema.Struct({ + importId: Schema.String, + itemTypeResults: Schema.Array(ServerNotification__ExternalAgentConfigImportTypeResult), +}).annotate({ identifier: "ServerNotification__ExternalAgentConfigImportCompletedNotification" }); + +export type ServerRequest__FileSystemSandboxEntry = { + readonly access: ServerRequest__FileSystemAccessMode; + readonly path: ServerRequest__FileSystemPath; +}; +export const ServerRequest__FileSystemSandboxEntry = Schema.Struct({ + access: ServerRequest__FileSystemAccessMode, + path: ServerRequest__FileSystemPath, +}).annotate({ identifier: "ServerRequest__FileSystemSandboxEntry" }); + +export type ServerRequest__McpElicitationMultiSelectEnumSchema = + | ServerRequest__McpElicitationUntitledMultiSelectEnumSchema + | ServerRequest__McpElicitationTitledMultiSelectEnumSchema; +export const ServerRequest__McpElicitationMultiSelectEnumSchema = Schema.Union([ + ServerRequest__McpElicitationUntitledMultiSelectEnumSchema, + ServerRequest__McpElicitationTitledMultiSelectEnumSchema, +]).annotate({ identifier: "ServerRequest__McpElicitationMultiSelectEnumSchema" }); + +export type V2ConfigReadResponse__ComputerUseConfig = { + readonly default_app_access?: V2ConfigReadResponse__AllowDenyRequirement | null; + readonly macos?: V2ConfigReadResponse__ComputerUseMacosConfig | null; + readonly windows?: V2ConfigReadResponse__ComputerUseWindowsConfig | null; +}; +export const V2ConfigReadResponse__ComputerUseConfig = Schema.Struct({ + default_app_access: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AllowDenyRequirement, Schema.Null]), ), - outputSchema: Schema.optionalKey( - Schema.Unknown.annotate({ - description: - "Optional JSON Schema used to constrain the final assistant message for this turn.", - }), + macos: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__ComputerUseMacosConfig, Schema.Null]), ), - personality: Schema.optionalKey( - Schema.Union([ClientRequest__Personality, Schema.Null]).annotate({ - description: "Override the personality for this turn and subsequent turns.", - }), + windows: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__ComputerUseWindowsConfig, Schema.Null]), ), - sandboxPolicy: Schema.optionalKey( - Schema.Union([ClientRequest__SandboxPolicy, Schema.Null]).annotate({ - description: "Override the sandbox policy for this turn and subsequent turns.", - }), +}).annotate({ identifier: "V2ConfigReadResponse__ComputerUseConfig" }); + +export type V2ConfigRequirementsReadResponse__ComputerUseRequirements = { + readonly allowLockedComputerUse?: boolean | null; + readonly allowPersistentApproval?: boolean | null; + readonly defaultAppAccess?: V2ConfigRequirementsReadResponse__AllowDenyRequirement | null; + readonly macos?: V2ConfigRequirementsReadResponse__ComputerUseMacosRequirements | null; + readonly windows?: V2ConfigRequirementsReadResponse__ComputerUseWindowsRequirements | null; +}; +export const V2ConfigRequirementsReadResponse__ComputerUseRequirements = Schema.Struct({ + allowLockedComputerUse: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + allowPersistentApproval: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + defaultAppAccess: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__AllowDenyRequirement, Schema.Null]), ), - serviceTier: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Override the service tier for this turn and subsequent turns.", - }), - Schema.Null, - ]), + macos: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__ComputerUseMacosRequirements, Schema.Null]), ), - summary: Schema.optionalKey( - Schema.Union([ClientRequest__ReasoningSummary, Schema.Null]).annotate({ - description: "Override the reasoning summary for this turn and subsequent turns.", - }), + windows: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__ComputerUseWindowsRequirements, Schema.Null]), ), - threadId: Schema.String, -}); +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ComputerUseRequirements" }); -export type ClientRequest__TurnSteerParams = { - readonly clientUserMessageId?: string | null; - readonly expectedTurnId: string; - readonly input: ReadonlyArray; - readonly threadId: string; +export type V2ConfigWriteResponse__OverriddenMetadata = { + readonly effectiveValue: Schema.Json; + readonly message: string; + readonly overridingLayer: V2ConfigWriteResponse__ConfigLayerMetadata; }; -export const ClientRequest__TurnSteerParams = Schema.Struct({ - clientUserMessageId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - expectedTurnId: Schema.String.annotate({ - description: - "Required active turn id precondition. The request fails when it does not match the currently active turn.", - }), - input: Schema.Array(ClientRequest__UserInput), - threadId: Schema.String, -}); +export const V2ConfigWriteResponse__OverriddenMetadata = Schema.Struct({ + effectiveValue: Schema.Json.annotate({ expected: "JSON value" }), + message: Schema.String, + overridingLayer: V2ConfigWriteResponse__ConfigLayerMetadata, +}).annotate({ identifier: "V2ConfigWriteResponse__OverriddenMetadata" }); -export type CommandExecutionRequestApprovalParams__FileSystemPath = +export type V2ItemCompletedNotification__ThreadItem = | { - readonly path: CommandExecutionRequestApprovalParams__LegacyAppPathString; - readonly type: "path"; + readonly clientId?: string | null; + readonly content: ReadonlyArray; + readonly id: string; + readonly type: "userMessage"; } - | { readonly pattern: string; readonly type: "glob_pattern" } | { - readonly type: "special"; - readonly value: CommandExecutionRequestApprovalParams__FileSystemSpecialPath; - }; -export const CommandExecutionRequestApprovalParams__FileSystemPath = Schema.Union( + readonly fragments: ReadonlyArray; + readonly id: string; + readonly type: "hookPrompt"; + } + | { + readonly delivery?: V2ItemCompletedNotification__AgentMessageDelivery | null; + readonly id: string; + readonly memoryCitation?: V2ItemCompletedNotification__MemoryCitation | null; + readonly phase?: V2ItemCompletedNotification__MessagePhase | null; + readonly questions?: ReadonlyArray | null; + readonly text: string; + readonly type: "agentMessage"; + } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ItemCompletedNotification__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } + | { 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: V2ItemCompletedNotification__LegacyAppPathString; + readonly durationMs?: number | null; + readonly exitCode?: number | null; + readonly id: string; + readonly pluginId?: string | null; + readonly processId?: string | null; + readonly scriptPath?: string | null; + readonly source?: V2ItemCompletedNotification__CommandExecutionSource; + readonly status: V2ItemCompletedNotification__CommandExecutionStatus; + readonly type: "commandExecution"; + } + | { + readonly changes: ReadonlyArray; + readonly id: string; + readonly status: V2ItemCompletedNotification__PatchApplyStatus; + readonly type: "fileChange"; + } + | { + readonly appContext?: V2ItemCompletedNotification__McpToolCallAppContext | null; + readonly arguments: Schema.Json; + readonly durationMs?: number | null; + readonly error?: V2ItemCompletedNotification__McpToolCallError | null; + readonly id: string; + readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ItemCompletedNotification__McpAppUi | null; + readonly pluginId?: string | null; + readonly readOnlyHint?: boolean | null; + readonly result?: V2ItemCompletedNotification__McpToolCallResult | null; + readonly server: string; + readonly status: V2ItemCompletedNotification__McpToolCallStatus; + readonly tool: string; + readonly type: "mcpToolCall"; + } + | { + readonly arguments: Schema.Json; + 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: V2ItemCompletedNotification__CollabAgentToolCallStatus; + readonly tool: V2ItemCompletedNotification__CollabAgentTool; + readonly type: "collabAgentToolCall"; + } + | { + readonly agentPath: string; + readonly agentThreadId: string; + readonly id: string; + readonly kind: V2ItemCompletedNotification__SubAgentActivityKind; + readonly type: "subAgentActivity"; + } + | { + readonly action?: V2ItemCompletedNotification__WebSearchAction | null; + readonly id: string; + readonly query: string; + readonly results?: ReadonlyArray | null; + readonly type: "webSearch"; + } + | { + readonly id: string; + readonly path: V2ItemCompletedNotification__LegacyAppPathString; + readonly type: "imageView"; + } + | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } + | { + readonly failure?: V2ItemCompletedNotification__ImageGenerationFailure | null; + readonly id: string; + readonly result: string; + readonly revisedPrompt?: string | null; + readonly savedPath?: V2ItemCompletedNotification__AbsolutePathBuf | null; + readonly status: string; + readonly transparentBackground?: boolean | null; + 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({ - path: CommandExecutionRequestApprovalParams__LegacyAppPathString, - type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), - }).annotate({ title: "PathFileSystemPath" }), + clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__AgentMessageDelivery, Schema.Null]), + ), + id: Schema.String, + memoryCitation: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__MessagePhase, Schema.Null]), + ), + questions: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemCompletedNotification__AsyncUserInputQuestion), + Schema.Null, + ]), + ), + text: Schema.String, + type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), + }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ItemCompletedNotification__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), + 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.suspend( + (): Schema.Codec => + V2ItemCompletedNotification__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The duration of the command execution in milliseconds.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + exitCode: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The command's exit code.", + format: "int32", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), + processId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Identifier for the underlying PTY process (when available).", + }), + Schema.Null, + ]), + ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), + source: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ItemCompletedNotification__CommandExecutionSource, + ).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({ + appContext: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__McpToolCallAppContext, Schema.Null]), + ), + arguments: Schema.Json.annotate({ expected: "JSON value" }), + durationMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "The duration of the MCP tool call in milliseconds.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + error: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__McpToolCallError, Schema.Null]), + ), + id: Schema.String, + mcpAppResourceUri: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", + }), + Schema.Null, + ]), + ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), + pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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({ - pattern: Schema.String, - type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), - }).annotate({ title: "GlobPatternFileSystemPath" }), + arguments: Schema.Json.annotate({ expected: "JSON value" }), + 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().annotate({ expected: "an integer" })), + 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({ - type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), - value: CommandExecutionRequestApprovalParams__FileSystemSpecialPath, - }).annotate({ title: "SpecialFileSystemPath" }), - ], - { mode: "oneOf" }, -); - -export type CommandExecutionRequestApprovalResponse__CommandExecutionApprovalDecision = - | "accept" - | "acceptForSession" - | { - readonly acceptWithExecpolicyAmendment: { - readonly execpolicy_amendment: ReadonlyArray; - }; - } - | { - readonly applyNetworkPolicyAmendment: { - readonly network_policy_amendment: CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment; - }; - } - | "decline" - | "cancel"; -export const CommandExecutionRequestApprovalResponse__CommandExecutionApprovalDecision = - Schema.Union( - [ - Schema.Literal("accept").annotate({ description: "User approved the command." }), - Schema.Literal("acceptForSession").annotate({ - description: - "User approved the command and future prompts in the same session-scoped approval cache should run without prompting.", - }), - Schema.Struct({ - acceptWithExecpolicyAmendment: Schema.Struct({ - execpolicy_amendment: Schema.Array(Schema.String), + 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.", }), - }).annotate({ - title: "AcceptWithExecpolicyAmendmentCommandExecutionApprovalDecision", + ), + receiverThreadIds: Schema.Array(Schema.String).annotate({ description: - "User approved the command, and wants to apply the proposed execpolicy amendment so future matching commands can run without prompting.", - }), - Schema.Struct({ - applyNetworkPolicyAmendment: Schema.Struct({ - network_policy_amendment: CommandExecutionRequestApprovalResponse__NetworkPolicyAmendment, - }), - }).annotate({ - title: "ApplyNetworkPolicyAmendmentCommandExecutionApprovalDecision", - description: "User chose a persistent network policy rule (allow/deny) for this host.", - }), - Schema.Literal("decline").annotate({ - description: "User denied the command. The agent will continue the turn.", + "Thread ID of the receiving agent, when applicable. In case of spawn operation, this corresponds to the newly spawned agent.", }), - Schema.Literal("cancel").annotate({ - description: "User denied the command. The turn will also be immediately interrupted.", + senderThreadId: Schema.String.annotate({ + description: "Thread ID of the agent issuing the collab request.", }), - ], - { mode: "oneOf" }, - ); - -export type ExecCommandApprovalResponse__ReviewDecision = - | "approved" - | { - readonly approved_execpolicy_amendment: { - readonly proposed_execpolicy_amendment: ReadonlyArray; - }; - } - | "approved_for_session" - | { - readonly network_policy_amendment: { - readonly network_policy_amendment: ExecCommandApprovalResponse__NetworkPolicyAmendment; - }; - } - | "denied" - | "timed_out" - | "abort"; -export const ExecCommandApprovalResponse__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), + status: Schema.suspend( + (): Schema.Codec => + V2ItemCompletedNotification__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ItemCompletedNotification__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), + type: Schema.Literal("collabAgentToolCall").annotate({ + title: "CollabAgentToolCallThreadItemType", }), - }).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.", - }), + }).annotate({ title: "CollabAgentToolCallThreadItem" }), Schema.Struct({ - network_policy_amendment: Schema.Struct({ - network_policy_amendment: ExecCommandApprovalResponse__NetworkPolicyAmendment, + agentPath: Schema.String, + agentThreadId: Schema.String, + id: Schema.String, + kind: V2ItemCompletedNotification__SubAgentActivityKind, + type: Schema.Literal("subAgentActivity").annotate({ + title: "SubAgentActivityThreadItemType", }), - }).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 McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema = { - readonly default?: ReadonlyArray | null; - readonly description?: string | null; - readonly items: McpServerElicitationRequestParams__McpElicitationTitledEnumItems; - readonly maxItems?: number | null; - readonly minItems?: number | null; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationArrayType; -}; -export const McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema = - Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - items: McpServerElicitationRequestParams__McpElicitationTitledEnumItems, - maxItems: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - minItems: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationArrayType, - }); - -export type McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema = { - readonly default?: ReadonlyArray | null; - readonly description?: string | null; - readonly items: McpServerElicitationRequestParams__McpElicitationUntitledEnumItems; - readonly maxItems?: number | null; - readonly minItems?: number | null; - readonly title?: string | null; - readonly type: McpServerElicitationRequestParams__McpElicitationArrayType; -}; -export const McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema = - Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - items: McpServerElicitationRequestParams__McpElicitationUntitledEnumItems, - maxItems: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - minItems: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - Schema.Null, - ]), - ), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: McpServerElicitationRequestParams__McpElicitationArrayType, - }); - -export type McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema = - | McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema - | McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema; -export const McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema = Schema.Union( - [ - McpServerElicitationRequestParams__McpElicitationUntitledSingleSelectEnumSchema, - McpServerElicitationRequestParams__McpElicitationTitledSingleSelectEnumSchema, - ], -); - -export type PermissionsRequestApprovalParams__FileSystemPath = - | { readonly path: PermissionsRequestApprovalParams__LegacyAppPathString; 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__LegacyAppPathString, - 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__FileSystemPath = - | { - readonly path: PermissionsRequestApprovalResponse__LegacyAppPathString; - readonly type: "path"; - } - | { readonly pattern: string; readonly type: "glob_pattern" } - | { - readonly type: "special"; - readonly value: PermissionsRequestApprovalResponse__FileSystemSpecialPath; - }; -export const PermissionsRequestApprovalResponse__FileSystemPath = Schema.Union( - [ + }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ - path: PermissionsRequestApprovalResponse__LegacyAppPathString, - type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), - }).annotate({ title: "PathFileSystemPath" }), + action: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__WebSearchAction, Schema.Null]), + ), + id: Schema.String, + query: Schema.String, + results: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ + description: + "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", + }), + Schema.Null, + ]), + ), + type: Schema.Literal("webSearch").annotate({ title: "WebSearchThreadItemType" }), + }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ - pattern: Schema.String, - type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), - }).annotate({ title: "GlobPatternFileSystemPath" }), + id: Schema.String, + path: V2ItemCompletedNotification__LegacyAppPathString, + type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), + }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ - type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), - value: PermissionsRequestApprovalResponse__FileSystemSpecialPath, - }).annotate({ title: "SpecialFileSystemPath" }), - ], - { mode: "oneOf" }, -); - -export type ServerNotification__AppInfo = { - readonly appMetadata?: ServerNotification__AppMetadata | null; - readonly branding?: ServerNotification__AppBranding | null; - readonly description?: string | null; - readonly distributionChannel?: string | null; - readonly iconAssets?: { readonly [x: string]: string } | null; - readonly iconDarkAssets?: { readonly [x: string]: string } | null; - readonly id: string; - readonly installUrl?: string | null; - readonly isAccessible?: boolean; - readonly isEnabled?: boolean; - readonly labels?: { readonly [x: string]: string } | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly name: string; - readonly pluginDisplayNames?: ReadonlyArray; -}; -export const ServerNotification__AppInfo = Schema.Struct({ - appMetadata: Schema.optionalKey(Schema.Union([ServerNotification__AppMetadata, Schema.Null])), - branding: Schema.optionalKey(Schema.Union([ServerNotification__AppBranding, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - distributionChannel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconAssets: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - iconDarkAssets: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - id: Schema.String, - installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - isAccessible: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - isEnabled: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", - default: true, + durationMs: Schema.Number.annotate({ format: "uint64" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + id: Schema.String, + type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), + }).annotate({ + title: "SleepThreadItem", + description: "Display item emitted by the interruptible `clock.sleep` tool.", }), - ), - labels: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - name: Schema.String, - pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), -}).annotate({ description: "EXPERIMENTAL - app metadata returned by app-list APIs." }); - -export type ServerNotification__ExternalAgentConfigImportTypeResult = { - readonly failures: ReadonlyArray; - readonly itemType: ServerNotification__ExternalAgentConfigMigrationItemType; - readonly successes: ReadonlyArray; -}; -export const ServerNotification__ExternalAgentConfigImportTypeResult = Schema.Struct({ - failures: Schema.Array(ServerNotification__ExternalAgentConfigImportItemTypeFailure), - itemType: ServerNotification__ExternalAgentConfigMigrationItemType, - successes: Schema.Array(ServerNotification__ExternalAgentConfigImportItemTypeSuccess), -}); - -export type ServerNotification__FuzzyFileSearchSessionUpdatedNotification = { - readonly files: ReadonlyArray; - readonly query: string; - readonly sessionId: string; -}; -export const ServerNotification__FuzzyFileSearchSessionUpdatedNotification = Schema.Struct({ - files: Schema.Array(ServerNotification__FuzzyFileSearchResult), - query: Schema.String, - sessionId: Schema.String, -}); - -export type ServerNotification__HookRunSummary = { - readonly completedAt?: number | null; - readonly displayOrder: number; - readonly durationMs?: number | null; - readonly entries: ReadonlyArray; - readonly eventName: ServerNotification__HookEventName; - readonly executionMode: ServerNotification__HookExecutionMode; - readonly handlerType: ServerNotification__HookHandlerType; - readonly id: string; - readonly scope: ServerNotification__HookScope; - readonly source?: - | "system" - | "user" - | "project" - | "mdm" - | "sessionFlags" - | "plugin" - | "cloudRequirements" - | "cloudManagedConfig" - | "legacyManagedConfigFile" - | "legacyManagedConfigMdm" - | "unknown"; - readonly sourcePath: ServerNotification__AbsolutePathBuf; - readonly startedAt: number; - readonly status: ServerNotification__HookRunStatus; - readonly statusMessage?: string | null; -}; -export const ServerNotification__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(ServerNotification__HookOutputEntry), - eventName: ServerNotification__HookEventName, - executionMode: ServerNotification__HookExecutionMode, - handlerType: ServerNotification__HookHandlerType, - id: Schema.String, - scope: ServerNotification__HookScope, - source: Schema.optionalKey( - Schema.Literals([ - "system", - "user", - "project", - "mdm", - "sessionFlags", - "plugin", - "cloudRequirements", - "cloudManagedConfig", - "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__FileSystemPath = - | { readonly path: ServerNotification__LegacyAppPathString; 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__LegacyAppPathString, - type: Schema.Literal("path").annotate({ title: "PathFileSystemPathType" }), - }).annotate({ title: "PathFileSystemPath" }), + failure: Schema.optionalKey( + Schema.Union([V2ItemCompletedNotification__ImageGenerationFailure, Schema.Null]), + ), + 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, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), + }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ - pattern: Schema.String, - type: Schema.Literal("glob_pattern").annotate({ title: "GlobPatternFileSystemPathType" }), - }).annotate({ title: "GlobPatternFileSystemPath" }), + id: Schema.String, + review: Schema.String, + type: Schema.Literal("enteredReviewMode").annotate({ + title: "EnteredReviewModeThreadItemType", + }), + }).annotate({ title: "EnteredReviewModeThreadItem" }), Schema.Struct({ - type: Schema.Literal("special").annotate({ title: "SpecialFileSystemPathType" }), - value: ServerNotification__FileSystemSpecialPath, - }).annotate({ title: "SpecialFileSystemPath" }), + 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 ServerNotification__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: ServerNotification__CodexErrorInfo | null; - readonly message: string; -}; -export const ServerNotification__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([ServerNotification__CodexErrorInfo, Schema.Null]), - ), - 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, -}); +).annotate({ identifier: "V2ItemCompletedNotification__ThreadItem" }); -export type ServerNotification__CollaborationMode = { - readonly mode: ServerNotification__ModeKind; - readonly settings: ServerNotification__Settings; +export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSandboxEntry = { + readonly access: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode; + readonly path: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath; }; -export const ServerNotification__CollaborationMode = Schema.Struct({ - mode: ServerNotification__ModeKind, - settings: ServerNotification__Settings, -}).annotate({ description: "Collaboration mode for a Codex session." }); +export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSandboxEntry = + Schema.Struct({ + access: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode, + path: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath, + }).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSandboxEntry", + }); -export type ServerNotification__AccountRateLimitsUpdatedNotification = { - readonly rateLimits: ServerNotification__RateLimitSnapshot; +export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry = { + readonly access: V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode; + readonly path: V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath; }; -export const ServerNotification__AccountRateLimitsUpdatedNotification = Schema.Struct({ - rateLimits: ServerNotification__RateLimitSnapshot, -}).annotate({ - description: - "Sparse rolling rate-limit update.\n\nClients should merge available values into the most recent `account/rateLimits/read` response or refetch that snapshot. Nullable account metadata may be unavailable in a rolling update and does not clear a previously observed value.", -}); +export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry = + Schema.Struct({ + access: V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode, + path: V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath, + }).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry", + }); -export type ServerNotification__ThreadItem = +export type V2ItemStartedNotification__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ItemStartedNotification__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: ServerNotification__MemoryCitation | null; - readonly phase?: ServerNotification__MessagePhase | null; + readonly memoryCitation?: V2ItemStartedNotification__MemoryCitation | null; + readonly phase?: V2ItemStartedNotification__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ItemStartedNotification__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -20442,133 +34372,138 @@ export type ServerNotification__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ItemStartedNotification__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: ServerNotification__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ItemStartedNotification__CommandExecutionSource; + readonly status: V2ItemStartedNotification__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: ServerNotification__PatchApplyStatus; + readonly status: V2ItemStartedNotification__PatchApplyStatus; readonly type: "fileChange"; } | { - readonly appContext?: ServerNotification__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ItemStartedNotification__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: ServerNotification__McpToolCallError | null; + readonly error?: V2ItemStartedNotification__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ItemStartedNotification__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: ServerNotification__McpToolCallResult | null; + readonly readOnlyHint?: boolean | null; + readonly result?: V2ItemStartedNotification__McpToolCallResult | null; readonly server: string; - readonly status: ServerNotification__McpToolCallStatus; + readonly status: V2ItemStartedNotification__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: ServerNotification__DynamicToolCallStatus; + readonly status: V2ItemStartedNotification__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: ServerNotification__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ItemStartedNotification__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: ServerNotification__ReasoningEffort | null; + readonly reasoningEffort?: V2ItemStartedNotification__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ItemStartedNotification__CollabAgentToolCallStatus; + readonly tool: V2ItemStartedNotification__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: ServerNotification__SubAgentActivityKind; + readonly kind: V2ItemStartedNotification__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: ServerNotification__WebSearchAction | null; + readonly action?: V2ItemStartedNotification__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: ServerNotification__LegacyAppPathString; + readonly path: V2ItemStartedNotification__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ItemStartedNotification__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: ServerNotification__AbsolutePathBuf | null; + readonly savedPath?: V2ItemStartedNotification__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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 ServerNotification__ThreadItem = Schema.Union( +export const V2ItemStartedNotification__ThreadItem = Schema.Union( [ Schema.Struct({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - content: Schema.Array(ServerNotification__UserInput), + content: Schema.Array(V2ItemStartedNotification__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(ServerNotification__HookPromptFragment), + fragments: Schema.Array(V2ItemStartedNotification__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([ServerNotification__MemoryCitation, Schema.Null]), + Schema.Union([V2ItemStartedNotification__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__MessagePhase, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([ServerNotification__MessagePhase, Schema.Null])), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2ItemStartedNotification__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ItemStartedNotification__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -20594,17 +34529,20 @@ export const ServerNotification__ThreadItem = Schema.Union( ]), ), command: Schema.String.annotate({ description: "The command to be executed." }), - commandActions: Schema.Array(ServerNotification__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.suspend( + (): Schema.Codec => + V2ItemStartedNotification__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -20613,11 +34551,20 @@ export const ServerNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -20626,63 +34573,80 @@ export const ServerNotification__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ItemStartedNotification__CommandExecutionSource, + ).annotate({ default: "agent" }), ), - status: ServerNotification__CommandExecutionStatus, + status: V2ItemStartedNotification__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(ServerNotification__FileUpdateChange), + changes: Schema.Array(V2ItemStartedNotification__FileUpdateChange), id: Schema.String, - status: ServerNotification__PatchApplyStatus, + status: V2ItemStartedNotification__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ appContext: Schema.optionalKey( - Schema.Union([ServerNotification__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ItemStartedNotification__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), - error: Schema.optionalKey(Schema.Union([ServerNotification__McpToolCallError, Schema.Null])), + error: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__McpToolCallError, Schema.Null]), + ), id: Schema.String, mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( - Schema.Union([ServerNotification__McpToolCallResult, Schema.Null]), + Schema.Union([V2ItemStartedNotification__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: ServerNotification__McpToolCallStatus, + status: V2ItemStartedNotification__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(ServerNotification__DynamicToolCallOutputContentItem), + Schema.Array(V2ItemStartedNotification__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -20691,21 +34655,22 @@ export const ServerNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: ServerNotification__DynamicToolCallStatus, + status: V2ItemStartedNotification__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, ServerNotification__CollabAgentState).annotate({ - description: "Last known status of the target agents, when available.", - }), + agentsStates: Schema.Record( + Schema.String, + 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( Schema.Union([ @@ -20724,7 +34689,7 @@ export const ServerNotification__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([ServerNotification__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ItemStartedNotification__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -20735,20 +34700,14 @@ export const ServerNotification__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ItemStartedNotification__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ItemStartedNotification__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -20757,18 +34716,20 @@ export const ServerNotification__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: ServerNotification__SubAgentActivityKind, + kind: V2ItemStartedNotification__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ - action: Schema.optionalKey(Schema.Union([ServerNotification__WebSearchAction, Schema.Null])), + action: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__WebSearchAction, Schema.Null]), + ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -20779,13 +34740,17 @@ export const ServerNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: ServerNotification__LegacyAppPathString, + path: V2ItemStartedNotification__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -20793,13 +34758,17 @@ export const ServerNotification__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ItemStartedNotification__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([ServerNotification__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ItemStartedNotification__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -20824,725 +34793,794 @@ export const ServerNotification__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ItemStartedNotification__ThreadItem" }); -export type ServerNotification__ConfigWarningNotification = { - readonly details?: string | null; - readonly path?: string | null; - readonly range?: ServerNotification__TextRange | null; - readonly summary: string; +export type V2PluginInstalledResponse__PluginSummary = { + readonly authPolicy: V2PluginInstalledResponse__PluginAuthPolicy; + readonly availability?: V2PluginInstalledResponse__PluginAvailability; + readonly disabledReason?: V2PluginInstalledResponse__PluginDisabledReason | null; + readonly eligiblePlanTypes?: ReadonlyArray | null; + readonly enabled: boolean; + readonly id: string; + readonly installPolicy: V2PluginInstalledResponse__PluginInstallPolicy; + readonly installPolicySource?: V2PluginInstalledResponse__PluginInstallPolicySource | null; + readonly installed: boolean; + readonly installedAt?: number | null; + readonly interface?: V2PluginInstalledResponse__PluginInterface | null; + readonly keywords?: ReadonlyArray; + readonly localVersion?: string | null; + readonly mustShowInstallationInterstitial?: boolean | null; + readonly name: string; + readonly remotePluginId?: string | null; + readonly shareContext?: V2PluginInstalledResponse__PluginShareContext | null; + readonly source: V2PluginInstalledResponse__PluginSource; + readonly version?: string | null; }; -export const ServerNotification__ConfigWarningNotification = Schema.Struct({ - details: Schema.optionalKey( +export const V2PluginInstalledResponse__PluginSummary = Schema.Struct({ + authPolicy: V2PluginInstalledResponse__PluginAuthPolicy, + availability: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2PluginInstalledResponse__PluginAvailability, + ).annotate({ + description: "Availability state for installing and using the plugin.", + default: "AVAILABLE", + }), + ), + disabledReason: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__PluginDisabledReason, Schema.Null]).annotate({ + description: "Why the remote plugin is unavailable, when provided by plugin-service.", + }), + ), + eligiblePlanTypes: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Optional extra guidance or error details." }), + Schema.Array(Schema.String).annotate({ + description: "Raw plugin-service plan identifiers eligible to install the plugin.", + }), Schema.Null, ]), ), - path: Schema.optionalKey( + enabled: Schema.Boolean, + id: Schema.String, + installPolicy: V2PluginInstalledResponse__PluginInstallPolicy, + installPolicySource: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__PluginInstallPolicySource, Schema.Null]), + ), + installed: Schema.Boolean, + installedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Unix timestamp in seconds when the remote plugin was installed, when available.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + interface: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__PluginInterface, Schema.Null]), + ), + keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + localVersion: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Optional path to the config file that triggered the warning.", + description: "Version of the locally materialized plugin package when available.", }), Schema.Null, ]), ), - range: Schema.optionalKey( - Schema.Union([ServerNotification__TextRange, Schema.Null]).annotate({ - description: "Optional range for the error location inside the config file.", + mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + name: Schema.String, + remotePluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), + Schema.Null, + ]), + ), + shareContext: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__PluginShareContext, Schema.Null]).annotate({ + description: "Remote sharing context associated with this plugin when available.", }), ), - summary: Schema.String.annotate({ description: "Concise summary of the warning." }), -}); - -export type ServerNotification__ThreadStatusChangedNotification = { - readonly status: ServerNotification__ThreadStatus; - readonly threadId: string; -}; -export const ServerNotification__ThreadStatusChangedNotification = Schema.Struct({ - status: ServerNotification__ThreadStatus, - threadId: Schema.String, -}); - -export type ServerNotification__ThreadGoalUpdatedNotification = { - readonly goal: ServerNotification__ThreadGoal; - readonly threadId: string; - readonly turnId?: string | null; -}; -export const ServerNotification__ThreadGoalUpdatedNotification = Schema.Struct({ - goal: ServerNotification__ThreadGoal, - threadId: Schema.String, - turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ServerNotification__ThreadTokenUsageUpdatedNotification = { - readonly threadId: string; - readonly tokenUsage: ServerNotification__ThreadTokenUsage; - readonly turnId: string; -}; -export const ServerNotification__ThreadTokenUsageUpdatedNotification = Schema.Struct({ - threadId: Schema.String, - tokenUsage: ServerNotification__ThreadTokenUsage, - turnId: Schema.String, -}); - -export type ServerNotification__TurnPlanUpdatedNotification = { - readonly explanation?: string | null; - readonly plan: ReadonlyArray; - readonly threadId: string; - readonly turnId: string; -}; -export const ServerNotification__TurnPlanUpdatedNotification = Schema.Struct({ - explanation: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - plan: Schema.Array(ServerNotification__TurnPlanStep), - threadId: Schema.String, - turnId: Schema.String, -}); - -export type ServerRequest__FileSystemPath = - | { readonly path: ServerRequest__LegacyAppPathString; 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__LegacyAppPathString, - 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" }, -); + source: V2PluginInstalledResponse__PluginSource, + version: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Version advertised by the remote marketplace backend when available.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2PluginInstalledResponse__PluginSummary" }); -export type ServerRequest__McpElicitationTitledMultiSelectEnumSchema = { - readonly default?: ReadonlyArray | null; - readonly description?: string | null; - readonly items: ServerRequest__McpElicitationTitledEnumItems; - readonly maxItems?: number | null; - readonly minItems?: number | null; - readonly title?: string | null; - readonly type: ServerRequest__McpElicitationArrayType; +export type V2PluginListResponse__PluginSummary = { + readonly authPolicy: V2PluginListResponse__PluginAuthPolicy; + readonly availability?: V2PluginListResponse__PluginAvailability; + readonly disabledReason?: V2PluginListResponse__PluginDisabledReason | null; + readonly eligiblePlanTypes?: ReadonlyArray | null; + readonly enabled: boolean; + readonly id: string; + readonly installPolicy: V2PluginListResponse__PluginInstallPolicy; + readonly installPolicySource?: V2PluginListResponse__PluginInstallPolicySource | null; + readonly installed: boolean; + readonly installedAt?: number | null; + readonly interface?: V2PluginListResponse__PluginInterface | null; + readonly keywords?: ReadonlyArray; + readonly localVersion?: string | null; + readonly mustShowInstallationInterstitial?: boolean | null; + readonly name: string; + readonly remotePluginId?: string | null; + readonly shareContext?: V2PluginListResponse__PluginShareContext | null; + readonly source: V2PluginListResponse__PluginSource; + readonly version?: string | null; }; -export const ServerRequest__McpElicitationTitledMultiSelectEnumSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - items: ServerRequest__McpElicitationTitledEnumItems, - maxItems: Schema.optionalKey( +export const V2PluginListResponse__PluginSummary = Schema.Struct({ + authPolicy: V2PluginListResponse__PluginAuthPolicy, + availability: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2PluginListResponse__PluginAvailability, + ).annotate({ + description: "Availability state for installing and using the plugin.", + default: "AVAILABLE", + }), + ), + disabledReason: Schema.optionalKey( + Schema.Union([V2PluginListResponse__PluginDisabledReason, Schema.Null]).annotate({ + description: "Why the remote plugin is unavailable, when provided by plugin-service.", + }), + ), + eligiblePlanTypes: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Array(Schema.String).annotate({ + description: "Raw plugin-service plan identifiers eligible to install the plugin.", + }), Schema.Null, ]), ), - minItems: Schema.optionalKey( + enabled: Schema.Boolean, + id: Schema.String, + installPolicy: V2PluginListResponse__PluginInstallPolicy, + installPolicySource: Schema.optionalKey( + Schema.Union([V2PluginListResponse__PluginInstallPolicySource, Schema.Null]), + ), + installed: Schema.Boolean, + installedAt: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Number.annotate({ + description: + "Unix timestamp in seconds when the remote plugin was installed, when available.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationArrayType, -}); - -export type ServerRequest__McpElicitationUntitledMultiSelectEnumSchema = { - readonly default?: ReadonlyArray | null; - readonly description?: string | null; - readonly items: ServerRequest__McpElicitationUntitledEnumItems; - readonly maxItems?: number | null; - readonly minItems?: number | null; - readonly title?: string | null; - readonly type: ServerRequest__McpElicitationArrayType; -}; -export const ServerRequest__McpElicitationUntitledMultiSelectEnumSchema = Schema.Struct({ - default: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - items: ServerRequest__McpElicitationUntitledEnumItems, - maxItems: Schema.optionalKey( + interface: Schema.optionalKey(Schema.Union([V2PluginListResponse__PluginInterface, Schema.Null])), + keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + localVersion: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.String.annotate({ + description: "Version of the locally materialized plugin package when available.", + }), Schema.Null, ]), ), - minItems: Schema.optionalKey( + mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + name: Schema.String, + remotePluginId: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), Schema.Null, ]), ), - title: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: ServerRequest__McpElicitationArrayType, -}); - -export type ServerRequest__McpElicitationSingleSelectEnumSchema = - | ServerRequest__McpElicitationUntitledSingleSelectEnumSchema - | ServerRequest__McpElicitationTitledSingleSelectEnumSchema; -export const ServerRequest__McpElicitationSingleSelectEnumSchema = Schema.Union([ - ServerRequest__McpElicitationUntitledSingleSelectEnumSchema, - ServerRequest__McpElicitationTitledSingleSelectEnumSchema, -]); + shareContext: Schema.optionalKey( + Schema.Union([V2PluginListResponse__PluginShareContext, Schema.Null]).annotate({ + description: "Remote sharing context associated with this plugin when available.", + }), + ), + source: V2PluginListResponse__PluginSource, + version: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Version advertised by the remote marketplace backend when available.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2PluginListResponse__PluginSummary" }); -export type ServerRequest__CommandExecutionRequestApprovalParams = { - readonly approvalId?: string | null; - readonly command?: string | null; - readonly commandActions?: ReadonlyArray | null; - readonly cwd?: ServerRequest__LegacyAppPathString | null; - readonly environmentId?: string | null; - readonly itemId: string; - readonly networkApprovalContext?: ServerRequest__NetworkApprovalContext | null; - readonly proposedExecpolicyAmendment?: ReadonlyArray | null; - readonly proposedNetworkPolicyAmendments?: ReadonlyArray | null; - readonly reason?: string | null; - readonly startedAtMs: number; - readonly threadId: string; - readonly turnId: string; +export type V2PluginReadResponse__PluginSummary = { + readonly authPolicy: V2PluginReadResponse__PluginAuthPolicy; + readonly availability?: V2PluginReadResponse__PluginAvailability; + readonly disabledReason?: V2PluginReadResponse__PluginDisabledReason | null; + readonly eligiblePlanTypes?: ReadonlyArray | null; + readonly enabled: boolean; + readonly id: string; + readonly installPolicy: V2PluginReadResponse__PluginInstallPolicy; + readonly installPolicySource?: V2PluginReadResponse__PluginInstallPolicySource | null; + readonly installed: boolean; + readonly installedAt?: number | null; + readonly interface?: V2PluginReadResponse__PluginInterface | null; + readonly keywords?: ReadonlyArray; + readonly localVersion?: string | null; + readonly mustShowInstallationInterstitial?: boolean | null; + readonly name: string; + readonly remotePluginId?: string | null; + readonly shareContext?: V2PluginReadResponse__PluginShareContext | null; + readonly source: V2PluginReadResponse__PluginSource; + readonly version?: string | null; }; -export const ServerRequest__CommandExecutionRequestApprovalParams = Schema.Struct({ - approvalId: Schema.optionalKey( +export const V2PluginReadResponse__PluginSummary = Schema.Struct({ + authPolicy: V2PluginReadResponse__PluginAuthPolicy, + availability: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2PluginReadResponse__PluginAvailability, + ).annotate({ + description: "Availability state for installing and using the plugin.", + default: "AVAILABLE", + }), + ), + disabledReason: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__PluginDisabledReason, Schema.Null]).annotate({ + description: "Why the remote plugin is unavailable, when provided by plugin-service.", + }), + ), + eligiblePlanTypes: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ + Schema.Array(Schema.String).annotate({ + description: "Raw plugin-service plan identifiers eligible to install the plugin.", + }), + Schema.Null, + ]), + ), + enabled: Schema.Boolean, + id: Schema.String, + installPolicy: V2PluginReadResponse__PluginInstallPolicy, + installPolicySource: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__PluginInstallPolicySource, Schema.Null]), + ), + installed: Schema.Boolean, + installedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ description: - "Unique identifier for this specific approval callback.\n\nFor regular shell/unified_exec approvals, this is null.\n\nFor zsh-exec-bridge subcommand approvals, multiple callbacks can belong to one parent `itemId`, so `approvalId` is a distinct opaque callback id (a UUID) used to disambiguate routing.", + "Unix timestamp in seconds when the remote plugin was installed, when available.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + interface: Schema.optionalKey(Schema.Union([V2PluginReadResponse__PluginInterface, Schema.Null])), + keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + localVersion: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Version of the locally materialized plugin package when available.", }), Schema.Null, ]), ), - command: Schema.optionalKey( + mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + name: Schema.String, + remotePluginId: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "The command to be executed." }), + Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), Schema.Null, ]), ), - commandActions: Schema.optionalKey( + shareContext: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__PluginShareContext, Schema.Null]).annotate({ + description: "Remote sharing context associated with this plugin when available.", + }), + ), + source: V2PluginReadResponse__PluginSource, + version: Schema.optionalKey( Schema.Union([ - Schema.Array(ServerRequest__CommandAction).annotate({ - description: "Best-effort parsed command actions for friendly display.", + Schema.String.annotate({ + description: "Version advertised by the remote marketplace backend when available.", }), Schema.Null, ]), ), - cwd: Schema.optionalKey( - Schema.Union([ServerRequest__LegacyAppPathString, Schema.Null]).annotate({ - description: "The command's working directory.", +}).annotate({ identifier: "V2PluginReadResponse__PluginSummary" }); + +export type V2PluginShareListResponse__PluginSummary = { + readonly authPolicy: V2PluginShareListResponse__PluginAuthPolicy; + readonly availability?: V2PluginShareListResponse__PluginAvailability; + readonly disabledReason?: V2PluginShareListResponse__PluginDisabledReason | null; + readonly eligiblePlanTypes?: ReadonlyArray | null; + readonly enabled: boolean; + readonly id: string; + readonly installPolicy: V2PluginShareListResponse__PluginInstallPolicy; + readonly installPolicySource?: V2PluginShareListResponse__PluginInstallPolicySource | null; + readonly installed: boolean; + readonly installedAt?: number | null; + readonly interface?: V2PluginShareListResponse__PluginInterface | null; + readonly keywords?: ReadonlyArray; + readonly localVersion?: string | null; + readonly mustShowInstallationInterstitial?: boolean | null; + readonly name: string; + readonly remotePluginId?: string | null; + readonly shareContext?: V2PluginShareListResponse__PluginShareContext | null; + readonly source: V2PluginShareListResponse__PluginSource; + readonly version?: string | null; +}; +export const V2PluginShareListResponse__PluginSummary = Schema.Struct({ + authPolicy: V2PluginShareListResponse__PluginAuthPolicy, + availability: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2PluginShareListResponse__PluginAvailability, + ).annotate({ + description: "Availability state for installing and using the plugin.", + default: "AVAILABLE", }), ), - environmentId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Environment in which the command will run." }), - Schema.Null, - ]), - ), - itemId: Schema.String, - networkApprovalContext: Schema.optionalKey( - Schema.Union([ServerRequest__NetworkApprovalContext, Schema.Null]).annotate({ - description: "Optional context for a managed-network approval prompt.", + disabledReason: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__PluginDisabledReason, Schema.Null]).annotate({ + description: "Why the remote plugin is unavailable, when provided by plugin-service.", }), ), - proposedExecpolicyAmendment: Schema.optionalKey( + eligiblePlanTypes: Schema.optionalKey( Schema.Union([ Schema.Array(Schema.String).annotate({ - description: - "Optional proposed execpolicy amendment to allow similar commands without prompting.", + description: "Raw plugin-service plan identifiers eligible to install the plugin.", }), Schema.Null, ]), ), - proposedNetworkPolicyAmendments: Schema.optionalKey( + enabled: Schema.Boolean, + id: Schema.String, + installPolicy: V2PluginShareListResponse__PluginInstallPolicy, + installPolicySource: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__PluginInstallPolicySource, Schema.Null]), + ), + installed: Schema.Boolean, + installedAt: Schema.optionalKey( Schema.Union([ - Schema.Array(ServerRequest__NetworkPolicyAmendment).annotate({ + Schema.Number.annotate({ description: - "Optional proposed network policy amendments (allow/deny host) for future requests.", - }), + "Unix timestamp in seconds when the remote plugin was installed, when available.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), - reason: Schema.optionalKey( + interface: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__PluginInterface, Schema.Null]), + ), + keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), + localVersion: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Optional explanatory reason (e.g. request for network access).", + description: "Version of the locally materialized plugin package when available.", }), Schema.Null, ]), ), - startedAtMs: Schema.Number.annotate({ - description: "Unix timestamp (in milliseconds) when this approval request started.", - format: "int64", - }).check(Schema.isInt()), - threadId: Schema.String, - turnId: Schema.String, -}); - -export type ServerRequest__ToolRequestUserInputParams = { - readonly autoResolutionMs?: number | null; - readonly itemId: string; - readonly questions: ReadonlyArray; - readonly threadId: string; - readonly turnId: string; -}; -export const ServerRequest__ToolRequestUserInputParams = Schema.Struct({ - autoResolutionMs: Schema.optionalKey( + mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + name: Schema.String, + remotePluginId: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), Schema.Null, ]), ), - itemId: Schema.String, - questions: Schema.Array(ServerRequest__ToolRequestUserInputQuestion), - threadId: Schema.String, - turnId: Schema.String, -}).annotate({ description: "EXPERIMENTAL. Params sent with a request_user_input event." }); - -export type V2AppListUpdatedNotification__AppInfo = { - readonly appMetadata?: V2AppListUpdatedNotification__AppMetadata | null; - readonly branding?: V2AppListUpdatedNotification__AppBranding | null; - readonly description?: string | null; - readonly distributionChannel?: string | null; - readonly iconAssets?: { readonly [x: string]: string } | null; - readonly iconDarkAssets?: { readonly [x: string]: string } | null; - readonly id: string; - readonly installUrl?: string | null; - readonly isAccessible?: boolean; - readonly isEnabled?: boolean; - readonly labels?: { readonly [x: string]: string } | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly name: string; - readonly pluginDisplayNames?: ReadonlyArray; -}; -export const V2AppListUpdatedNotification__AppInfo = Schema.Struct({ - appMetadata: Schema.optionalKey( - Schema.Union([V2AppListUpdatedNotification__AppMetadata, Schema.Null]), - ), - branding: Schema.optionalKey( - Schema.Union([V2AppListUpdatedNotification__AppBranding, Schema.Null]), - ), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - distributionChannel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconAssets: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - iconDarkAssets: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - id: Schema.String, - installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - isAccessible: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - isEnabled: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", - default: true, - }), - ), - labels: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - name: Schema.String, - pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), -}).annotate({ description: "EXPERIMENTAL - app metadata returned by app-list APIs." }); - -export type V2AppsListResponse__AppInfo = { - readonly appMetadata?: V2AppsListResponse__AppMetadata | null; - readonly branding?: V2AppsListResponse__AppBranding | null; - readonly description?: string | null; - readonly distributionChannel?: string | null; - readonly iconAssets?: { readonly [x: string]: string } | null; - readonly iconDarkAssets?: { readonly [x: string]: string } | null; - readonly id: string; - readonly installUrl?: string | null; - readonly isAccessible?: boolean; - readonly isEnabled?: boolean; - readonly labels?: { readonly [x: string]: string } | null; - readonly logoUrl?: string | null; - readonly logoUrlDark?: string | null; - readonly name: string; - readonly pluginDisplayNames?: ReadonlyArray; -}; -export const V2AppsListResponse__AppInfo = Schema.Struct({ - appMetadata: Schema.optionalKey(Schema.Union([V2AppsListResponse__AppMetadata, Schema.Null])), - branding: Schema.optionalKey(Schema.Union([V2AppsListResponse__AppBranding, Schema.Null])), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - distributionChannel: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - iconAssets: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - iconDarkAssets: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - id: Schema.String, - installUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - isAccessible: Schema.optionalKey(Schema.Boolean.annotate({ default: false })), - isEnabled: Schema.optionalKey( - Schema.Boolean.annotate({ - description: - "Whether this app is enabled in config.toml. Example: ```toml [apps.bad_app] enabled = false ```", - default: true, + shareContext: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__PluginShareContext, Schema.Null]).annotate({ + description: "Remote sharing context associated with this plugin when available.", }), ), - labels: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.String), Schema.Null]), - ), - logoUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - logoUrlDark: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - name: Schema.String, - pluginDisplayNames: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), -}).annotate({ description: "EXPERIMENTAL - app metadata returned by app-list APIs." }); - -export type V2ConfigReadResponse__ConfigLayer = { - readonly config: unknown; - readonly disabledReason?: string | null; - readonly name: V2ConfigReadResponse__ConfigLayerSource; - readonly version: string; -}; -export const V2ConfigReadResponse__ConfigLayer = Schema.Struct({ - config: Schema.Unknown, - disabledReason: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - name: V2ConfigReadResponse__ConfigLayerSource, - version: Schema.String, -}); - -export type V2ConfigReadResponse__ConfigLayerMetadata = { - readonly name: V2ConfigReadResponse__ConfigLayerSource; - readonly version: string; -}; -export const V2ConfigReadResponse__ConfigLayerMetadata = Schema.Struct({ - name: V2ConfigReadResponse__ConfigLayerSource, - version: Schema.String, -}); - -export type V2ConfigReadResponse__ToolsV2 = { - readonly web_search?: V2ConfigReadResponse__WebSearchToolConfig | null; -}; -export const V2ConfigReadResponse__ToolsV2 = Schema.Struct({ - web_search: Schema.optionalKey( - Schema.Union([V2ConfigReadResponse__WebSearchToolConfig, Schema.Null]), - ), -}); - -export type V2ConfigRequirementsReadResponse__ModelsRequirements = { - readonly newThread?: V2ConfigRequirementsReadResponse__NewThreadModelDefaults | null; -}; -export const V2ConfigRequirementsReadResponse__ModelsRequirements = Schema.Struct({ - newThread: Schema.optionalKey( - Schema.Union([V2ConfigRequirementsReadResponse__NewThreadModelDefaults, Schema.Null]), - ), -}); - -export type V2ConfigWriteResponse__ConfigLayerMetadata = { - readonly name: V2ConfigWriteResponse__ConfigLayerSource; - readonly version: string; -}; -export const V2ConfigWriteResponse__ConfigLayerMetadata = Schema.Struct({ - name: V2ConfigWriteResponse__ConfigLayerSource, - version: Schema.String, -}); - -export type V2ErrorNotification__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ErrorNotification__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ErrorNotification__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ErrorNotification__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); - -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( + source: V2PluginShareListResponse__PluginSource, + version: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: - "Null or empty means home-scoped migration; non-empty means repo-scoped migration.", + description: "Version advertised by the remote marketplace backend when available.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "V2PluginShareListResponse__PluginSummary" }); + +export type V2RawResponseItemCompletedNotification__ResponseItem = + | { + readonly content: ReadonlyArray; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly phase?: V2RawResponseItemCompletedNotification__MessagePhase | null; + readonly role: string; + readonly type: "message"; + } + | { + readonly author: string; + readonly content: ReadonlyArray; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly recipient: string; + readonly type: "agent_message"; + } + | { + readonly content?: ReadonlyArray | null; + readonly encrypted_content?: string | null; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly summary: ReadonlyArray; + readonly type: "reasoning"; + } + | { + readonly action: V2RawResponseItemCompletedNotification__LocalShellAction; + readonly call_id?: string | null; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly status: V2RawResponseItemCompletedNotification__LocalShellStatus; + readonly type: "local_shell_call"; + } + | { + readonly arguments: string; + readonly call_id: string; + readonly encrypted_function_args?: ReadonlyArray | null; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly name: string; + readonly namespace?: string | null; + readonly type: "function_call"; + } + | { + readonly arguments: Schema.Json; + readonly call_id?: string | null; + readonly execution: string; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly status?: string | null; + readonly type: "tool_search_call"; + } + | { + readonly call_id?: string | null; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly name?: string | null; + readonly namespace?: string | null; + readonly output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody; + readonly type: "function_call_output"; + } + | { + readonly call_id: string; + readonly id?: string | null; + readonly input: string; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly name: string; + readonly namespace?: string | null; + readonly status?: string | null; + readonly type: "custom_tool_call"; + } + | { + readonly call_id: string; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly name?: string | null; + readonly output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody; + readonly type: "custom_tool_call_output"; + } + | { + readonly call_id?: string | null; + readonly execution: string; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly status: string; + readonly tools: ReadonlyArray; + readonly type: "tool_search_output"; + } + | { + readonly action?: V2RawResponseItemCompletedNotification__ResponsesApiWebSearchAction | null; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly status?: string | null; + readonly type: "web_search_call"; + } + | { + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly result: string; + readonly revised_prompt?: string | null; + readonly status: string; + readonly type: "image_generation_call"; + } + | { + readonly encrypted_content: string; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly type: "compaction"; + } + | { + readonly reasoning: V2RawResponseItemCompletedNotification__ConfigurationReasoning; + readonly type: "configuration_update"; + } + | { readonly type: "compaction_trigger" } + | { + readonly encrypted_content?: string | null; + readonly id?: string | null; + readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; + readonly type: "context_compaction"; + } + | { readonly type: "other" }; +export const V2RawResponseItemCompletedNotification__ResponseItem = Schema.Union( + [ + Schema.Struct({ + content: Schema.Array(V2RawResponseItemCompletedNotification__ContentItem), + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + phase: Schema.optionalKey( + Schema.Union([V2RawResponseItemCompletedNotification__MessagePhase, Schema.Null]), + ), + role: Schema.String, + type: Schema.Literal("message").annotate({ title: "MessageResponseItemType" }), + }).annotate({ title: "MessageResponseItem" }), + Schema.Struct({ + author: Schema.String, + content: Schema.Array(V2RawResponseItemCompletedNotification__AgentMessageInputContent), + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + recipient: Schema.String, + type: Schema.Literal("agent_message").annotate({ title: "AgentMessageResponseItemType" }), + }).annotate({ title: "AgentMessageResponseItem" }), + Schema.Struct({ + content: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2RawResponseItemCompletedNotification__ReasoningItemContent), + Schema.Null, + ]), + ), + encrypted_content: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + summary: Schema.Array(V2RawResponseItemCompletedNotification__ReasoningItemReasoningSummary), + type: Schema.Literal("reasoning").annotate({ title: "ReasoningResponseItemType" }), + }).annotate({ title: "ReasoningResponseItem" }), + Schema.Struct({ + action: V2RawResponseItemCompletedNotification__LocalShellAction, + call_id: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Set when using the Responses API." }), + Schema.Null, + ]), + ), + id: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Legacy id field retained for compatibility with older payloads.", + }), + Schema.Null, + ]), + ), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + status: V2RawResponseItemCompletedNotification__LocalShellStatus, + type: Schema.Literal("local_shell_call").annotate({ + title: "LocalShellCallResponseItemType", + }), + }).annotate({ title: "LocalShellCallResponseItem" }), + Schema.Struct({ + arguments: Schema.String, + call_id: Schema.String, + encrypted_function_args: Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]), + ), + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("function_call").annotate({ title: "FunctionCallResponseItemType" }), + }).annotate({ title: "FunctionCallResponseItem" }), + Schema.Struct({ + arguments: Schema.Json.annotate({ expected: "JSON value" }), + call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + execution: Schema.String, + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + status: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("tool_search_call").annotate({ + title: "ToolSearchCallResponseItemType", + }), + }).annotate({ title: "ToolSearchCallResponseItem" }), + Schema.Struct({ + call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + name: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody, + type: Schema.Literal("function_call_output").annotate({ + title: "FunctionCallOutputResponseItemType", + }), + }).annotate({ title: "FunctionCallOutputResponseItem" }), + Schema.Struct({ + call_id: Schema.String, + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + input: Schema.String, + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("custom_tool_call").annotate({ + title: "CustomToolCallResponseItemType", + }), + }).annotate({ title: "CustomToolCallResponseItem" }), + Schema.Struct({ + call_id: Schema.String, + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + name: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody, + type: Schema.Literal("custom_tool_call_output").annotate({ + title: "CustomToolCallOutputResponseItemType", }), - Schema.Null, - ]), - ), - description: Schema.String, - details: Schema.optionalKey( - Schema.Union([V2ExternalAgentConfigDetectResponse__MigrationDetails, Schema.Null]), - ), - itemType: V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItemType, -}); - -export type V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportTypeResult = - { - readonly failures: ReadonlyArray; - readonly itemType: V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType; - readonly successes: ReadonlyArray; - }; -export const V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportTypeResult = - Schema.Struct({ - failures: Schema.Array( - V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeFailure, - ), - itemType: - V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigMigrationItemType, - successes: Schema.Array( - V2ExternalAgentConfigImportCompletedNotification__ExternalAgentConfigImportItemTypeSuccess, - ), - }); - -export type V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportHistory = { - readonly completedAtMs: number; - readonly failures: ReadonlyArray; - readonly importId: string; - readonly successes: ReadonlyArray; -}; -export const V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportHistory = - Schema.Struct({ - completedAtMs: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - failures: Schema.Array( - V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeFailure, - ), - importId: Schema.String, - successes: Schema.Array( - V2ExternalAgentConfigImportHistoriesReadResponse__ExternalAgentConfigImportItemTypeSuccess, - ), - }); - -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.", + }).annotate({ title: "CustomToolCallOutputResponseItem" }), + Schema.Struct({ + call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + execution: Schema.String, + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + status: Schema.String, + tools: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), + type: Schema.Literal("tool_search_output").annotate({ + title: "ToolSearchOutputResponseItemType", }), - Schema.Null, - ]), - ), - description: Schema.String, - details: Schema.optionalKey( - Schema.Union([V2ExternalAgentConfigImportParams__MigrationDetails, Schema.Null]), - ), - itemType: V2ExternalAgentConfigImportParams__ExternalAgentConfigMigrationItemType, -}); - -export type V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportTypeResult = { - readonly failures: ReadonlyArray; - readonly itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType; - readonly successes: ReadonlyArray; -}; -export const V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportTypeResult = - Schema.Struct({ - failures: Schema.Array( - V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeFailure, - ), - itemType: V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigMigrationItemType, - successes: Schema.Array( - V2ExternalAgentConfigImportProgressNotification__ExternalAgentConfigImportItemTypeSuccess, - ), - }); - -export type V2GetAccountRateLimitsResponse__RateLimitResetCreditsSummary = { - readonly availableCount: number; - readonly credits?: ReadonlyArray | null; -}; -export const V2GetAccountRateLimitsResponse__RateLimitResetCreditsSummary = Schema.Struct({ - availableCount: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - credits: Schema.optionalKey( - Schema.Union([ - Schema.Array(V2GetAccountRateLimitsResponse__RateLimitResetCredit).annotate({ - description: - "Detail rows for available reset credits, when the backend provides them.\n\n`null` means only `availableCount` is known, while an empty array means details were fetched and no available credits were returned. The backend may cap this list, so its length can be less than `availableCount`.", + }).annotate({ title: "ToolSearchOutputResponseItem" }), + Schema.Struct({ + action: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__ResponsesApiWebSearchAction, + Schema.Null, + ]), + ), + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + status: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("web_search_call").annotate({ title: "WebSearchCallResponseItemType" }), + }).annotate({ title: "WebSearchCallResponseItem" }), + Schema.Struct({ + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + result: Schema.String, + revised_prompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + status: Schema.String, + type: Schema.Literal("image_generation_call").annotate({ + title: "ImageGenerationCallResponseItemType", }), - Schema.Null, - ]), - ), -}); - -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" - | "plugin" - | "cloudRequirements" - | "cloudManagedConfig" - | "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", - "plugin", - "cloudRequirements", - "cloudManagedConfig", - "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 V2HooksListResponse__HooksListEntry = { - readonly cwd: string; - readonly errors: ReadonlyArray; - readonly hooks: ReadonlyArray; - readonly warnings: ReadonlyArray; -}; -export const V2HooksListResponse__HooksListEntry = Schema.Struct({ - cwd: Schema.String, - errors: Schema.Array(V2HooksListResponse__HookErrorInfo), - hooks: Schema.Array(V2HooksListResponse__HookMetadata), - warnings: Schema.Array(Schema.String), -}); - -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" - | "plugin" - | "cloudRequirements" - | "cloudManagedConfig" - | "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", - "plugin", - "cloudRequirements", - "cloudManagedConfig", - "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])), -}); + }).annotate({ title: "ImageGenerationCallResponseItem" }), + Schema.Struct({ + encrypted_content: Schema.String, + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + type: Schema.Literal("compaction").annotate({ title: "CompactionResponseItemType" }), + }).annotate({ title: "CompactionResponseItem" }), + Schema.Struct({ + reasoning: V2RawResponseItemCompletedNotification__ConfigurationReasoning, + type: Schema.Literal("configuration_update").annotate({ + title: "ConfigurationUpdateResponseItemType", + }), + }).annotate({ + title: "ConfigurationUpdateResponseItem", + description: "A durable input control interpreted by the backend at its position in history.", + }), + Schema.Struct({ + type: Schema.Literal("compaction_trigger").annotate({ + title: "CompactionTriggerResponseItemType", + }), + }).annotate({ title: "CompactionTriggerResponseItem" }), + Schema.Struct({ + encrypted_content: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + internal_chat_message_metadata_passthrough: Schema.optionalKey( + Schema.Union([ + V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, + Schema.Null, + ]), + ), + type: Schema.Literal("context_compaction").annotate({ + title: "ContextCompactionResponseItemType", + }), + }).annotate({ title: "ContextCompactionResponseItem" }), + Schema.Struct({ + type: Schema.Literal("other").annotate({ title: "OtherResponseItemType" }), + }).annotate({ title: "OtherResponseItem" }), + ], + { mode: "oneOf" }, +).annotate({ identifier: "V2RawResponseItemCompletedNotification__ResponseItem" }); -export type V2ItemCompletedNotification__ThreadItem = +export type V2ReviewStartResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ReviewStartResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ItemCompletedNotification__MemoryCitation | null; - readonly phase?: V2ItemCompletedNotification__MessagePhase | null; + readonly memoryCitation?: V2ReviewStartResponse__MemoryCitation | null; + readonly phase?: V2ReviewStartResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ReviewStartResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -21553,137 +35591,133 @@ export type V2ItemCompletedNotification__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ReviewStartResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ItemCompletedNotification__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ReviewStartResponse__CommandExecutionSource; + readonly status: V2ReviewStartResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ItemCompletedNotification__PatchApplyStatus; + readonly status: V2ReviewStartResponse__PatchApplyStatus; readonly type: "fileChange"; } | { - readonly appContext?: V2ItemCompletedNotification__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ReviewStartResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ItemCompletedNotification__McpToolCallError | null; + readonly error?: V2ReviewStartResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ReviewStartResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ItemCompletedNotification__McpToolCallResult | null; + readonly readOnlyHint?: boolean | null; + readonly result?: V2ReviewStartResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ItemCompletedNotification__McpToolCallStatus; + readonly status: V2ReviewStartResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ItemCompletedNotification__DynamicToolCallStatus; + readonly status: V2ReviewStartResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { - readonly [x: string]: V2ItemCompletedNotification__CollabAgentState; - }; + readonly agentsStates: { readonly [x: string]: V2ReviewStartResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ItemCompletedNotification__ReasoningEffort | null; + readonly reasoningEffort?: V2ReviewStartResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ReviewStartResponse__CollabAgentToolCallStatus; + readonly tool: V2ReviewStartResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ItemCompletedNotification__SubAgentActivityKind; + readonly kind: V2ReviewStartResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ItemCompletedNotification__WebSearchAction | null; + readonly action?: V2ReviewStartResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ItemCompletedNotification__LegacyAppPathString; + readonly path: V2ReviewStartResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ReviewStartResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ItemCompletedNotification__AbsolutePathBuf | null; + readonly savedPath?: V2ReviewStartResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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 V2ReviewStartResponse__ThreadItem = Schema.Union( [ Schema.Struct({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - content: Schema.Array(V2ItemCompletedNotification__UserInput), + content: Schema.Array(V2ReviewStartResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ItemCompletedNotification__HookPromptFragment), + fragments: Schema.Array(V2ReviewStartResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2ReviewStartResponse__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__MemoryCitation, Schema.Null]), - ), - phase: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__MessagePhase, Schema.Null]), + Schema.Union([V2ReviewStartResponse__MemoryCitation, Schema.Null]), ), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__MessagePhase, Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2ReviewStartResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ReviewStartResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -21709,17 +35743,20 @@ 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(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.suspend( + (): Schema.Codec => + V2ReviewStartResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -21728,11 +35765,20 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -21741,65 +35787,80 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ReviewStartResponse__CommandExecutionSource, + ).annotate({ default: "agent" }), ), - status: V2ItemCompletedNotification__CommandExecutionStatus, + status: V2ReviewStartResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ItemCompletedNotification__FileUpdateChange), + changes: Schema.Array(V2ReviewStartResponse__FileUpdateChange), id: Schema.String, - status: V2ItemCompletedNotification__PatchApplyStatus, + status: V2ReviewStartResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ appContext: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ReviewStartResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), error: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__McpToolCallError, Schema.Null]), + Schema.Union([V2ReviewStartResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ReviewStartResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__McpToolCallResult, Schema.Null]), + Schema.Union([V2ReviewStartResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ItemCompletedNotification__McpToolCallStatus, + status: V2ReviewStartResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ItemCompletedNotification__DynamicToolCallOutputContentItem), + Schema.Array(V2ReviewStartResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -21808,22 +35869,21 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ItemCompletedNotification__DynamicToolCallStatus, + 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, - V2ItemCompletedNotification__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([ @@ -21842,7 +35902,7 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ReviewStartResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -21853,20 +35913,14 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ReviewStartResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ReviewStartResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -21875,20 +35929,20 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ItemCompletedNotification__SubAgentActivityKind, + kind: V2ReviewStartResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__WebSearchAction, Schema.Null]), + Schema.Union([V2ReviewStartResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -21899,13 +35953,17 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ItemCompletedNotification__LegacyAppPathString, + path: V2ReviewStartResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -21913,13 +35971,17 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ReviewStartResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ItemCompletedNotification__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ReviewStartResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -21944,88 +36006,47 @@ export const V2ItemCompletedNotification__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); - -export type V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath = - | { - readonly path: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString; - 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__LegacyAppPathString, - 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" }, -); +).annotate({ identifier: "V2ReviewStartResponse__ThreadItem" }); -export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath = - | { - readonly path: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString; - readonly type: "path"; - } - | { readonly pattern: string; readonly type: "glob_pattern" } - | { - readonly type: "special"; - readonly value: V2ItemGuardianApprovalReviewStartedNotification__FileSystemSpecialPath; - }; -export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath = Schema.Union( - [ - Schema.Struct({ - path: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, - 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 V2SkillsListResponse__SkillsListEntry = { + readonly cwd: string; + readonly errors: ReadonlyArray; + readonly skills: ReadonlyArray; +}; +export const V2SkillsListResponse__SkillsListEntry = Schema.Struct({ + cwd: Schema.String, + errors: Schema.Array(V2SkillsListResponse__SkillErrorInfo), + skills: Schema.Array(V2SkillsListResponse__SkillMetadata), +}).annotate({ identifier: "V2SkillsListResponse__SkillsListEntry" }); -export type V2ItemStartedNotification__ThreadItem = +export type V2ThreadForkResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadForkResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ItemStartedNotification__MemoryCitation | null; - readonly phase?: V2ItemStartedNotification__MessagePhase | null; + readonly memoryCitation?: V2ThreadForkResponse__MemoryCitation | null; + readonly phase?: V2ThreadForkResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadForkResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -22036,135 +36057,133 @@ export type V2ItemStartedNotification__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadForkResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ItemStartedNotification__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadForkResponse__CommandExecutionSource; + readonly status: V2ThreadForkResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ItemStartedNotification__PatchApplyStatus; + readonly status: V2ThreadForkResponse__PatchApplyStatus; readonly type: "fileChange"; } | { - readonly appContext?: V2ItemStartedNotification__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadForkResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ItemStartedNotification__McpToolCallError | null; + readonly error?: V2ThreadForkResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadForkResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ItemStartedNotification__McpToolCallResult | null; + readonly readOnlyHint?: boolean | null; + readonly result?: V2ThreadForkResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ItemStartedNotification__McpToolCallStatus; + readonly status: V2ThreadForkResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ItemStartedNotification__DynamicToolCallStatus; + readonly status: V2ThreadForkResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ItemStartedNotification__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadForkResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ItemStartedNotification__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadForkResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadForkResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadForkResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ItemStartedNotification__SubAgentActivityKind; + readonly kind: V2ThreadForkResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ItemStartedNotification__WebSearchAction | null; + readonly action?: V2ThreadForkResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ItemStartedNotification__LegacyAppPathString; + readonly path: V2ThreadForkResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadForkResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ItemStartedNotification__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadForkResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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 V2ThreadForkResponse__ThreadItem = Schema.Union( [ Schema.Struct({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - content: Schema.Array(V2ItemStartedNotification__UserInput), + content: Schema.Array(V2ThreadForkResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ItemStartedNotification__HookPromptFragment), + fragments: Schema.Array(V2ThreadForkResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__MemoryCitation, Schema.Null]), - ), - phase: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__MessagePhase, Schema.Null]), + Schema.Union([V2ThreadForkResponse__MemoryCitation, Schema.Null]), ), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadForkResponse__MessagePhase, Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2ThreadForkResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadForkResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -22190,17 +36209,20 @@ 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(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.suspend( + (): Schema.Codec => + V2ThreadForkResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -22209,11 +36231,20 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -22222,65 +36253,80 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadForkResponse__CommandExecutionSource, + ).annotate({ default: "agent" }), ), - status: V2ItemStartedNotification__CommandExecutionStatus, + status: V2ThreadForkResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ItemStartedNotification__FileUpdateChange), + changes: Schema.Array(V2ThreadForkResponse__FileUpdateChange), id: Schema.String, - status: V2ItemStartedNotification__PatchApplyStatus, + status: V2ThreadForkResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ appContext: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadForkResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), error: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadForkResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadForkResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ItemStartedNotification__McpToolCallStatus, + status: V2ThreadForkResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ItemStartedNotification__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadForkResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -22289,22 +36335,21 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ItemStartedNotification__DynamicToolCallStatus, + 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, - V2ItemStartedNotification__CollabAgentState, - ).annotate({ description: "Last known status of the target agents, when available." }), + 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." }), model: Schema.optionalKey( Schema.Union([ @@ -22323,7 +36368,7 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadForkResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -22334,20 +36379,14 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadForkResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadForkResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -22356,20 +36395,20 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ItemStartedNotification__SubAgentActivityKind, + kind: V2ThreadForkResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadForkResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -22380,13 +36419,17 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ItemStartedNotification__LegacyAppPathString, + path: V2ThreadForkResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -22394,13 +36437,17 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ItemStartedNotification__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadForkResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -22425,260 +36472,36 @@ export const V2ItemStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); - -export type V2ModelListResponse__Model = { - readonly additionalSpeedTiers?: ReadonlyArray; - readonly availabilityNux?: V2ModelListResponse__ModelAvailabilityNux | null; - readonly defaultReasoningEffort: V2ModelListResponse__ReasoningEffort; - readonly defaultServiceTier?: string | null; - readonly description: string; - readonly displayName: string; - readonly hidden: boolean; - readonly id: string; - readonly inputModalities?: ReadonlyArray; - readonly isDefault: boolean; - readonly model: string; - readonly serviceTiers?: ReadonlyArray; - 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({ - description: "Deprecated: use `serviceTiers` instead.", - default: [], - }), - ), - availabilityNux: Schema.optionalKey( - Schema.Union([V2ModelListResponse__ModelAvailabilityNux, Schema.Null]), - ), - defaultReasoningEffort: V2ModelListResponse__ReasoningEffort, - defaultServiceTier: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Catalog default service tier id for this model, when one is configured.", - }), - Schema.Null, - ]), - ), - 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, - serviceTiers: Schema.optionalKey( - Schema.Array(V2ModelListResponse__ModelServiceTier).annotate({ default: [] }), - ), - 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 V2PluginInstalledResponse__PluginShareContext = { - readonly creatorAccountUserId?: string | null; - readonly creatorName?: string | null; - readonly discoverability?: V2PluginInstalledResponse__PluginShareDiscoverability | null; - readonly remotePluginId: string; - readonly remoteVersion?: string | null; - readonly sharePrincipals?: ReadonlyArray | null; - readonly shareUrl?: string | null; -}; -export const V2PluginInstalledResponse__PluginShareContext = Schema.Struct({ - creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - discoverability: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__PluginShareDiscoverability, Schema.Null]), - ), - remotePluginId: Schema.String, - remoteVersion: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version of the remote shared plugin release when available.", - }), - Schema.Null, - ]), - ), - sharePrincipals: Schema.optionalKey( - Schema.Union([Schema.Array(V2PluginInstalledResponse__PluginSharePrincipal), Schema.Null]), - ), - shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2PluginListResponse__PluginShareContext = { - readonly creatorAccountUserId?: string | null; - readonly creatorName?: string | null; - readonly discoverability?: V2PluginListResponse__PluginShareDiscoverability | null; - readonly remotePluginId: string; - readonly remoteVersion?: string | null; - readonly sharePrincipals?: ReadonlyArray | null; - readonly shareUrl?: string | null; -}; -export const V2PluginListResponse__PluginShareContext = Schema.Struct({ - creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - discoverability: Schema.optionalKey( - Schema.Union([V2PluginListResponse__PluginShareDiscoverability, Schema.Null]), - ), - remotePluginId: Schema.String, - remoteVersion: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version of the remote shared plugin release when available.", - }), - Schema.Null, - ]), - ), - sharePrincipals: Schema.optionalKey( - Schema.Union([Schema.Array(V2PluginListResponse__PluginSharePrincipal), Schema.Null]), - ), - shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2PluginReadResponse__SkillSummary = { - readonly description: string; - readonly enabled: boolean; - readonly interface?: V2PluginReadResponse__SkillInterface | null; - readonly name: string; - readonly path?: 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.optionalKey(Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null])), - shortDescription: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2PluginReadResponse__PluginShareContext = { - readonly creatorAccountUserId?: string | null; - readonly creatorName?: string | null; - readonly discoverability?: V2PluginReadResponse__PluginShareDiscoverability | null; - readonly remotePluginId: string; - readonly remoteVersion?: string | null; - readonly sharePrincipals?: ReadonlyArray | null; - readonly shareUrl?: string | null; -}; -export const V2PluginReadResponse__PluginShareContext = Schema.Struct({ - creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - discoverability: Schema.optionalKey( - Schema.Union([V2PluginReadResponse__PluginShareDiscoverability, Schema.Null]), - ), - remotePluginId: Schema.String, - remoteVersion: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version of the remote shared plugin release when available.", - }), - Schema.Null, - ]), - ), - sharePrincipals: Schema.optionalKey( - Schema.Union([Schema.Array(V2PluginReadResponse__PluginSharePrincipal), Schema.Null]), - ), - shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2PluginReadResponse__ScheduledTaskSummary = { - readonly key: string; - readonly name: string; - readonly prompt: string; - readonly schedule: V2PluginReadResponse__ScheduledTaskSchedule; -}; -export const V2PluginReadResponse__ScheduledTaskSummary = Schema.Struct({ - key: Schema.String, - name: Schema.String, - prompt: Schema.String, - schedule: V2PluginReadResponse__ScheduledTaskSchedule, -}); - -export type V2PluginShareListResponse__PluginShareContext = { - readonly creatorAccountUserId?: string | null; - readonly creatorName?: string | null; - readonly discoverability?: V2PluginShareListResponse__PluginShareDiscoverability | null; - readonly remotePluginId: string; - readonly remoteVersion?: string | null; - readonly sharePrincipals?: ReadonlyArray | null; - readonly shareUrl?: string | null; -}; -export const V2PluginShareListResponse__PluginShareContext = Schema.Struct({ - creatorAccountUserId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - creatorName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - discoverability: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__PluginShareDiscoverability, Schema.Null]), - ), - remotePluginId: Schema.String, - remoteVersion: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version of the remote shared plugin release when available.", - }), - Schema.Null, - ]), - ), - sharePrincipals: Schema.optionalKey( - Schema.Union([Schema.Array(V2PluginShareListResponse__PluginSharePrincipal), Schema.Null]), - ), - shareUrl: 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, -}); +).annotate({ identifier: "V2ThreadForkResponse__ThreadItem" }); -export type V2ReviewStartResponse__ThreadItem = +export type V2ThreadItemsListResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadItemsListResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ReviewStartResponse__MemoryCitation | null; - readonly phase?: V2ReviewStartResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadItemsListResponse__MemoryCitation | null; + readonly phase?: V2ThreadItemsListResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadItemsListResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -22689,133 +36512,138 @@ export type V2ReviewStartResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadItemsListResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ReviewStartResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadItemsListResponse__CommandExecutionSource; + readonly status: V2ThreadItemsListResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ReviewStartResponse__PatchApplyStatus; + readonly status: V2ThreadItemsListResponse__PatchApplyStatus; readonly type: "fileChange"; } | { - readonly appContext?: V2ReviewStartResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadItemsListResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ReviewStartResponse__McpToolCallError | null; + readonly error?: V2ThreadItemsListResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadItemsListResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ReviewStartResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | null; + readonly result?: V2ThreadItemsListResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ReviewStartResponse__McpToolCallStatus; + readonly status: V2ThreadItemsListResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ReviewStartResponse__DynamicToolCallStatus; + readonly status: V2ThreadItemsListResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ReviewStartResponse__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadItemsListResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ReviewStartResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadItemsListResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadItemsListResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadItemsListResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ReviewStartResponse__SubAgentActivityKind; + readonly kind: V2ThreadItemsListResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ReviewStartResponse__WebSearchAction | null; + readonly action?: V2ThreadItemsListResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ReviewStartResponse__LegacyAppPathString; + readonly path: V2ThreadItemsListResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadItemsListResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ReviewStartResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadItemsListResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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 V2ThreadItemsListResponse__ThreadItem = Schema.Union( [ Schema.Struct({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - content: Schema.Array(V2ReviewStartResponse__UserInput), + content: Schema.Array(V2ThreadItemsListResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ReviewStartResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadItemsListResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadItemsListResponse__MemoryCitation, Schema.Null]), + ), + phase: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__MessagePhase, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ReviewStartResponse__MessagePhase, Schema.Null])), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2ThreadItemsListResponse__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadItemsListResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -22841,17 +36669,20 @@ 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(V2ThreadItemsListResponse__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.suspend( + (): Schema.Codec => + V2ThreadItemsListResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -22860,11 +36691,20 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -22873,65 +36713,80 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadItemsListResponse__CommandExecutionSource, + ).annotate({ default: "agent" }), ), - status: V2ReviewStartResponse__CommandExecutionStatus, + status: V2ThreadItemsListResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ReviewStartResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadItemsListResponse__FileUpdateChange), id: Schema.String, - status: V2ReviewStartResponse__PatchApplyStatus, + status: V2ThreadItemsListResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ appContext: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadItemsListResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), error: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadItemsListResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadItemsListResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ReviewStartResponse__McpToolCallStatus, + status: V2ThreadItemsListResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ReviewStartResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadItemsListResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -22940,21 +36795,22 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ReviewStartResponse__DynamicToolCallStatus, + status: V2ThreadItemsListResponse__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({ - description: "Last known status of the target agents, when available.", - }), + agentsStates: Schema.Record( + Schema.String, + V2ThreadItemsListResponse__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([ @@ -22973,7 +36829,7 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadItemsListResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -22984,20 +36840,14 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadItemsListResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadItemsListResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -23006,20 +36856,20 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ReviewStartResponse__SubAgentActivityKind, + kind: V2ThreadItemsListResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadItemsListResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -23030,13 +36880,17 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ReviewStartResponse__LegacyAppPathString, + path: V2ThreadItemsListResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -23044,13 +36898,17 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadItemsListResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ReviewStartResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadItemsListResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -23075,76 +36933,36 @@ export const V2ReviewStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { 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: V2SkillsListResponse__AbsolutePathBuf; - 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: 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 V2ThreadForkResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadForkResponse__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadForkResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); +).annotate({ identifier: "V2ThreadItemsListResponse__ThreadItem" }); -export type V2ThreadForkResponse__ThreadItem = +export type V2ThreadListResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadListResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadForkResponse__MemoryCitation | null; - readonly phase?: V2ThreadForkResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadListResponse__MemoryCitation | null; + readonly phase?: V2ThreadListResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadListResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -23155,133 +36973,133 @@ export type V2ThreadForkResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadListResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadForkResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadListResponse__CommandExecutionSource; + 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 appContext?: V2ThreadForkResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadListResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadForkResponse__McpToolCallError | null; + readonly error?: V2ThreadListResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadListResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadForkResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | 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 arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadForkResponse__DynamicToolCallStatus; + 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" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadListResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadListResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadForkResponse__SubAgentActivityKind; + readonly kind: V2ThreadListResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadForkResponse__WebSearchAction | null; + readonly action?: V2ThreadListResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadForkResponse__LegacyAppPathString; + readonly path: V2ThreadListResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadListResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadForkResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadListResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__AgentMessageDelivery, Schema.Null]), + ), 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])), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadListResponse__MessagePhase, Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2ThreadListResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadListResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -23307,17 +37125,20 @@ 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.suspend( + (): Schema.Codec => + V2ThreadListResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -23326,11 +37147,20 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -23339,65 +37169,80 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadListResponse__CommandExecutionSource, + ).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({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadListResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), 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.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadForkResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadListResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -23406,19 +37251,19 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadForkResponse__DynamicToolCallStatus, + 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." }), @@ -23439,7 +37284,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.", }), ), @@ -23450,20 +37295,14 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadListResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadListResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -23472,20 +37311,20 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadForkResponse__SubAgentActivityKind, + kind: V2ThreadListResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadListResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -23496,13 +37335,17 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadForkResponse__LegacyAppPathString, + path: V2ThreadListResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -23510,13 +37353,17 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadForkResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadListResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -23541,45 +37388,36 @@ export const V2ThreadForkResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadListResponse__ThreadItem" }); -export type V2ThreadListResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadListResponse__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadListResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); - -export type V2ThreadListResponse__ThreadItem = +export type V2ThreadMetadataUpdateResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadMetadataUpdateResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadListResponse__MemoryCitation | null; - readonly phase?: V2ThreadListResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadMetadataUpdateResponse__MemoryCitation | null; + readonly phase?: V2ThreadMetadataUpdateResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadMetadataUpdateResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -23590,133 +37428,140 @@ export type V2ThreadListResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadMetadataUpdateResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadListResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadMetadataUpdateResponse__CommandExecutionSource; + 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 appContext?: V2ThreadListResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadMetadataUpdateResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadListResponse__McpToolCallError | null; + readonly error?: V2ThreadMetadataUpdateResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadMetadataUpdateResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadListResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | 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 arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadListResponse__DynamicToolCallStatus; + 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" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadMetadataUpdateResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadMetadataUpdateResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadListResponse__SubAgentActivityKind; + readonly kind: V2ThreadMetadataUpdateResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadListResponse__WebSearchAction | null; + readonly action?: V2ThreadMetadataUpdateResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadListResponse__LegacyAppPathString; + readonly path: V2ThreadMetadataUpdateResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadMetadataUpdateResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadListResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadMetadataUpdateResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__AgentMessageDelivery, Schema.Null]), + ), 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, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2ThreadMetadataUpdateResponse__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadMetadataUpdateResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -23742,17 +37587,20 @@ 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.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -23761,11 +37609,20 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -23774,65 +37631,80 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__CommandExecutionSource, + ).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({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), 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.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadListResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -23841,21 +37713,22 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadListResponse__DynamicToolCallStatus, + 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([ @@ -23874,7 +37747,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.", }), ), @@ -23885,20 +37758,14 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -23907,20 +37774,20 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadListResponse__SubAgentActivityKind, + kind: V2ThreadMetadataUpdateResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -23931,13 +37798,17 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadListResponse__LegacyAppPathString, + path: V2ThreadMetadataUpdateResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -23945,13 +37816,17 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadMetadataUpdateResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -23976,45 +37851,36 @@ export const V2ThreadListResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); - -export type V2ThreadMetadataUpdateResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadMetadataUpdateResponse__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadMetadataUpdateResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); +).annotate({ identifier: "V2ThreadMetadataUpdateResponse__ThreadItem" }); -export type V2ThreadMetadataUpdateResponse__ThreadItem = +export type V2ThreadReadResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadReadResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadMetadataUpdateResponse__MemoryCitation | null; - readonly phase?: V2ThreadMetadataUpdateResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadReadResponse__MemoryCitation | null; + readonly phase?: V2ThreadReadResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadReadResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -24025,137 +37891,133 @@ export type V2ThreadMetadataUpdateResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadReadResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadMetadataUpdateResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadReadResponse__CommandExecutionSource; + 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 appContext?: V2ThreadMetadataUpdateResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadReadResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadMetadataUpdateResponse__McpToolCallError | null; + readonly error?: V2ThreadReadResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadReadResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadMetadataUpdateResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | 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 arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadMetadataUpdateResponse__DynamicToolCallStatus; + 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" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadReadResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadReadResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadMetadataUpdateResponse__SubAgentActivityKind; + readonly kind: V2ThreadReadResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadMetadataUpdateResponse__WebSearchAction | null; + readonly action?: V2ThreadReadResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadMetadataUpdateResponse__LegacyAppPathString; + readonly path: V2ThreadReadResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadReadResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadMetadataUpdateResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadReadResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__AgentMessageDelivery, Schema.Null]), + ), 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]), ), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadReadResponse__MessagePhase, Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2ThreadReadResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadReadResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -24181,17 +38043,20 @@ 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.suspend( + (): Schema.Codec => + V2ThreadReadResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -24200,11 +38065,20 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -24213,65 +38087,80 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadReadResponse__CommandExecutionSource, + ).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({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadReadResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), 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.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadMetadataUpdateResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadReadResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -24280,22 +38169,21 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadMetadataUpdateResponse__DynamicToolCallStatus, + 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([ @@ -24314,7 +38202,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.", }), ), @@ -24325,20 +38213,14 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadReadResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadReadResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -24347,20 +38229,20 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadMetadataUpdateResponse__SubAgentActivityKind, + kind: V2ThreadReadResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadReadResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -24371,13 +38253,17 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadMetadataUpdateResponse__LegacyAppPathString, + path: V2ThreadReadResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -24385,13 +38271,17 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadReadResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -24416,45 +38306,36 @@ export const V2ThreadMetadataUpdateResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); - -export type V2ThreadReadResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadReadResponse__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadReadResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); +).annotate({ identifier: "V2ThreadReadResponse__ThreadItem" }); -export type V2ThreadReadResponse__ThreadItem = +export type V2ThreadResumeResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadResumeResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadReadResponse__MemoryCitation | null; - readonly phase?: V2ThreadReadResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadResumeResponse__MemoryCitation | null; + readonly phase?: V2ThreadResumeResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadResumeResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -24465,133 +38346,133 @@ export type V2ThreadReadResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadResumeResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadReadResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadResumeResponse__CommandExecutionSource; + 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 appContext?: V2ThreadReadResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadResumeResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadReadResponse__McpToolCallError | null; + readonly error?: V2ThreadResumeResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadResumeResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadReadResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | 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 arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadReadResponse__DynamicToolCallStatus; + 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" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadResumeResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadResumeResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadReadResponse__SubAgentActivityKind; + readonly kind: V2ThreadResumeResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadReadResponse__WebSearchAction | null; + readonly action?: V2ThreadResumeResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadReadResponse__LegacyAppPathString; + readonly path: V2ThreadResumeResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadResumeResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadReadResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadResumeResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__AgentMessageDelivery, Schema.Null]), + ), 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])), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__MessagePhase, Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2ThreadResumeResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadResumeResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -24617,17 +38498,20 @@ 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.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -24636,11 +38520,20 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -24649,65 +38542,80 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__CommandExecutionSource, + ).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({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), 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.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadReadResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadResumeResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -24716,21 +38624,21 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadReadResponse__DynamicToolCallStatus, + 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([ @@ -24749,7 +38657,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.", }), ), @@ -24760,20 +38668,14 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -24782,20 +38684,20 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadReadResponse__SubAgentActivityKind, + kind: V2ThreadResumeResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -24806,13 +38708,17 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadReadResponse__LegacyAppPathString, + path: V2ThreadResumeResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -24820,13 +38726,17 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadResumeResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -24851,53 +38761,36 @@ export const V2ThreadReadResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadResumeResponse__ThreadItem" }); -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?: V2ThreadResumeResponse__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadResumeResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); - -export type V2ThreadResumeResponse__ThreadItem = +export type V2ThreadRevertResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadRevertResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadResumeResponse__MemoryCitation | null; - readonly phase?: V2ThreadResumeResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadRevertResponse__MemoryCitation | null; + readonly phase?: V2ThreadRevertResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadRevertResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -24908,133 +38801,133 @@ export type V2ThreadResumeResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadRevertResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadResumeResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadRevertResponse__CommandExecutionSource; + readonly status: V2ThreadRevertResponse__CommandExecutionStatus; readonly type: "commandExecution"; } | { - readonly changes: ReadonlyArray; + readonly changes: ReadonlyArray; readonly id: string; - readonly status: V2ThreadResumeResponse__PatchApplyStatus; + readonly status: V2ThreadRevertResponse__PatchApplyStatus; readonly type: "fileChange"; } | { - readonly appContext?: V2ThreadResumeResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadRevertResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadResumeResponse__McpToolCallError | null; + readonly error?: V2ThreadRevertResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadRevertResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadResumeResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | null; + readonly result?: V2ThreadRevertResponse__McpToolCallResult | null; readonly server: string; - readonly status: V2ThreadResumeResponse__McpToolCallStatus; + readonly status: V2ThreadRevertResponse__McpToolCallStatus; readonly tool: string; readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; - readonly contentItems?: ReadonlyArray | null; + readonly arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadResumeResponse__DynamicToolCallStatus; + readonly status: V2ThreadRevertResponse__DynamicToolCallStatus; readonly success?: boolean | null; readonly tool: string; readonly type: "dynamicToolCall"; } | { - readonly agentsStates: { readonly [x: string]: V2ThreadResumeResponse__CollabAgentState }; + readonly agentsStates: { readonly [x: string]: V2ThreadRevertResponse__CollabAgentState }; readonly id: string; readonly model?: string | null; readonly prompt?: string | null; - readonly reasoningEffort?: V2ThreadResumeResponse__ReasoningEffort | null; + readonly reasoningEffort?: V2ThreadRevertResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadRevertResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadRevertResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadResumeResponse__SubAgentActivityKind; + readonly kind: V2ThreadRevertResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadResumeResponse__WebSearchAction | null; + readonly action?: V2ThreadRevertResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadResumeResponse__LegacyAppPathString; + readonly path: V2ThreadRevertResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadRevertResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadResumeResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadRevertResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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 V2ThreadRevertResponse__ThreadItem = Schema.Union( [ Schema.Struct({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - content: Schema.Array(V2ThreadResumeResponse__UserInput), + content: Schema.Array(V2ThreadRevertResponse__UserInput), id: Schema.String, type: Schema.Literal("userMessage").annotate({ title: "UserMessageThreadItemType" }), }).annotate({ title: "UserMessageThreadItem" }), Schema.Struct({ - fragments: Schema.Array(V2ThreadResumeResponse__HookPromptFragment), + fragments: Schema.Array(V2ThreadRevertResponse__HookPromptFragment), id: Schema.String, type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__MemoryCitation, Schema.Null]), + Schema.Union([V2ThreadRevertResponse__MemoryCitation, Schema.Null]), ), - phase: Schema.optionalKey(Schema.Union([V2ThreadResumeResponse__MessagePhase, Schema.Null])), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadRevertResponse__MessagePhase, Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2ThreadRevertResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadRevertResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -25060,17 +38953,20 @@ 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(V2ThreadRevertResponse__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.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -25079,11 +38975,20 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -25092,65 +38997,80 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__CommandExecutionSource, + ).annotate({ default: "agent" }), ), - status: V2ThreadResumeResponse__CommandExecutionStatus, + status: V2ThreadRevertResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ title: "CommandExecutionThreadItemType", }), }).annotate({ title: "CommandExecutionThreadItem" }), Schema.Struct({ - changes: Schema.Array(V2ThreadResumeResponse__FileUpdateChange), + changes: Schema.Array(V2ThreadRevertResponse__FileUpdateChange), id: Schema.String, - status: V2ThreadResumeResponse__PatchApplyStatus, + status: V2ThreadRevertResponse__PatchApplyStatus, type: Schema.Literal("fileChange").annotate({ title: "FileChangeThreadItemType" }), }).annotate({ title: "FileChangeThreadItem" }), Schema.Struct({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadRevertResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__McpToolCallError, Schema.Null]), + Schema.Union([V2ThreadRevertResponse__McpToolCallError, Schema.Null]), ), id: Schema.String, mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__McpToolCallResult, Schema.Null]), + Schema.Union([V2ThreadRevertResponse__McpToolCallResult, Schema.Null]), ), server: Schema.String, - status: V2ThreadResumeResponse__McpToolCallStatus, + status: V2ThreadRevertResponse__McpToolCallStatus, tool: Schema.String, type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadResumeResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadRevertResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -25159,19 +39079,19 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadResumeResponse__DynamicToolCallStatus, + status: V2ThreadRevertResponse__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( + agentsStates: Schema.Record(Schema.String, V2ThreadRevertResponse__CollabAgentState).annotate( { description: "Last known status of the target agents, when available." }, ), id: Schema.String.annotate({ description: "Unique identifier for this collab tool call." }), @@ -25192,7 +39112,7 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( ]), ), reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__ReasoningEffort, Schema.Null]).annotate({ + Schema.Union([V2ThreadRevertResponse__ReasoningEffort, Schema.Null]).annotate({ description: "Reasoning effort requested for the spawned agent, when applicable.", }), ), @@ -25203,20 +39123,14 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -25225,20 +39139,20 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadResumeResponse__SubAgentActivityKind, + kind: V2ThreadRevertResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadRevertResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -25249,13 +39163,17 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadResumeResponse__LegacyAppPathString, + path: V2ThreadRevertResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -25263,13 +39181,17 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadRevertResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -25294,45 +39216,83 @@ export const V2ThreadResumeResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadRevertResponse__ThreadItem" }); -export type V2ThreadRollbackResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadRollbackResponse__CodexErrorInfo | null; - readonly message: string; +export type V2ThreadSettingsUpdatedNotification__ThreadSettings = { + readonly activePermissionProfile?: V2ThreadSettingsUpdatedNotification__ActivePermissionProfile | null; + readonly approvalPolicy: V2ThreadSettingsUpdatedNotification__AskForApproval; + readonly approvalsReviewer: V2ThreadSettingsUpdatedNotification__ApprovalsReviewer; + readonly collaborationMode: V2ThreadSettingsUpdatedNotification__CollaborationMode; + readonly cwd: V2ThreadSettingsUpdatedNotification__AbsolutePathBuf; + readonly disabledPluginIds?: ReadonlyArray; + readonly effort?: V2ThreadSettingsUpdatedNotification__ReasoningEffort | null; + readonly model: string; + readonly modelProvider: string; + readonly personality?: V2ThreadSettingsUpdatedNotification__Personality | null; + readonly sandboxPolicy: V2ThreadSettingsUpdatedNotification__SandboxPolicy; + readonly serviceTier?: string | null; + readonly summary?: V2ThreadSettingsUpdatedNotification__ReasoningSummary | null; }; -export const V2ThreadRollbackResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__CodexErrorInfo, Schema.Null]), +export const V2ThreadSettingsUpdatedNotification__ThreadSettings = Schema.Struct({ + activePermissionProfile: Schema.optionalKey( + Schema.Union([V2ThreadSettingsUpdatedNotification__ActivePermissionProfile, Schema.Null]), ), - message: Schema.String, -}); + approvalPolicy: V2ThreadSettingsUpdatedNotification__AskForApproval, + approvalsReviewer: V2ThreadSettingsUpdatedNotification__ApprovalsReviewer, + collaborationMode: V2ThreadSettingsUpdatedNotification__CollaborationMode, + cwd: V2ThreadSettingsUpdatedNotification__AbsolutePathBuf, + disabledPluginIds: Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + description: "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.", + default: [], + }), + ), + effort: Schema.optionalKey( + Schema.Union([V2ThreadSettingsUpdatedNotification__ReasoningEffort, Schema.Null]), + ), + model: Schema.String, + modelProvider: Schema.String, + personality: Schema.optionalKey( + Schema.Union([V2ThreadSettingsUpdatedNotification__Personality, Schema.Null]).annotate({ + description: + "@deprecated Reports the saved setting; `friendly` and `pragmatic` no longer select a style.", + }), + ), + sandboxPolicy: V2ThreadSettingsUpdatedNotification__SandboxPolicy, + serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + summary: Schema.optionalKey( + Schema.Union([V2ThreadSettingsUpdatedNotification__ReasoningSummary, Schema.Null]), + ), +}).annotate({ identifier: "V2ThreadSettingsUpdatedNotification__ThreadSettings" }); -export type V2ThreadRollbackResponse__ThreadItem = +export type V2ThreadStartedNotification__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadStartedNotification__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadRollbackResponse__MemoryCitation | null; - readonly phase?: V2ThreadRollbackResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadStartedNotification__MemoryCitation | null; + readonly phase?: V2ThreadStartedNotification__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadStartedNotification__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -25343,135 +39303,140 @@ export type V2ThreadRollbackResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadStartedNotification__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadRollbackResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadStartedNotification__CommandExecutionSource; + 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 appContext?: V2ThreadRollbackResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadStartedNotification__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadRollbackResponse__McpToolCallError | null; + readonly error?: V2ThreadStartedNotification__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadStartedNotification__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadRollbackResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | 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 arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadRollbackResponse__DynamicToolCallStatus; + 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" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadStartedNotification__CollabAgentToolCallStatus; + readonly tool: V2ThreadStartedNotification__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadRollbackResponse__SubAgentActivityKind; + readonly kind: V2ThreadStartedNotification__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadRollbackResponse__WebSearchAction | null; + readonly action?: V2ThreadStartedNotification__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadRollbackResponse__LegacyAppPathString; + readonly path: V2ThreadStartedNotification__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadStartedNotification__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadRollbackResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadStartedNotification__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__AgentMessageDelivery, Schema.Null]), + ), 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, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2ThreadStartedNotification__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadStartedNotification__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -25497,17 +39462,20 @@ 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.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -25516,11 +39484,20 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -25529,65 +39506,80 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), - ), - status: V2ThreadRollbackResponse__CommandExecutionStatus, + Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__CommandExecutionSource, + ).annotate({ default: "agent" }), + ), + 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({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), 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.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadRollbackResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadStartedNotification__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -25596,13 +39588,13 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadRollbackResponse__DynamicToolCallStatus, + status: V2ThreadStartedNotification__DynamicToolCallStatus, success: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tool: Schema.String, type: Schema.Literal("dynamicToolCall").annotate({ title: "DynamicToolCallThreadItemType" }), @@ -25610,7 +39602,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( @@ -25630,7 +39622,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.", }), ), @@ -25641,20 +39633,14 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -25663,20 +39649,20 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadRollbackResponse__SubAgentActivityKind, + kind: V2ThreadStartedNotification__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -25687,13 +39673,17 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadRollbackResponse__LegacyAppPathString, + path: V2ThreadStartedNotification__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -25701,13 +39691,17 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadStartedNotification__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -25732,54 +39726,36 @@ export const V2ThreadRollbackResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadStartedNotification__ThreadItem" }); -export type V2ThreadSettingsUpdatedNotification__CollaborationMode = { - readonly mode: V2ThreadSettingsUpdatedNotification__ModeKind; - readonly settings: V2ThreadSettingsUpdatedNotification__Settings; -}; -export const V2ThreadSettingsUpdatedNotification__CollaborationMode = Schema.Struct({ - mode: V2ThreadSettingsUpdatedNotification__ModeKind, - settings: V2ThreadSettingsUpdatedNotification__Settings, -}).annotate({ description: "Collaboration mode for a Codex session." }); - -export type V2ThreadStartedNotification__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadStartedNotification__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadStartedNotification__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); - -export type V2ThreadStartedNotification__ThreadItem = +export type V2ThreadStartResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadStartResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadStartedNotification__MemoryCitation | null; - readonly phase?: V2ThreadStartedNotification__MessagePhase | null; + readonly memoryCitation?: V2ThreadStartResponse__MemoryCitation | null; + readonly phase?: V2ThreadStartResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadStartResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -25790,137 +39766,133 @@ export type V2ThreadStartedNotification__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadStartResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadStartedNotification__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadStartResponse__CommandExecutionSource; + 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 appContext?: V2ThreadStartedNotification__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadStartResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadStartedNotification__McpToolCallError | null; + readonly error?: V2ThreadStartResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadStartResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadStartedNotification__McpToolCallResult | null; + readonly readOnlyHint?: boolean | 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 arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadStartedNotification__DynamicToolCallStatus; + 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" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadStartResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadStartResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadStartedNotification__SubAgentActivityKind; + readonly kind: V2ThreadStartResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadStartedNotification__WebSearchAction | null; + readonly action?: V2ThreadStartResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadStartedNotification__LegacyAppPathString; + readonly path: V2ThreadStartResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadStartResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadStartedNotification__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadStartResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__AgentMessageDelivery, Schema.Null]), + ), 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]), ), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), + phase: Schema.optionalKey(Schema.Union([V2ThreadStartResponse__MessagePhase, Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2ThreadStartResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadStartResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -25946,17 +39918,20 @@ 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.suspend( + (): Schema.Codec => + V2ThreadStartResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -25965,11 +39940,20 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -25978,65 +39962,80 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__CommandExecutionSource, + ).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({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadStartResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), 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.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadStartedNotification__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadStartResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -26045,22 +40044,21 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadStartedNotification__DynamicToolCallStatus, + 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([ @@ -26079,7 +40077,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.", }), ), @@ -26090,20 +40088,14 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -26112,20 +40104,20 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadStartedNotification__SubAgentActivityKind, + kind: V2ThreadStartResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadStartResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -26136,13 +40128,17 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadStartedNotification__LegacyAppPathString, + path: V2ThreadStartResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -26150,13 +40146,17 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadStartedNotification__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadStartResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -26181,45 +40181,36 @@ export const V2ThreadStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2ThreadStartResponse__ThreadItem" }); -export type V2ThreadStartResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadStartResponse__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadStartResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); - -export type V2ThreadStartResponse__ThreadItem = +export type V2ThreadTurnsListResponse__ThreadItem = | { readonly clientId?: string | null; - 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 delivery?: V2ThreadTurnsListResponse__AgentMessageDelivery | null; readonly id: string; - readonly memoryCitation?: V2ThreadStartResponse__MemoryCitation | null; - readonly phase?: V2ThreadStartResponse__MessagePhase | null; + readonly memoryCitation?: V2ThreadTurnsListResponse__MemoryCitation | null; + readonly phase?: V2ThreadTurnsListResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadTurnsListResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -26230,133 +40221,138 @@ export type V2ThreadStartResponse__ThreadItem = | { readonly aggregatedOutput?: string | null; readonly command: string; - readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly commandActions: ReadonlyArray; + readonly cwd: V2ThreadTurnsListResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; - readonly status: V2ThreadStartResponse__CommandExecutionStatus; + readonly scriptPath?: string | null; + readonly source?: V2ThreadTurnsListResponse__CommandExecutionSource; + 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 appContext?: V2ThreadStartResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly appContext?: V2ThreadTurnsListResponse__McpToolCallAppContext | null; + readonly arguments: Schema.Json; readonly durationMs?: number | null; - readonly error?: V2ThreadStartResponse__McpToolCallError | null; + readonly error?: V2ThreadTurnsListResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadTurnsListResponse__McpAppUi | null; readonly pluginId?: string | null; - readonly result?: V2ThreadStartResponse__McpToolCallResult | null; + readonly readOnlyHint?: boolean | 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 arguments: Schema.Json; + readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; readonly namespace?: string | null; - readonly status: V2ThreadStartResponse__DynamicToolCallStatus; + 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" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadTurnsListResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadTurnsListResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { readonly agentPath: string; readonly agentThreadId: string; readonly id: string; - readonly kind: V2ThreadStartResponse__SubAgentActivityKind; + readonly kind: V2ThreadTurnsListResponse__SubAgentActivityKind; readonly type: "subAgentActivity"; } | { - readonly action?: V2ThreadStartResponse__WebSearchAction | null; + readonly action?: V2ThreadTurnsListResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { readonly id: string; - readonly path: V2ThreadStartResponse__LegacyAppPathString; + readonly path: V2ThreadTurnsListResponse__LegacyAppPathString; readonly type: "imageView"; } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadTurnsListResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; - readonly savedPath?: V2ThreadStartResponse__AbsolutePathBuf | null; + readonly savedPath?: V2ThreadTurnsListResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; 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({ clientId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - 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({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__AgentMessageDelivery, Schema.Null]), + ), 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, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2ThreadTurnsListResponse__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadTurnsListResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -26382,17 +40378,20 @@ 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.suspend( + (): Schema.Codec => + V2ThreadTurnsListResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -26401,11 +40400,20 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -26414,65 +40422,80 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadTurnsListResponse__CommandExecutionSource, + ).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({ appContext: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__McpToolCallAppContext, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), 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.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, 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" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ThreadStartResponse__DynamicToolCallOutputContentItem), + Schema.Array(V2ThreadTurnsListResponse__DynamicToolCallOutputContentItem), Schema.Null, ]), ), @@ -26481,21 +40504,22 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: V2ThreadStartResponse__DynamicToolCallStatus, + 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([ @@ -26514,7 +40538,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.", }), ), @@ -26525,20 +40549,14 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadTurnsListResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadTurnsListResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -26547,20 +40565,20 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( agentPath: Schema.String, agentThreadId: Schema.String, id: Schema.String, - kind: V2ThreadStartResponse__SubAgentActivityKind, + kind: V2ThreadTurnsListResponse__SubAgentActivityKind, type: Schema.Literal("subAgentActivity").annotate({ title: "SubAgentActivityThreadItemType", }), }).annotate({ title: "SubAgentActivityThreadItem" }), Schema.Struct({ action: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__WebSearchAction, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__WebSearchAction, Schema.Null]), ), id: Schema.String, query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -26571,13 +40589,17 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "WebSearchThreadItem" }), Schema.Struct({ id: Schema.String, - path: V2ThreadStartResponse__LegacyAppPathString, + path: V2ThreadTurnsListResponse__LegacyAppPathString, type: Schema.Literal("imageView").annotate({ title: "ImageViewThreadItemType" }), }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -26585,13 +40607,17 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), savedPath: Schema.optionalKey( - Schema.Union([V2ThreadStartResponse__AbsolutePathBuf, Schema.Null]), + Schema.Union([V2ThreadTurnsListResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -26616,20 +40642,7 @@ export const V2ThreadStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); - -export type V2ThreadUnarchiveResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2ThreadUnarchiveResponse__CodexErrorInfo | null; - readonly message: string; -}; -export const V2ThreadUnarchiveResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2ThreadUnarchiveResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); +).annotate({ identifier: "V2ThreadTurnsListResponse__ThreadItem" }); export type V2ThreadUnarchiveResponse__ThreadItem = | { @@ -26644,17 +40657,21 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly type: "hookPrompt"; } | { + readonly delivery?: V2ThreadUnarchiveResponse__AgentMessageDelivery | null; readonly id: string; readonly memoryCitation?: V2ThreadUnarchiveResponse__MemoryCitation | null; readonly phase?: V2ThreadUnarchiveResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2ThreadUnarchiveResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -26666,12 +40683,14 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly aggregatedOutput?: string | null; readonly command: string; readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly cwd: V2ThreadUnarchiveResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; + readonly scriptPath?: string | null; + readonly source?: V2ThreadUnarchiveResponse__CommandExecutionSource; readonly status: V2ThreadUnarchiveResponse__CommandExecutionStatus; readonly type: "commandExecution"; } @@ -26683,12 +40702,14 @@ export type V2ThreadUnarchiveResponse__ThreadItem = } | { readonly appContext?: V2ThreadUnarchiveResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly durationMs?: number | null; readonly error?: V2ThreadUnarchiveResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2ThreadUnarchiveResponse__McpAppUi | null; readonly pluginId?: string | null; + readonly readOnlyHint?: boolean | null; readonly result?: V2ThreadUnarchiveResponse__McpToolCallResult | null; readonly server: string; readonly status: V2ThreadUnarchiveResponse__McpToolCallStatus; @@ -26696,7 +40717,7 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; @@ -26714,17 +40735,8 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly reasoningEffort?: V2ThreadUnarchiveResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2ThreadUnarchiveResponse__CollabAgentToolCallStatus; + readonly tool: V2ThreadUnarchiveResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { @@ -26738,7 +40750,7 @@ export type V2ThreadUnarchiveResponse__ThreadItem = readonly action?: V2ThreadUnarchiveResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { @@ -26748,11 +40760,13 @@ export type V2ThreadUnarchiveResponse__ThreadItem = } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2ThreadUnarchiveResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; readonly savedPath?: V2ThreadUnarchiveResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } @@ -26772,6 +40786,9 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( Schema.Union([V2ThreadUnarchiveResponse__MemoryCitation, Schema.Null]), @@ -26779,21 +40796,24 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( phase: Schema.optionalKey( Schema.Union([V2ThreadUnarchiveResponse__MessagePhase, Schema.Null]), ), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2ThreadUnarchiveResponse__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2ThreadUnarchiveResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -26823,13 +40843,16 @@ 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.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -26838,11 +40861,20 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -26851,13 +40883,20 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__CommandExecutionSource, + ).annotate({ default: "agent" }), ), status: V2ThreadUnarchiveResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ @@ -26874,13 +40913,13 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( appContext: Schema.optionalKey( Schema.Union([V2ThreadUnarchiveResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -26891,12 +40930,20 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2ThreadUnarchiveResponse__McpToolCallResult, Schema.Null]), ), @@ -26906,7 +40953,7 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ Schema.Array(V2ThreadUnarchiveResponse__DynamicToolCallOutputContentItem), @@ -26918,7 +40965,7 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -26963,20 +41010,14 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -26998,7 +41039,7 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -27014,8 +41055,12 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -27023,6 +41068,9 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -27030,6 +41078,7 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( Schema.Union([V2ThreadUnarchiveResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -27054,20 +41103,7 @@ export const V2ThreadUnarchiveResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); - -export type V2TurnCompletedNotification__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2TurnCompletedNotification__CodexErrorInfo | null; - readonly message: string; -}; -export const V2TurnCompletedNotification__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2TurnCompletedNotification__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); +).annotate({ identifier: "V2ThreadUnarchiveResponse__ThreadItem" }); export type V2TurnCompletedNotification__ThreadItem = | { @@ -27082,17 +41118,21 @@ export type V2TurnCompletedNotification__ThreadItem = readonly type: "hookPrompt"; } | { + readonly delivery?: V2TurnCompletedNotification__AgentMessageDelivery | null; readonly id: string; readonly memoryCitation?: V2TurnCompletedNotification__MemoryCitation | null; readonly phase?: V2TurnCompletedNotification__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2TurnCompletedNotification__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -27104,12 +41144,14 @@ export type V2TurnCompletedNotification__ThreadItem = readonly aggregatedOutput?: string | null; readonly command: string; readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly cwd: V2TurnCompletedNotification__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; + readonly scriptPath?: string | null; + readonly source?: V2TurnCompletedNotification__CommandExecutionSource; readonly status: V2TurnCompletedNotification__CommandExecutionStatus; readonly type: "commandExecution"; } @@ -27121,12 +41163,14 @@ export type V2TurnCompletedNotification__ThreadItem = } | { readonly appContext?: V2TurnCompletedNotification__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly durationMs?: number | null; readonly error?: V2TurnCompletedNotification__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2TurnCompletedNotification__McpAppUi | null; readonly pluginId?: string | null; + readonly readOnlyHint?: boolean | null; readonly result?: V2TurnCompletedNotification__McpToolCallResult | null; readonly server: string; readonly status: V2TurnCompletedNotification__McpToolCallStatus; @@ -27134,7 +41178,7 @@ export type V2TurnCompletedNotification__ThreadItem = readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; @@ -27154,17 +41198,8 @@ export type V2TurnCompletedNotification__ThreadItem = readonly reasoningEffort?: V2TurnCompletedNotification__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2TurnCompletedNotification__CollabAgentToolCallStatus; + readonly tool: V2TurnCompletedNotification__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { @@ -27178,7 +41213,7 @@ export type V2TurnCompletedNotification__ThreadItem = readonly action?: V2TurnCompletedNotification__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { @@ -27188,11 +41223,13 @@ export type V2TurnCompletedNotification__ThreadItem = } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2TurnCompletedNotification__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; readonly savedPath?: V2TurnCompletedNotification__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } @@ -27212,6 +41249,9 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( Schema.Union([V2TurnCompletedNotification__MemoryCitation, Schema.Null]), @@ -27219,21 +41259,24 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( phase: Schema.optionalKey( Schema.Union([V2TurnCompletedNotification__MessagePhase, Schema.Null]), ), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2TurnCompletedNotification__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2TurnCompletedNotification__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -27263,13 +41306,16 @@ 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.suspend( + (): Schema.Codec => + V2TurnCompletedNotification__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -27278,11 +41324,20 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -27291,13 +41346,20 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2TurnCompletedNotification__CommandExecutionSource, + ).annotate({ default: "agent" }), ), status: V2TurnCompletedNotification__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ @@ -27314,13 +41376,13 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( appContext: Schema.optionalKey( Schema.Union([V2TurnCompletedNotification__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -27331,12 +41393,20 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2TurnCompletedNotification__McpToolCallResult, Schema.Null]), ), @@ -27346,7 +41416,7 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ Schema.Array(V2TurnCompletedNotification__DynamicToolCallOutputContentItem), @@ -27358,7 +41428,7 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -27403,20 +41473,14 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2TurnCompletedNotification__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2TurnCompletedNotification__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -27438,7 +41502,7 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -27454,8 +41518,12 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -27463,6 +41531,9 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2TurnCompletedNotification__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -27470,6 +41541,7 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( Schema.Union([V2TurnCompletedNotification__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -27494,20 +41566,7 @@ export const V2TurnCompletedNotification__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); - -export type V2TurnStartedNotification__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2TurnStartedNotification__CodexErrorInfo | null; - readonly message: string; -}; -export const V2TurnStartedNotification__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2TurnStartedNotification__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); +).annotate({ identifier: "V2TurnCompletedNotification__ThreadItem" }); export type V2TurnStartedNotification__ThreadItem = | { @@ -27522,17 +41581,21 @@ export type V2TurnStartedNotification__ThreadItem = readonly type: "hookPrompt"; } | { + readonly delivery?: V2TurnStartedNotification__AgentMessageDelivery | null; readonly id: string; readonly memoryCitation?: V2TurnStartedNotification__MemoryCitation | null; readonly phase?: V2TurnStartedNotification__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2TurnStartedNotification__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -27544,12 +41607,14 @@ export type V2TurnStartedNotification__ThreadItem = readonly aggregatedOutput?: string | null; readonly command: string; readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly cwd: V2TurnStartedNotification__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; + readonly scriptPath?: string | null; + readonly source?: V2TurnStartedNotification__CommandExecutionSource; readonly status: V2TurnStartedNotification__CommandExecutionStatus; readonly type: "commandExecution"; } @@ -27561,12 +41626,14 @@ export type V2TurnStartedNotification__ThreadItem = } | { readonly appContext?: V2TurnStartedNotification__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly durationMs?: number | null; readonly error?: V2TurnStartedNotification__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2TurnStartedNotification__McpAppUi | null; readonly pluginId?: string | null; + readonly readOnlyHint?: boolean | null; readonly result?: V2TurnStartedNotification__McpToolCallResult | null; readonly server: string; readonly status: V2TurnStartedNotification__McpToolCallStatus; @@ -27574,7 +41641,7 @@ export type V2TurnStartedNotification__ThreadItem = readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; @@ -27592,17 +41659,8 @@ export type V2TurnStartedNotification__ThreadItem = readonly reasoningEffort?: V2TurnStartedNotification__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2TurnStartedNotification__CollabAgentToolCallStatus; + readonly tool: V2TurnStartedNotification__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { @@ -27616,7 +41674,7 @@ export type V2TurnStartedNotification__ThreadItem = readonly action?: V2TurnStartedNotification__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { @@ -27626,11 +41684,13 @@ export type V2TurnStartedNotification__ThreadItem = } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2TurnStartedNotification__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; readonly savedPath?: V2TurnStartedNotification__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } @@ -27650,6 +41710,9 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( Schema.Union([V2TurnStartedNotification__MemoryCitation, Schema.Null]), @@ -27657,21 +41720,24 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( phase: Schema.optionalKey( Schema.Union([V2TurnStartedNotification__MessagePhase, Schema.Null]), ), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), + Schema.Array(V2TurnStartedNotification__AsyncUserInputQuestion), Schema.Null, ]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2TurnStartedNotification__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -27701,13 +41767,16 @@ 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.suspend( + (): Schema.Codec => + V2TurnStartedNotification__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -27716,11 +41785,20 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -27729,13 +41807,20 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2TurnStartedNotification__CommandExecutionSource, + ).annotate({ default: "agent" }), ), status: V2TurnStartedNotification__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ @@ -27752,13 +41837,13 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( appContext: Schema.optionalKey( Schema.Union([V2TurnStartedNotification__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -27769,12 +41854,20 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2TurnStartedNotification__McpToolCallResult, Schema.Null]), ), @@ -27784,7 +41877,7 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ Schema.Array(V2TurnStartedNotification__DynamicToolCallOutputContentItem), @@ -27796,7 +41889,7 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -27841,20 +41934,14 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2TurnStartedNotification__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2TurnStartedNotification__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -27876,7 +41963,7 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -27892,8 +41979,12 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -27901,6 +41992,9 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2TurnStartedNotification__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -27908,6 +42002,7 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( Schema.Union([V2TurnStartedNotification__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -27932,20 +42027,18 @@ export const V2TurnStartedNotification__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnStartedNotification__ThreadItem" }); -export type V2TurnStartResponse__TurnError = { - readonly additionalDetails?: string | null; - readonly codexErrorInfo?: V2TurnStartResponse__CodexErrorInfo | null; - readonly message: string; +export type V2TurnStartParams__TurnToolOutput = { + readonly name: string; + readonly namespace?: string | null; + readonly output: V2TurnStartParams__FunctionCallOutputBody; }; -export const V2TurnStartResponse__TurnError = Schema.Struct({ - additionalDetails: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - codexErrorInfo: Schema.optionalKey( - Schema.Union([V2TurnStartResponse__CodexErrorInfo, Schema.Null]), - ), - message: Schema.String, -}); +export const V2TurnStartParams__TurnToolOutput = Schema.Struct({ + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2TurnStartParams__FunctionCallOutputBody, +}).annotate({ identifier: "V2TurnStartParams__TurnToolOutput" }); export type V2TurnStartResponse__ThreadItem = | { @@ -27960,17 +42053,21 @@ export type V2TurnStartResponse__ThreadItem = readonly type: "hookPrompt"; } | { + readonly delivery?: V2TurnStartResponse__AgentMessageDelivery | null; readonly id: string; readonly memoryCitation?: V2TurnStartResponse__MemoryCitation | null; readonly phase?: V2TurnStartResponse__MessagePhase | null; + readonly questions?: ReadonlyArray | null; readonly text: string; - readonly delivery?: "async" | null; - readonly questions?: ReadonlyArray<{ - readonly title: string; - readonly options?: ReadonlyArray | null; - }> | null; readonly type: "agentMessage"; } + | { + readonly id: string; + readonly name: string; + readonly namespace?: string | null; + readonly output: V2TurnStartResponse__FunctionCallOutputBody; + readonly type: "functionCallOutput"; + } | { readonly id: string; readonly text: string; readonly type: "plan" } | { readonly content?: ReadonlyArray; @@ -27982,12 +42079,14 @@ export type V2TurnStartResponse__ThreadItem = readonly aggregatedOutput?: string | null; readonly command: string; readonly commandActions: ReadonlyArray; - readonly cwd: string; + readonly cwd: V2TurnStartResponse__LegacyAppPathString; readonly durationMs?: number | null; readonly exitCode?: number | null; readonly id: string; + readonly pluginId?: string | null; readonly processId?: string | null; - readonly source?: "agent" | "userShell" | "unifiedExecStartup" | "unifiedExecInteraction"; + readonly scriptPath?: string | null; + readonly source?: V2TurnStartResponse__CommandExecutionSource; readonly status: V2TurnStartResponse__CommandExecutionStatus; readonly type: "commandExecution"; } @@ -27999,12 +42098,14 @@ export type V2TurnStartResponse__ThreadItem = } | { readonly appContext?: V2TurnStartResponse__McpToolCallAppContext | null; - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly durationMs?: number | null; readonly error?: V2TurnStartResponse__McpToolCallError | null; readonly id: string; readonly mcpAppResourceUri?: string | null; + readonly mcpAppUi?: V2TurnStartResponse__McpAppUi | null; readonly pluginId?: string | null; + readonly readOnlyHint?: boolean | null; readonly result?: V2TurnStartResponse__McpToolCallResult | null; readonly server: string; readonly status: V2TurnStartResponse__McpToolCallStatus; @@ -28012,7 +42113,7 @@ export type V2TurnStartResponse__ThreadItem = readonly type: "mcpToolCall"; } | { - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly contentItems?: ReadonlyArray | null; readonly durationMs?: number | null; readonly id: string; @@ -28030,17 +42131,8 @@ export type V2TurnStartResponse__ThreadItem = readonly reasoningEffort?: V2TurnStartResponse__ReasoningEffort | null; readonly receiverThreadIds: ReadonlyArray; readonly senderThreadId: string; - readonly status: "inProgress" | "completed" | "failed" | "interrupted"; - readonly tool: - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; + readonly status: V2TurnStartResponse__CollabAgentToolCallStatus; + readonly tool: V2TurnStartResponse__CollabAgentTool; readonly type: "collabAgentToolCall"; } | { @@ -28054,7 +42146,7 @@ export type V2TurnStartResponse__ThreadItem = readonly action?: V2TurnStartResponse__WebSearchAction | null; readonly id: string; readonly query: string; - readonly results?: ReadonlyArray | null; + readonly results?: ReadonlyArray | null; readonly type: "webSearch"; } | { @@ -28064,11 +42156,13 @@ export type V2TurnStartResponse__ThreadItem = } | { readonly durationMs: number; readonly id: string; readonly type: "sleep" } | { + readonly failure?: V2TurnStartResponse__ImageGenerationFailure | null; readonly id: string; readonly result: string; readonly revisedPrompt?: string | null; readonly savedPath?: V2TurnStartResponse__AbsolutePathBuf | null; readonly status: string; + readonly transparentBackground?: boolean | null; readonly type: "imageGeneration"; } | { readonly id: string; readonly review: string; readonly type: "enteredReviewMode" } @@ -28088,26 +42182,29 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( type: Schema.Literal("hookPrompt").annotate({ title: "HookPromptThreadItemType" }), }).annotate({ title: "HookPromptThreadItem" }), Schema.Struct({ + delivery: Schema.optionalKey( + Schema.Union([V2TurnStartResponse__AgentMessageDelivery, Schema.Null]), + ), id: Schema.String, memoryCitation: Schema.optionalKey( Schema.Union([V2TurnStartResponse__MemoryCitation, Schema.Null]), ), phase: Schema.optionalKey(Schema.Union([V2TurnStartResponse__MessagePhase, Schema.Null])), - text: Schema.String, - delivery: Schema.optionalKey(Schema.Union([Schema.Literal("async"), Schema.Null])), questions: Schema.optionalKey( - Schema.Union([ - Schema.Array( - Schema.Struct({ - title: Schema.String, - options: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), - }), - ), - Schema.Null, - ]), + Schema.Union([Schema.Array(V2TurnStartResponse__AsyncUserInputQuestion), Schema.Null]), ), + text: Schema.String, type: Schema.Literal("agentMessage").annotate({ title: "AgentMessageThreadItemType" }), }).annotate({ title: "AgentMessageThreadItem" }), + Schema.Struct({ + id: Schema.String, + name: Schema.String, + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + output: V2TurnStartResponse__FunctionCallOutputBody, + type: Schema.Literal("functionCallOutput").annotate({ + title: "FunctionCallOutputThreadItemType", + }), + }).annotate({ title: "FunctionCallOutputThreadItem" }), Schema.Struct({ id: Schema.String, text: Schema.String, @@ -28137,13 +42234,16 @@ 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.suspend( + (): Schema.Codec => + V2TurnStartResponse__LegacyAppPathString, + ).annotate({ description: "The command's working directory." }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the command execution in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -28152,11 +42252,20 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The command's exit code.", format: "int32", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), id: Schema.String, + pluginId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Trusted first-party plugin id when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), processId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -28165,13 +42274,20 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( Schema.Null, ]), ), + scriptPath: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Safe plugin-relative path when this command resolves to one plugin script.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( - Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", - ]).annotate({ default: "agent" }), + Schema.suspend( + (): Schema.Codec => + V2TurnStartResponse__CommandExecutionSource, + ).annotate({ default: "agent" }), ), status: V2TurnStartResponse__CommandExecutionStatus, type: Schema.Literal("commandExecution").annotate({ @@ -28188,13 +42304,13 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( appContext: Schema.optionalKey( Schema.Union([V2TurnStartResponse__McpToolCallAppContext, Schema.Null]), ), - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), durationMs: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "The duration of the MCP tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -28203,12 +42319,20 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( mcpAppResourceUri: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Deprecated: use `appContext.resourceUri` instead.", + description: + "Legacy compatibility field; prefer `mcpAppUi.resourceUri` when available.", }), Schema.Null, ]), ), + mcpAppUi: Schema.optionalKey( + Schema.Union([V2TurnStartResponse__McpAppUi, Schema.Null]).annotate({ + description: + "Presentation captured from the invoked descriptor; absent in older history.", + }), + ), pluginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + readOnlyHint: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), result: Schema.optionalKey( Schema.Union([V2TurnStartResponse__McpToolCallResult, Schema.Null]), ), @@ -28218,7 +42342,7 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( type: Schema.Literal("mcpToolCall").annotate({ title: "McpToolCallThreadItemType" }), }).annotate({ title: "McpToolCallThreadItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), contentItems: Schema.optionalKey( Schema.Union([ Schema.Array(V2TurnStartResponse__DynamicToolCallOutputContentItem), @@ -28230,7 +42354,7 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( Schema.Number.annotate({ description: "The duration of the dynamic tool call in milliseconds.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -28274,20 +42398,14 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( senderThreadId: Schema.String.annotate({ description: "Thread ID of the agent issuing the collab request.", }), - status: Schema.Literals(["inProgress", "completed", "failed", "interrupted"]).annotate({ - description: "Current status of the collab tool call.", - }), - tool: Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", - ]).annotate({ description: "Name of the collab tool that was invoked." }), + status: Schema.suspend( + (): Schema.Codec => + V2TurnStartResponse__CollabAgentToolCallStatus, + ).annotate({ description: "Current status of the collab tool call." }), + tool: Schema.suspend( + (): Schema.Codec => + V2TurnStartResponse__CollabAgentTool, + ).annotate({ description: "Name of the collab tool that was invoked." }), type: Schema.Literal("collabAgentToolCall").annotate({ title: "CollabAgentToolCallThreadItemType", }), @@ -28307,7 +42425,7 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( query: Schema.String, results: Schema.optionalKey( Schema.Union([ - Schema.Array(Schema.Unknown).annotate({ + Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).annotate({ description: "Structured search results returned out-of-band by standalone web search.\n\nThese stay as opaque JSON at the extension/app-server boundary so new result fields and result types can pass through without a Codex release.", }), @@ -28323,8 +42441,12 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "ImageViewThreadItem" }), Schema.Struct({ durationMs: Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), id: Schema.String, type: Schema.Literal("sleep").annotate({ title: "SleepThreadItemType" }), }).annotate({ @@ -28332,6 +42454,9 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( description: "Display item emitted by the interruptible `clock.sleep` tool.", }), Schema.Struct({ + failure: Schema.optionalKey( + Schema.Union([V2TurnStartResponse__ImageGenerationFailure, Schema.Null]), + ), id: Schema.String, result: Schema.String, revisedPrompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -28339,6 +42464,7 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( Schema.Union([V2TurnStartResponse__AbsolutePathBuf, Schema.Null]), ), status: Schema.String, + transparentBackground: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), type: Schema.Literal("imageGeneration").annotate({ title: "ImageGenerationThreadItemType" }), }).annotate({ title: "ImageGenerationThreadItem" }), Schema.Struct({ @@ -28363,200 +42489,275 @@ export const V2TurnStartResponse__ThreadItem = Schema.Union( }).annotate({ title: "ContextCompactionThreadItem" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "V2TurnStartResponse__ThreadItem" }); -export type ClientRequest__ExternalAgentConfigImportParams = { - readonly migrationItems: ReadonlyArray; - readonly migrationSource?: string | null; - readonly source?: string | null; +export type ClientRequest__TurnStartParams = { + readonly approvalPolicy?: ClientRequest__AskForApproval | null; + readonly approvalsReviewer?: ClientRequest__ApprovalsReviewer | null; + readonly clientUserMessageId?: string | null; + readonly cwd?: string | null; + readonly disabledPluginIds?: ReadonlyArray | null; + readonly effort?: ClientRequest__ReasoningEffort | null; + readonly input: ReadonlyArray; + readonly model?: string | null; + readonly outputSchema?: Schema.Json; + readonly personality?: ClientRequest__Personality | null; + readonly sandboxPolicy?: ClientRequest__SandboxPolicy | null; + readonly serviceTier?: string | null; + readonly serviceTierForTurn?: string | null; + readonly summary?: ClientRequest__ReasoningSummary | null; + readonly threadId: string; + readonly toolOutput?: ClientRequest__TurnToolOutput | null; + readonly turnTrigger?: string | null; }; -export const ClientRequest__ExternalAgentConfigImportParams = Schema.Struct({ - migrationItems: Schema.Array(ClientRequest__ExternalAgentConfigMigrationItem), - migrationSource: Schema.optionalKey( +export const ClientRequest__TurnStartParams = Schema.Struct({ + approvalPolicy: Schema.optionalKey( + Schema.Union([ClientRequest__AskForApproval, Schema.Null]).annotate({ + description: "Override the approval policy for this turn and subsequent turns.", + }), + ), + approvalsReviewer: Schema.optionalKey( + Schema.Union([ClientRequest__ApprovalsReviewer, Schema.Null]).annotate({ + description: + "Override where approval requests are routed for review on this turn and subsequent turns.", + }), + ), + clientUserMessageId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + cwd: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ + description: "Override the working directory for this turn and subsequent turns.", + }), + Schema.Null, + ]), + ), + disabledPluginIds: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ description: - "Migration-source selector used to produce the migration items. Pass the same value to detection and import; missing or unrecognized values use the default source.", + "Replace this thread's disabled plugin IDs. Omitted/null preserves the list; [] clears it.", }), Schema.Null, ]), ), - source: Schema.optionalKey( + effort: Schema.optionalKey( + Schema.Union([ClientRequest__ReasoningEffort, Schema.Null]).annotate({ + description: "Override the reasoning effort for this turn and subsequent turns.", + }), + ), + input: Schema.Array(ClientRequest__UserInput), + model: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Optional identifier for the product that initiated the import.", + description: "Override the model for this turn and subsequent turns.", }), Schema.Null, ]), ), -}); - -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 McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema = - | McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema - | McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema; -export const McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema = Schema.Union([ - McpServerElicitationRequestParams__McpElicitationUntitledMultiSelectEnumSchema, - McpServerElicitationRequestParams__McpElicitationTitledMultiSelectEnumSchema, -]); - -export type PermissionsRequestApprovalParams__FileSystemSandboxEntry = { - readonly access: PermissionsRequestApprovalParams__FileSystemAccessMode; - readonly path: PermissionsRequestApprovalParams__FileSystemPath; -}; -export const PermissionsRequestApprovalParams__FileSystemSandboxEntry = Schema.Struct({ - access: PermissionsRequestApprovalParams__FileSystemAccessMode, - path: PermissionsRequestApprovalParams__FileSystemPath, -}); - -export type PermissionsRequestApprovalResponse__FileSystemSandboxEntry = { - readonly access: PermissionsRequestApprovalResponse__FileSystemAccessMode; - readonly path: PermissionsRequestApprovalResponse__FileSystemPath; -}; -export const PermissionsRequestApprovalResponse__FileSystemSandboxEntry = Schema.Struct({ - access: PermissionsRequestApprovalResponse__FileSystemAccessMode, - path: PermissionsRequestApprovalResponse__FileSystemPath, -}); - -export type ServerNotification__AppListUpdatedNotification = { - readonly data: ReadonlyArray; -}; -export const ServerNotification__AppListUpdatedNotification = Schema.Struct({ - data: Schema.Array(ServerNotification__AppInfo), -}).annotate({ description: "EXPERIMENTAL - notification emitted when the app list changes." }); - -export type ServerNotification__ExternalAgentConfigImportCompletedNotification = { - readonly importId: string; - readonly itemTypeResults: ReadonlyArray; -}; -export const ServerNotification__ExternalAgentConfigImportCompletedNotification = Schema.Struct({ - importId: Schema.String, - itemTypeResults: Schema.Array(ServerNotification__ExternalAgentConfigImportTypeResult), -}); - -export type ServerNotification__ExternalAgentConfigImportProgressNotification = { - readonly importId: string; - readonly itemTypeResults: ReadonlyArray; -}; -export const ServerNotification__ExternalAgentConfigImportProgressNotification = Schema.Struct({ - importId: Schema.String, - itemTypeResults: Schema.Array(ServerNotification__ExternalAgentConfigImportTypeResult), -}); - -export type ServerNotification__HookCompletedNotification = { - readonly run: ServerNotification__HookRunSummary; - readonly threadId: string; - readonly turnId?: string | null; -}; -export const ServerNotification__HookCompletedNotification = Schema.Struct({ - run: ServerNotification__HookRunSummary, - threadId: Schema.String, - turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type ServerNotification__HookStartedNotification = { - readonly run: ServerNotification__HookRunSummary; - readonly threadId: string; - readonly turnId?: string | null; -}; -export const ServerNotification__HookStartedNotification = Schema.Struct({ - run: ServerNotification__HookRunSummary, + outputSchema: Schema.optionalKey( + Schema.Json.annotate({ + expected: "JSON value", + description: + "Optional JSON Schema used to constrain the final assistant message for this turn.", + }), + ), + personality: Schema.optionalKey( + Schema.Union([ClientRequest__Personality, Schema.Null]).annotate({ + description: + "@deprecated `friendly` and `pragmatic` no longer select a style. Changing this does not rewrite the thread's existing instructions.", + }), + ), + sandboxPolicy: Schema.optionalKey( + Schema.Union([ClientRequest__SandboxPolicy, Schema.Null]).annotate({ + description: "Override the sandbox policy for this turn and subsequent turns.", + }), + ), + serviceTier: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Override the service tier for this turn and subsequent turns.", + }), + Schema.Null, + ]), + ), + serviceTierForTurn: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Override the service tier only when this request starts a new turn. Use \"default\" for standard speed. Omitted or null inherits the thread's tier. Does not change the thread's tier or a turn being steered.", + }), + Schema.Null, + ]), + ), + summary: Schema.optionalKey( + Schema.Union([ClientRequest__ReasoningSummary, Schema.Null]).annotate({ + description: "Override the reasoning summary for this turn and subsequent turns.", + }), + ), threadId: Schema.String, - turnId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); + toolOutput: Schema.optionalKey(Schema.Union([ClientRequest__TurnToolOutput, Schema.Null])), + turnTrigger: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional source classification for the caller that starts this turn. Ignored when this request steers an already-active turn.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ClientRequest__TurnStartParams" }); -export type ServerNotification__FileSystemSandboxEntry = { - readonly access: ServerNotification__FileSystemAccessMode; - readonly path: ServerNotification__FileSystemPath; +export type CommandExecutionRequestApprovalParams__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; }; -export const ServerNotification__FileSystemSandboxEntry = Schema.Struct({ - access: ServerNotification__FileSystemAccessMode, - path: ServerNotification__FileSystemPath, +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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(CommandExecutionRequestApprovalParams__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(CommandExecutionRequestApprovalParams__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), + }, +).annotate({ + identifier: "CommandExecutionRequestApprovalParams__AdditionalFileSystemPermissions", }); -export type ServerNotification__ErrorNotification = { - readonly error: ServerNotification__TurnError; - readonly threadId: string; - readonly turnId: string; - readonly willRetry: boolean; -}; -export const ServerNotification__ErrorNotification = Schema.Struct({ - error: ServerNotification__TurnError, - threadId: Schema.String, - turnId: Schema.String, - willRetry: Schema.Boolean, -}); +export type McpServerElicitationRequestParams__McpElicitationEnumSchema = + | McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema + | McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema + | McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema; +export const McpServerElicitationRequestParams__McpElicitationEnumSchema = Schema.Union([ + McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema, + McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema, + McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema, +]).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationEnumSchema" }); -export type ServerNotification__ThreadSettings = { - readonly activePermissionProfile?: ServerNotification__ActivePermissionProfile | null; - readonly approvalPolicy: ServerNotification__AskForApproval; - readonly approvalsReviewer: ServerNotification__ApprovalsReviewer; - readonly collaborationMode: ServerNotification__CollaborationMode; - readonly cwd: ServerNotification__AbsolutePathBuf; - readonly effort?: ServerNotification__ReasoningEffort | null; - readonly model: string; - readonly modelProvider: string; - readonly personality?: ServerNotification__Personality | null; - readonly sandboxPolicy: ServerNotification__SandboxPolicy; - readonly serviceTier?: string | null; - readonly summary?: ServerNotification__ReasoningSummary | null; -}; -export const ServerNotification__ThreadSettings = Schema.Struct({ - activePermissionProfile: Schema.optionalKey( - Schema.Union([ServerNotification__ActivePermissionProfile, Schema.Null]), +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, + ]), ), - approvalPolicy: ServerNotification__AskForApproval, - approvalsReviewer: ServerNotification__ApprovalsReviewer, - collaborationMode: ServerNotification__CollaborationMode, - cwd: ServerNotification__AbsolutePathBuf, - effort: Schema.optionalKey(Schema.Union([ServerNotification__ReasoningEffort, Schema.Null])), - model: Schema.String, - modelProvider: Schema.String, - personality: Schema.optionalKey(Schema.Union([ServerNotification__Personality, Schema.Null])), - sandboxPolicy: ServerNotification__SandboxPolicy, - serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - summary: Schema.optionalKey(Schema.Union([ServerNotification__ReasoningSummary, Schema.Null])), -}); + globScanMaxDepth: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(PermissionsRequestApprovalParams__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(PermissionsRequestApprovalParams__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "PermissionsRequestApprovalParams__AdditionalFileSystemPermissions" }); -export type ServerNotification__ItemCompletedNotification = { - readonly completedAtMs: number; - readonly item: ServerNotification__ThreadItem; - readonly threadId: string; - readonly turnId: string; +export type PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; }; -export const ServerNotification__ItemCompletedNotification = Schema.Struct({ - completedAtMs: Schema.Number.annotate({ - description: "Unix timestamp (in milliseconds) when this item lifecycle completed.", - format: "int64", - }).check(Schema.isInt()), - item: ServerNotification__ThreadItem, - threadId: Schema.String, - turnId: Schema.String, -}); +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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(PermissionsRequestApprovalResponse__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(PermissionsRequestApprovalResponse__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions" }); -export type ServerNotification__ItemStartedNotification = { - readonly item: ServerNotification__ThreadItem; - readonly startedAtMs: number; +export type ServerNotification__ThreadSettingsUpdatedNotification = { readonly threadId: string; - readonly turnId: string; + readonly threadSettings: ServerNotification__ThreadSettings; }; -export const ServerNotification__ItemStartedNotification = Schema.Struct({ - item: ServerNotification__ThreadItem, - startedAtMs: Schema.Number.annotate({ - description: "Unix timestamp (in milliseconds) when this item lifecycle started.", - format: "int64", - }).check(Schema.isInt()), +export const ServerNotification__ThreadSettingsUpdatedNotification = Schema.Struct({ threadId: Schema.String, - turnId: Schema.String, -}); + threadSettings: ServerNotification__ThreadSettings, +}).annotate({ identifier: "ServerNotification__ThreadSettingsUpdatedNotification" }); export type ServerNotification__Turn = { readonly completedAt?: number | null; @@ -28564,7 +42765,7 @@ export type ServerNotification__Turn = { readonly error?: ServerNotification__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: ServerNotification__TurnItemsView; readonly startedAt?: number | null; readonly status: ServerNotification__TurnStatus; }; @@ -28574,7 +42775,7 @@ export const ServerNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -28583,7 +42784,7 @@ export const ServerNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -28599,7 +42800,9 @@ export const ServerNotification__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => ServerNotification__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -28609,36 +42812,143 @@ export const ServerNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: ServerNotification__TurnStatus, -}); +}).annotate({ identifier: "ServerNotification__Turn" }); -export type ServerRequest__FileSystemSandboxEntry = { - readonly access: ServerRequest__FileSystemAccessMode; - readonly path: ServerRequest__FileSystemPath; +export type ServerNotification__ItemStartedNotification = { + readonly item: ServerNotification__ThreadItem; + readonly startedAtMs: number; + readonly threadId: string; + readonly turnId: string; }; -export const ServerRequest__FileSystemSandboxEntry = Schema.Struct({ - access: ServerRequest__FileSystemAccessMode, - path: ServerRequest__FileSystemPath, -}); +export const ServerNotification__ItemStartedNotification = Schema.Struct({ + item: ServerNotification__ThreadItem, + startedAtMs: Schema.Number.annotate({ + description: "Unix timestamp (in milliseconds) when this item lifecycle started.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__ItemStartedNotification" }); -export type ServerRequest__McpElicitationMultiSelectEnumSchema = - | ServerRequest__McpElicitationUntitledMultiSelectEnumSchema - | ServerRequest__McpElicitationTitledMultiSelectEnumSchema; -export const ServerRequest__McpElicitationMultiSelectEnumSchema = Schema.Union([ - ServerRequest__McpElicitationUntitledMultiSelectEnumSchema, - ServerRequest__McpElicitationTitledMultiSelectEnumSchema, -]); +export type ServerNotification__ItemCompletedNotification = { + readonly completedAtMs: number; + readonly item: ServerNotification__ThreadItem; + readonly threadId: string; + readonly turnId: string; +}; +export const ServerNotification__ItemCompletedNotification = Schema.Struct({ + completedAtMs: Schema.Number.annotate({ + description: "Unix timestamp (in milliseconds) when this item lifecycle completed.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + item: ServerNotification__ThreadItem, + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ identifier: "ServerNotification__ItemCompletedNotification" }); + +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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(ServerNotification__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(ServerNotification__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ServerNotification__AdditionalFileSystemPermissions" }); + +export type ServerRequest__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; +}; +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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(ServerRequest__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(ServerRequest__LegacyAppPathString).annotate({ + description: "This will be removed in favor of `entries`.", + }), + Schema.Null, + ]), + ), +}).annotate({ identifier: "ServerRequest__AdditionalFileSystemPermissions" }); + +export type ServerRequest__McpElicitationEnumSchema = + | ServerRequest__McpElicitationSingleSelectEnumSchema + | ServerRequest__McpElicitationMultiSelectEnumSchema + | ServerRequest__McpElicitationLegacyTitledEnumSchema; +export const ServerRequest__McpElicitationEnumSchema = Schema.Union([ + ServerRequest__McpElicitationSingleSelectEnumSchema, + ServerRequest__McpElicitationMultiSelectEnumSchema, + ServerRequest__McpElicitationLegacyTitledEnumSchema, +]).annotate({ identifier: "ServerRequest__McpElicitationEnumSchema" }); export type V2ConfigReadResponse__Config = { readonly analytics?: V2ConfigReadResponse__AnalyticsConfig | null; readonly approval_policy?: V2ConfigReadResponse__AskForApproval | null; readonly approvals_reviewer?: V2ConfigReadResponse__ApprovalsReviewer | null; + readonly browser_use?: V2ConfigReadResponse__BrowserUseConfig | null; readonly compact_prompt?: string | null; - readonly desktop?: { readonly [x: string]: unknown } | null; + readonly computer_use?: V2ConfigReadResponse__ComputerUseConfig | null; + readonly desktop?: { readonly [x: string]: Schema.Json } | null; readonly developer_instructions?: string | null; readonly forced_chatgpt_workspace_id?: V2ConfigReadResponse__ForcedChatgptWorkspaceIds | null; readonly forced_login_method?: V2ConfigReadResponse__ForcedLoginMethod | null; @@ -28657,8 +42967,7 @@ export type V2ConfigReadResponse__Config = { readonly service_tier?: string | null; readonly tools?: V2ConfigReadResponse__ToolsV2 | null; readonly web_search?: V2ConfigReadResponse__WebSearchMode | null; - readonly [x: string]: unknown; -}; +} & { readonly [x: string]: Schema.Json }; export const V2ConfigReadResponse__Config = Schema.StructWithRest( Schema.Struct({ analytics: Schema.optionalKey( @@ -28673,9 +42982,18 @@ export const V2ConfigReadResponse__Config = Schema.StructWithRest( "[UNSTABLE] Optional default for where approval requests are routed for review.", }), ), + browser_use: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__BrowserUseConfig, Schema.Null]), + ), compact_prompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + computer_use: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__ComputerUseConfig, Schema.Null]), + ), desktop: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.Unknown), Schema.Null]), + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + Schema.Null, + ]), ), developer_instructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), forced_chatgpt_workspace_id: Schema.optionalKey( @@ -28688,7 +43006,9 @@ export const V2ConfigReadResponse__Config = Schema.StructWithRest( model: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), model_auto_compact_token_limit: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), Schema.Null, ]), ), @@ -28697,7 +43017,9 @@ export const V2ConfigReadResponse__Config = Schema.StructWithRest( ), model_context_window: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), Schema.Null, ]), ), @@ -28724,31 +43046,59 @@ export const V2ConfigReadResponse__Config = Schema.StructWithRest( Schema.Union([V2ConfigReadResponse__WebSearchMode, Schema.Null]), ), }), - [Schema.Record(Schema.String, Schema.Unknown)], -); + [Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" }))], +).annotate({ identifier: "V2ConfigReadResponse__Config" }); export type V2ConfigRequirementsReadResponse__ConfigRequirements = { + readonly additionalDeveloperInstructions?: string | null; readonly allowAppshots?: boolean | null; + readonly allowBrowserAndComputerUse?: boolean | null; + readonly allowLoginShell?: boolean | null; readonly allowManagedHooksOnly?: boolean | null; readonly allowRemoteControl?: boolean | null; readonly allowedApprovalPolicies?: ReadonlyArray | null; + readonly allowedLoginMethods?: ReadonlyArray | null; readonly allowedPermissionProfiles?: { readonly [x: string]: boolean } | null; readonly allowedSandboxModes?: ReadonlyArray | null; readonly allowedWebSearchModes?: ReadonlyArray | null; - readonly allowedWindowsSandboxImplementations?: ReadonlyArray | null; + readonly allowedWindowsSandboxImplementations?: ReadonlyArray | null; + readonly autoReview?: V2ConfigRequirementsReadResponse__AutoReviewRequirements | null; + readonly browserUse?: V2ConfigRequirementsReadResponse__BrowserUseRequirements | null; + readonly chatgptBaseUrl?: string | null; + readonly checkForUpdateOnStartup?: boolean | null; + readonly cliAuthCredentialsStore?: V2ConfigRequirementsReadResponse__CliAuthCredentialsStoreMode | null; readonly computerUse?: V2ConfigRequirementsReadResponse__ComputerUseRequirements | null; readonly defaultPermissions?: string | null; readonly enforceResidency?: V2ConfigRequirementsReadResponse__ResidencyRequirement | null; readonly featureRequirements?: { readonly [x: string]: boolean } | null; + readonly feedback?: V2ConfigRequirementsReadResponse__FeedbackRequirements | null; + readonly inAppBrowser?: V2ConfigRequirementsReadResponse__InAppBrowserRequirements | null; + readonly logDir?: string | null; + readonly modelCatalogJson?: string | null; + readonly modelProvider?: string | null; + readonly modelProviders?: { readonly [x: string]: Schema.Json } | null; readonly models?: V2ConfigRequirementsReadResponse__ModelsRequirements | null; + readonly sqliteHome?: string | null; }; export const V2ConfigRequirementsReadResponse__ConfigRequirements = Schema.Struct({ + additionalDeveloperInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), allowAppshots: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + allowBrowserAndComputerUse: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + allowLoginShell: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), allowManagedHooksOnly: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), allowRemoteControl: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), allowedApprovalPolicies: Schema.optionalKey( Schema.Union([Schema.Array(V2ConfigRequirementsReadResponse__AskForApproval), Schema.Null]), ), + allowedLoginMethods: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ConfigRequirementsReadResponse__ForcedLoginMethod).annotate({ + description: + "Effective login methods after managed, forced-login, and workspace restrictions. An empty list permits no login method. Older servers may omit this field.", + }), + Schema.Null, + ]), + ), allowedPermissionProfiles: Schema.optionalKey( Schema.Union([Schema.Record(Schema.String, Schema.Boolean), Schema.Null]), ), @@ -28760,10 +43110,21 @@ export const V2ConfigRequirementsReadResponse__ConfigRequirements = Schema.Struc ), allowedWindowsSandboxImplementations: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ConfigRequirementsReadResponse__WindowsSandboxSetupMode), + Schema.Array(V2ConfigRequirementsReadResponse__WindowsSandboxImplementation), Schema.Null, ]), ), + autoReview: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__AutoReviewRequirements, Schema.Null]), + ), + browserUse: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__BrowserUseRequirements, Schema.Null]), + ), + chatgptBaseUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + checkForUpdateOnStartup: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), + cliAuthCredentialsStore: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__CliAuthCredentialsStoreMode, Schema.Null]), + ), computerUse: Schema.optionalKey( Schema.Union([V2ConfigRequirementsReadResponse__ComputerUseRequirements, Schema.Null]), ), @@ -28774,662 +43135,216 @@ export const V2ConfigRequirementsReadResponse__ConfigRequirements = Schema.Struc featureRequirements: Schema.optionalKey( Schema.Union([Schema.Record(Schema.String, Schema.Boolean), Schema.Null]), ), - models: Schema.optionalKey( - Schema.Union([V2ConfigRequirementsReadResponse__ModelsRequirements, Schema.Null]), - ), -}); - -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__FileSystemSandboxEntry = { - readonly access: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode; - readonly path: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath; -}; -export const V2ItemGuardianApprovalReviewCompletedNotification__FileSystemSandboxEntry = - Schema.Struct({ - access: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemAccessMode, - path: V2ItemGuardianApprovalReviewCompletedNotification__FileSystemPath, - }); - -export type V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry = { - readonly access: V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode; - readonly path: V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath; -}; -export const V2ItemGuardianApprovalReviewStartedNotification__FileSystemSandboxEntry = - Schema.Struct({ - access: V2ItemGuardianApprovalReviewStartedNotification__FileSystemAccessMode, - path: V2ItemGuardianApprovalReviewStartedNotification__FileSystemPath, - }); - -export type V2PluginInstalledResponse__PluginSummary = { - readonly authPolicy: V2PluginInstalledResponse__PluginAuthPolicy; - readonly availability?: "DISABLED_BY_ADMIN" | "AVAILABLE"; - readonly enabled: boolean; - readonly id: string; - readonly installPolicy: V2PluginInstalledResponse__PluginInstallPolicy; - readonly installPolicySource?: V2PluginInstalledResponse__PluginInstallPolicySource | null; - readonly installed: boolean; - readonly interface?: V2PluginInstalledResponse__PluginInterface | null; - readonly keywords?: ReadonlyArray; - readonly localVersion?: string | null; - readonly mustShowInstallationInterstitial?: boolean | null; - readonly name: string; - readonly remotePluginId?: string | null; - readonly shareContext?: V2PluginInstalledResponse__PluginShareContext | null; - readonly source: V2PluginInstalledResponse__PluginSource; - readonly version?: string | null; -}; -export const V2PluginInstalledResponse__PluginSummary = Schema.Struct({ - authPolicy: V2PluginInstalledResponse__PluginAuthPolicy, - availability: Schema.optionalKey( - Schema.Literals(["DISABLED_BY_ADMIN", "AVAILABLE"]).annotate({ - description: "Availability state for installing and using the plugin.", - default: "AVAILABLE", - }), - ), - enabled: Schema.Boolean, - id: Schema.String, - installPolicy: V2PluginInstalledResponse__PluginInstallPolicy, - installPolicySource: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__PluginInstallPolicySource, Schema.Null]), + feedback: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__FeedbackRequirements, Schema.Null]), ), - installed: Schema.Boolean, - interface: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__PluginInterface, Schema.Null]), + inAppBrowser: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__InAppBrowserRequirements, Schema.Null]), ), - keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), - localVersion: Schema.optionalKey( + logDir: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + modelCatalogJson: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + modelProvider: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Version of the locally materialized plugin package when available.", + description: "Exact provider selection required by managed policy.", }), Schema.Null, ]), ), - mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - name: Schema.String, - remotePluginId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), - Schema.Null, - ]), - ), - shareContext: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__PluginShareContext, Schema.Null]).annotate({ - description: "Remote sharing context associated with this plugin when available.", - }), - ), - source: V2PluginInstalledResponse__PluginSource, - version: Schema.optionalKey( + modelProviders: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: "Version advertised by the remote marketplace backend when available.", + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })).annotate({ + description: "Complete required provider definitions, using config.toml field names.", }), Schema.Null, ]), ), -}); + models: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__ModelsRequirements, Schema.Null]), + ), + sqliteHome: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ identifier: "V2ConfigRequirementsReadResponse__ConfigRequirements" }); -export type V2PluginListResponse__PluginSummary = { - readonly authPolicy: V2PluginListResponse__PluginAuthPolicy; - readonly availability?: "DISABLED_BY_ADMIN" | "AVAILABLE"; - readonly enabled: boolean; - readonly id: string; - readonly installPolicy: V2PluginListResponse__PluginInstallPolicy; - readonly installPolicySource?: V2PluginListResponse__PluginInstallPolicySource | null; - readonly installed: boolean; - readonly interface?: V2PluginListResponse__PluginInterface | null; - readonly keywords?: ReadonlyArray; - readonly localVersion?: string | null; - readonly mustShowInstallationInterstitial?: boolean | null; - readonly name: string; - readonly remotePluginId?: string | null; - readonly shareContext?: V2PluginListResponse__PluginShareContext | null; - readonly source: V2PluginListResponse__PluginSource; - readonly version?: string | null; +export type V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; }; -export const V2PluginListResponse__PluginSummary = Schema.Struct({ - authPolicy: V2PluginListResponse__PluginAuthPolicy, - availability: Schema.optionalKey( - Schema.Literals(["DISABLED_BY_ADMIN", "AVAILABLE"]).annotate({ - description: "Availability state for installing and using the plugin.", - default: "AVAILABLE", - }), - ), - enabled: Schema.Boolean, - id: Schema.String, - installPolicy: V2PluginListResponse__PluginInstallPolicy, - installPolicySource: Schema.optionalKey( - Schema.Union([V2PluginListResponse__PluginInstallPolicySource, Schema.Null]), - ), - installed: Schema.Boolean, - interface: Schema.optionalKey(Schema.Union([V2PluginListResponse__PluginInterface, Schema.Null])), - keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), - localVersion: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version of the locally materialized plugin package when available.", - }), - Schema.Null, - ]), - ), - mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - name: Schema.String, - remotePluginId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), - Schema.Null, - ]), - ), - shareContext: Schema.optionalKey( - Schema.Union([V2PluginListResponse__PluginShareContext, Schema.Null]).annotate({ - description: "Remote sharing context associated with this plugin when available.", - }), - ), - source: V2PluginListResponse__PluginSource, - version: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version advertised by the remote marketplace backend when available.", - }), - Schema.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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array( + V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, + ).annotate({ description: "This will be removed in favor of `entries`." }), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array( + V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, + ).annotate({ description: "This will be removed in favor of `entries`." }), + Schema.Null, + ]), + ), + }).annotate({ + identifier: + "V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions", + }); -export type V2PluginReadResponse__PluginSummary = { - readonly authPolicy: V2PluginReadResponse__PluginAuthPolicy; - readonly availability?: "DISABLED_BY_ADMIN" | "AVAILABLE"; - readonly enabled: boolean; - readonly id: string; - readonly installPolicy: V2PluginReadResponse__PluginInstallPolicy; - readonly installPolicySource?: V2PluginReadResponse__PluginInstallPolicySource | null; - readonly installed: boolean; - readonly interface?: V2PluginReadResponse__PluginInterface | null; - readonly keywords?: ReadonlyArray; - readonly localVersion?: string | null; - readonly mustShowInstallationInterstitial?: boolean | null; - readonly name: string; - readonly remotePluginId?: string | null; - readonly shareContext?: V2PluginReadResponse__PluginShareContext | null; - readonly source: V2PluginReadResponse__PluginSource; - readonly version?: string | null; +export type V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions = { + readonly entries?: ReadonlyArray | null; + readonly globScanMaxDepth?: number | null; + readonly read?: ReadonlyArray | null; + readonly write?: ReadonlyArray | null; }; -export const V2PluginReadResponse__PluginSummary = Schema.Struct({ - authPolicy: V2PluginReadResponse__PluginAuthPolicy, - availability: Schema.optionalKey( - Schema.Literals(["DISABLED_BY_ADMIN", "AVAILABLE"]).annotate({ - description: "Availability state for installing and using the plugin.", - default: "AVAILABLE", - }), - ), - enabled: Schema.Boolean, - id: Schema.String, - installPolicy: V2PluginReadResponse__PluginInstallPolicy, - installPolicySource: Schema.optionalKey( - Schema.Union([V2PluginReadResponse__PluginInstallPolicySource, Schema.Null]), - ), - installed: Schema.Boolean, - interface: Schema.optionalKey(Schema.Union([V2PluginReadResponse__PluginInterface, Schema.Null])), - keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), - localVersion: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version of the locally materialized plugin package when available.", - }), - Schema.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().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(1).annotate({ + expected: "a value greater than or equal to 1", + }), + ), + Schema.Null, + ]), + ), + read: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString).annotate( + { description: "This will be removed in favor of `entries`." }, + ), + Schema.Null, + ]), + ), + write: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString).annotate( + { description: "This will be removed in favor of `entries`." }, + ), + Schema.Null, + ]), + ), + }).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions", + }); + +export type V2PluginInstalledResponse__PluginMarketplaceEntry = { + readonly interface?: V2PluginInstalledResponse__MarketplaceInterface | null; + readonly name: string; + readonly path?: V2PluginInstalledResponse__AbsolutePathBuf | null; + readonly plugins: ReadonlyArray; +}; +export const V2PluginInstalledResponse__PluginMarketplaceEntry = Schema.Struct({ + interface: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__MarketplaceInterface, Schema.Null]), ), - mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), name: Schema.String, - remotePluginId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), - Schema.Null, - ]), - ), - shareContext: Schema.optionalKey( - Schema.Union([V2PluginReadResponse__PluginShareContext, Schema.Null]).annotate({ - description: "Remote sharing context associated with this plugin when available.", + path: Schema.optionalKey( + Schema.Union([V2PluginInstalledResponse__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.", }), ), - source: V2PluginReadResponse__PluginSource, - version: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version advertised by the remote marketplace backend when available.", - }), - Schema.Null, - ]), - ), -}); + plugins: Schema.Array(V2PluginInstalledResponse__PluginSummary), +}).annotate({ identifier: "V2PluginInstalledResponse__PluginMarketplaceEntry" }); -export type V2PluginShareListResponse__PluginSummary = { - readonly authPolicy: V2PluginShareListResponse__PluginAuthPolicy; - readonly availability?: "DISABLED_BY_ADMIN" | "AVAILABLE"; - readonly enabled: boolean; - readonly id: string; - readonly installPolicy: V2PluginShareListResponse__PluginInstallPolicy; - readonly installPolicySource?: V2PluginShareListResponse__PluginInstallPolicySource | null; - readonly installed: boolean; - readonly interface?: V2PluginShareListResponse__PluginInterface | null; - readonly keywords?: ReadonlyArray; - readonly localVersion?: string | null; - readonly mustShowInstallationInterstitial?: boolean | null; +export type V2PluginListResponse__PluginMarketplaceEntry = { + readonly interface?: V2PluginListResponse__MarketplaceInterface | null; readonly name: string; - readonly remotePluginId?: string | null; - readonly shareContext?: V2PluginShareListResponse__PluginShareContext | null; - readonly source: V2PluginShareListResponse__PluginSource; - readonly version?: string | null; + readonly path?: V2PluginListResponse__AbsolutePathBuf | null; + readonly plugins: ReadonlyArray; }; -export const V2PluginShareListResponse__PluginSummary = Schema.Struct({ - authPolicy: V2PluginShareListResponse__PluginAuthPolicy, - availability: Schema.optionalKey( - Schema.Literals(["DISABLED_BY_ADMIN", "AVAILABLE"]).annotate({ - description: "Availability state for installing and using the plugin.", - default: "AVAILABLE", - }), - ), - enabled: Schema.Boolean, - id: Schema.String, - installPolicy: V2PluginShareListResponse__PluginInstallPolicy, - installPolicySource: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__PluginInstallPolicySource, Schema.Null]), - ), - installed: Schema.Boolean, +export const V2PluginListResponse__PluginMarketplaceEntry = Schema.Struct({ interface: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__PluginInterface, Schema.Null]), - ), - keywords: Schema.optionalKey(Schema.Array(Schema.String).annotate({ default: [] })), - localVersion: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version of the locally materialized plugin package when available.", - }), - Schema.Null, - ]), + Schema.Union([V2PluginListResponse__MarketplaceInterface, Schema.Null]), ), - mustShowInstallationInterstitial: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), name: Schema.String, - remotePluginId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Backend remote plugin identifier when available." }), - Schema.Null, - ]), + 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.", + }), ), - shareContext: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__PluginShareContext, Schema.Null]).annotate({ - description: "Remote sharing context associated with this plugin when available.", + plugins: Schema.Array(V2PluginListResponse__PluginSummary), +}).annotate({ identifier: "V2PluginListResponse__PluginMarketplaceEntry" }); + +export type V2PluginReadResponse__PluginDetail = { + readonly appTemplates: ReadonlyArray; + readonly apps: ReadonlyArray; + readonly description?: string | null; + readonly hooks: ReadonlyArray; + readonly marketplaceName: string; + readonly marketplacePath?: V2PluginReadResponse__AbsolutePathBuf | null; + readonly mcpServers: ReadonlyArray; + readonly onboardingSkill?: V2PluginReadResponse__SkillSummary | null; + readonly scheduledTasks?: ReadonlyArray | null; + readonly shareUrl?: string | null; + readonly skills: ReadonlyArray; + readonly summary: V2PluginReadResponse__PluginSummary; +}; +export const V2PluginReadResponse__PluginDetail = Schema.Struct({ + appTemplates: Schema.Array(V2PluginReadResponse__AppTemplateSummary), + apps: Schema.Array(V2PluginReadResponse__AppSummary), + description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + hooks: Schema.Array(V2PluginReadResponse__PluginHookSummary), + marketplaceName: Schema.String, + marketplacePath: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null]), + ), + mcpServers: Schema.Array(Schema.String), + onboardingSkill: Schema.optionalKey( + Schema.Union([V2PluginReadResponse__SkillSummary, Schema.Null]).annotate({ + description: "The declared onboarding skill, when the plugin and visible skill are enabled.", }), ), - source: V2PluginShareListResponse__PluginSource, - version: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Version advertised by the remote marketplace backend when available.", - }), - Schema.Null, - ]), + scheduledTasks: Schema.optionalKey( + Schema.Union([Schema.Array(V2PluginReadResponse__ScheduledTaskSummary), Schema.Null]), ), -}); + shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + skills: Schema.Array(V2PluginReadResponse__SkillSummary), + summary: V2PluginReadResponse__PluginSummary, +}).annotate({ identifier: "V2PluginReadResponse__PluginDetail" }); -export type V2RawResponseItemCompletedNotification__ResponseItem = - | { - readonly content: ReadonlyArray; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly phase?: V2RawResponseItemCompletedNotification__MessagePhase | null; - readonly role: string; - readonly type: "message"; - } - | { - readonly author: string; - readonly content: ReadonlyArray; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly recipient: string; - readonly type: "agent_message"; - } - | { - readonly content?: ReadonlyArray | null; - readonly encrypted_content?: string | null; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly summary: ReadonlyArray; - readonly type: "reasoning"; - } - | { - readonly action: V2RawResponseItemCompletedNotification__LocalShellAction; - readonly call_id?: string | null; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly status: V2RawResponseItemCompletedNotification__LocalShellStatus; - readonly type: "local_shell_call"; - } - | { - readonly arguments: string; - readonly call_id: string; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly name: string; - readonly namespace?: string | null; - readonly type: "function_call"; - } - | { - readonly arguments: unknown; - readonly call_id?: string | null; - readonly execution: string; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly status?: string | null; - readonly type: "tool_search_call"; - } - | { - readonly call_id: string; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody; - readonly type: "function_call_output"; - } - | { - readonly call_id: string; - readonly id?: string | null; - readonly input: string; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly name: string; - readonly namespace?: string | null; - readonly status?: string | null; - readonly type: "custom_tool_call"; - } - | { - readonly call_id: string; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly name?: string | null; - readonly output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody; - readonly type: "custom_tool_call_output"; - } - | { - readonly call_id?: string | null; - readonly execution: string; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly status: string; - readonly tools: ReadonlyArray; - readonly type: "tool_search_output"; - } - | { - readonly action?: V2RawResponseItemCompletedNotification__ResponsesApiWebSearchAction | null; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly status?: string | null; - readonly type: "web_search_call"; - } - | { - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly result: string; - readonly revised_prompt?: string | null; - readonly status: string; - readonly type: "image_generation_call"; - } - | { - readonly encrypted_content: string; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly type: "compaction"; - } - | { readonly type: "compaction_trigger" } - | { - readonly encrypted_content?: string | null; - readonly id?: string | null; - readonly internal_chat_message_metadata_passthrough?: V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough | null; - readonly type: "context_compaction"; - } - | { readonly type: "other" }; -export const V2RawResponseItemCompletedNotification__ResponseItem = Schema.Union( - [ - Schema.Struct({ - content: Schema.Array(V2RawResponseItemCompletedNotification__ContentItem), - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - phase: Schema.optionalKey( - Schema.Union([V2RawResponseItemCompletedNotification__MessagePhase, Schema.Null]), - ), - role: Schema.String, - type: Schema.Literal("message").annotate({ title: "MessageResponseItemType" }), - }).annotate({ title: "MessageResponseItem" }), - Schema.Struct({ - author: Schema.String, - content: Schema.Array(V2RawResponseItemCompletedNotification__AgentMessageInputContent), - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - recipient: Schema.String, - type: Schema.Literal("agent_message").annotate({ title: "AgentMessageResponseItemType" }), - }).annotate({ title: "AgentMessageResponseItem" }), - Schema.Struct({ - content: Schema.optionalKey( - Schema.Union([ - Schema.Array(V2RawResponseItemCompletedNotification__ReasoningItemContent), - Schema.Null, - ]), - ), - encrypted_content: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - summary: Schema.Array(V2RawResponseItemCompletedNotification__ReasoningItemReasoningSummary), - type: Schema.Literal("reasoning").annotate({ title: "ReasoningResponseItemType" }), - }).annotate({ title: "ReasoningResponseItem" }), - Schema.Struct({ - action: V2RawResponseItemCompletedNotification__LocalShellAction, - call_id: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Set when using the Responses API." }), - Schema.Null, - ]), - ), - id: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Legacy id field retained for compatibility with older payloads.", - }), - Schema.Null, - ]), - ), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - status: V2RawResponseItemCompletedNotification__LocalShellStatus, - type: Schema.Literal("local_shell_call").annotate({ - title: "LocalShellCallResponseItemType", - }), - }).annotate({ title: "LocalShellCallResponseItem" }), - Schema.Struct({ - arguments: Schema.String, - call_id: Schema.String, - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - name: Schema.String, - namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("function_call").annotate({ title: "FunctionCallResponseItemType" }), - }).annotate({ title: "FunctionCallResponseItem" }), - Schema.Struct({ - arguments: Schema.Unknown, - call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - execution: Schema.String, - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - status: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("tool_search_call").annotate({ - title: "ToolSearchCallResponseItemType", - }), - }).annotate({ title: "ToolSearchCallResponseItem" }), - Schema.Struct({ - call_id: Schema.String, - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody, - type: Schema.Literal("function_call_output").annotate({ - title: "FunctionCallOutputResponseItemType", - }), - }).annotate({ title: "FunctionCallOutputResponseItem" }), - Schema.Struct({ - call_id: Schema.String, - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - input: Schema.String, - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - name: Schema.String, - namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("custom_tool_call").annotate({ - title: "CustomToolCallResponseItemType", - }), - }).annotate({ title: "CustomToolCallResponseItem" }), - Schema.Struct({ - call_id: Schema.String, - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - name: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - output: V2RawResponseItemCompletedNotification__FunctionCallOutputBody, - type: Schema.Literal("custom_tool_call_output").annotate({ - title: "CustomToolCallOutputResponseItemType", - }), - }).annotate({ title: "CustomToolCallOutputResponseItem" }), - Schema.Struct({ - call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - execution: Schema.String, - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - status: Schema.String, - tools: Schema.Array(Schema.Unknown), - type: Schema.Literal("tool_search_output").annotate({ - title: "ToolSearchOutputResponseItemType", - }), - }).annotate({ title: "ToolSearchOutputResponseItem" }), - Schema.Struct({ - action: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__ResponsesApiWebSearchAction, - Schema.Null, - ]), - ), - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - status: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - type: Schema.Literal("web_search_call").annotate({ title: "WebSearchCallResponseItemType" }), - }).annotate({ title: "WebSearchCallResponseItem" }), - Schema.Struct({ - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - result: Schema.String, - revised_prompt: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - status: Schema.String, - type: Schema.Literal("image_generation_call").annotate({ - title: "ImageGenerationCallResponseItemType", - }), - }).annotate({ title: "ImageGenerationCallResponseItem" }), - Schema.Struct({ - encrypted_content: Schema.String, - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - type: Schema.Literal("compaction").annotate({ title: "CompactionResponseItemType" }), - }).annotate({ title: "CompactionResponseItem" }), - Schema.Struct({ - type: Schema.Literal("compaction_trigger").annotate({ - title: "CompactionTriggerResponseItemType", - }), - }).annotate({ title: "CompactionTriggerResponseItem" }), - Schema.Struct({ - encrypted_content: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - internal_chat_message_metadata_passthrough: Schema.optionalKey( - Schema.Union([ - V2RawResponseItemCompletedNotification__InternalChatMessageMetadataPassthrough, - Schema.Null, - ]), - ), - type: Schema.Literal("context_compaction").annotate({ - title: "ContextCompactionResponseItemType", - }), - }).annotate({ title: "ContextCompactionResponseItem" }), - Schema.Struct({ - type: Schema.Literal("other").annotate({ title: "OtherResponseItemType" }), - }).annotate({ title: "OtherResponseItem" }), - ], - { mode: "oneOf" }, -); +export type V2PluginShareListResponse__PluginShareListItem = { + readonly localPluginPath?: V2PluginShareListResponse__AbsolutePathBuf | null; + readonly plugin: V2PluginShareListResponse__PluginSummary; +}; +export const V2PluginShareListResponse__PluginShareListItem = Schema.Struct({ + localPluginPath: Schema.optionalKey( + Schema.Union([V2PluginShareListResponse__AbsolutePathBuf, Schema.Null]), + ), + plugin: V2PluginShareListResponse__PluginSummary, +}).annotate({ identifier: "V2PluginShareListResponse__PluginShareListItem" }); export type V2ReviewStartResponse__Turn = { readonly completedAt?: number | null; @@ -29437,7 +43352,7 @@ export type V2ReviewStartResponse__Turn = { readonly error?: V2ReviewStartResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ReviewStartResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ReviewStartResponse__TurnStatus; }; @@ -29447,7 +43362,7 @@ export const V2ReviewStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29456,7 +43371,7 @@ export const V2ReviewStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29472,7 +43387,10 @@ export const V2ReviewStartResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2ReviewStartResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29482,23 +43400,12 @@ export const V2ReviewStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ReviewStartResponse__TurnStatus, -}); - -export type V2SkillsListResponse__SkillsListEntry = { - readonly cwd: string; - readonly errors: ReadonlyArray; - readonly skills: ReadonlyArray; -}; -export const V2SkillsListResponse__SkillsListEntry = Schema.Struct({ - cwd: Schema.String, - errors: Schema.Array(V2SkillsListResponse__SkillErrorInfo), - skills: Schema.Array(V2SkillsListResponse__SkillMetadata), -}); +}).annotate({ identifier: "V2ReviewStartResponse__Turn" }); export type V2ThreadForkResponse__Turn = { readonly completedAt?: number | null; @@ -29506,7 +43413,7 @@ export type V2ThreadForkResponse__Turn = { readonly error?: V2ThreadForkResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadForkResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadForkResponse__TurnStatus; }; @@ -29516,7 +43423,7 @@ export const V2ThreadForkResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29525,7 +43432,7 @@ export const V2ThreadForkResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29541,7 +43448,9 @@ export const V2ThreadForkResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => V2ThreadForkResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29551,12 +43460,21 @@ export const V2ThreadForkResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadForkResponse__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadForkResponse__Turn" }); + +export type V2ThreadItemsListResponse__ThreadItemEntry = { + readonly item: V2ThreadItemsListResponse__ThreadItem; + readonly turnId: string; +}; +export const V2ThreadItemsListResponse__ThreadItemEntry = Schema.Struct({ + item: V2ThreadItemsListResponse__ThreadItem, + turnId: Schema.String.annotate({ description: "Turn containing this item." }), +}).annotate({ identifier: "V2ThreadItemsListResponse__ThreadItemEntry" }); export type V2ThreadListResponse__Turn = { readonly completedAt?: number | null; @@ -29564,7 +43482,7 @@ export type V2ThreadListResponse__Turn = { readonly error?: V2ThreadListResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadListResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadListResponse__TurnStatus; }; @@ -29574,7 +43492,7 @@ export const V2ThreadListResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29583,7 +43501,7 @@ export const V2ThreadListResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29599,7 +43517,9 @@ export const V2ThreadListResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => V2ThreadListResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29609,12 +43529,12 @@ export const V2ThreadListResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadListResponse__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadListResponse__Turn" }); export type V2ThreadMetadataUpdateResponse__Turn = { readonly completedAt?: number | null; @@ -29622,7 +43542,7 @@ export type V2ThreadMetadataUpdateResponse__Turn = { readonly error?: V2ThreadMetadataUpdateResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadMetadataUpdateResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadMetadataUpdateResponse__TurnStatus; }; @@ -29632,7 +43552,7 @@ export const V2ThreadMetadataUpdateResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29641,7 +43561,7 @@ export const V2ThreadMetadataUpdateResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29657,7 +43577,10 @@ export const V2ThreadMetadataUpdateResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29667,12 +43590,12 @@ export const V2ThreadMetadataUpdateResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadMetadataUpdateResponse__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__Turn" }); export type V2ThreadReadResponse__Turn = { readonly completedAt?: number | null; @@ -29680,7 +43603,7 @@ export type V2ThreadReadResponse__Turn = { readonly error?: V2ThreadReadResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadReadResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadReadResponse__TurnStatus; }; @@ -29690,7 +43613,7 @@ export const V2ThreadReadResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29699,7 +43622,7 @@ export const V2ThreadReadResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29715,7 +43638,9 @@ export const V2ThreadReadResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => V2ThreadReadResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29725,12 +43650,12 @@ export const V2ThreadReadResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadReadResponse__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadReadResponse__Turn" }); export type V2ThreadResumeResponse__Turn = { readonly completedAt?: number | null; @@ -29738,7 +43663,7 @@ export type V2ThreadResumeResponse__Turn = { readonly error?: V2ThreadResumeResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadResumeResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadResumeResponse__TurnStatus; }; @@ -29748,7 +43673,7 @@ export const V2ThreadResumeResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29757,7 +43682,7 @@ export const V2ThreadResumeResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29773,7 +43698,10 @@ export const V2ThreadResumeResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29783,30 +43711,30 @@ export const V2ThreadResumeResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadResumeResponse__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadResumeResponse__Turn" }); -export type V2ThreadRollbackResponse__Turn = { +export type V2ThreadRevertResponse__Turn = { readonly completedAt?: number | null; readonly durationMs?: number | null; - readonly error?: V2ThreadRollbackResponse__TurnError | null; + readonly error?: V2ThreadRevertResponse__TurnError | null; readonly id: string; - readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly items: ReadonlyArray; + readonly itemsView?: V2ThreadRevertResponse__TurnItemsView; readonly startedAt?: number | null; - readonly status: V2ThreadRollbackResponse__TurnStatus; + readonly status: V2ThreadRevertResponse__TurnStatus; }; -export const V2ThreadRollbackResponse__Turn = Schema.Struct({ +export const V2ThreadRevertResponse__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()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29815,23 +43743,26 @@ export const V2ThreadRollbackResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), error: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__TurnError, Schema.Null]).annotate({ + Schema.Union([V2ThreadRevertResponse__TurnError, Schema.Null]).annotate({ description: "Only populated when the Turn's status is failed.", }), ), id: Schema.String.annotate({ description: "Identifier for this turn. Codex-generated turn IDs are UUIDv7.", }), - items: Schema.Array(V2ThreadRollbackResponse__ThreadItem).annotate({ + items: Schema.Array(V2ThreadRevertResponse__ThreadItem).annotate({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29841,49 +43772,12 @@ export const V2ThreadRollbackResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), - status: V2ThreadRollbackResponse__TurnStatus, -}); - -export type V2ThreadSettingsUpdatedNotification__ThreadSettings = { - readonly activePermissionProfile?: V2ThreadSettingsUpdatedNotification__ActivePermissionProfile | null; - readonly approvalPolicy: V2ThreadSettingsUpdatedNotification__AskForApproval; - readonly approvalsReviewer: V2ThreadSettingsUpdatedNotification__ApprovalsReviewer; - readonly collaborationMode: V2ThreadSettingsUpdatedNotification__CollaborationMode; - readonly cwd: V2ThreadSettingsUpdatedNotification__AbsolutePathBuf; - readonly effort?: V2ThreadSettingsUpdatedNotification__ReasoningEffort | null; - readonly model: string; - readonly modelProvider: string; - readonly personality?: V2ThreadSettingsUpdatedNotification__Personality | null; - readonly sandboxPolicy: V2ThreadSettingsUpdatedNotification__SandboxPolicy; - readonly serviceTier?: string | null; - readonly summary?: V2ThreadSettingsUpdatedNotification__ReasoningSummary | null; -}; -export const V2ThreadSettingsUpdatedNotification__ThreadSettings = Schema.Struct({ - activePermissionProfile: Schema.optionalKey( - Schema.Union([V2ThreadSettingsUpdatedNotification__ActivePermissionProfile, Schema.Null]), - ), - approvalPolicy: V2ThreadSettingsUpdatedNotification__AskForApproval, - approvalsReviewer: V2ThreadSettingsUpdatedNotification__ApprovalsReviewer, - collaborationMode: V2ThreadSettingsUpdatedNotification__CollaborationMode, - cwd: V2ThreadSettingsUpdatedNotification__AbsolutePathBuf, - effort: Schema.optionalKey( - Schema.Union([V2ThreadSettingsUpdatedNotification__ReasoningEffort, Schema.Null]), - ), - model: Schema.String, - modelProvider: Schema.String, - personality: Schema.optionalKey( - Schema.Union([V2ThreadSettingsUpdatedNotification__Personality, Schema.Null]), - ), - sandboxPolicy: V2ThreadSettingsUpdatedNotification__SandboxPolicy, - serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - summary: Schema.optionalKey( - Schema.Union([V2ThreadSettingsUpdatedNotification__ReasoningSummary, Schema.Null]), - ), -}); + status: V2ThreadRevertResponse__TurnStatus, +}).annotate({ identifier: "V2ThreadRevertResponse__Turn" }); export type V2ThreadStartedNotification__Turn = { readonly completedAt?: number | null; @@ -29891,7 +43785,7 @@ export type V2ThreadStartedNotification__Turn = { readonly error?: V2ThreadStartedNotification__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadStartedNotification__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadStartedNotification__TurnStatus; }; @@ -29901,7 +43795,7 @@ export const V2ThreadStartedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29910,7 +43804,7 @@ export const V2ThreadStartedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29926,7 +43820,10 @@ export const V2ThreadStartedNotification__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29936,12 +43833,12 @@ export const V2ThreadStartedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadStartedNotification__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadStartedNotification__Turn" }); export type V2ThreadStartResponse__Turn = { readonly completedAt?: number | null; @@ -29949,7 +43846,7 @@ export type V2ThreadStartResponse__Turn = { readonly error?: V2ThreadStartResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadStartResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadStartResponse__TurnStatus; }; @@ -29959,7 +43856,7 @@ export const V2ThreadStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29968,7 +43865,7 @@ export const V2ThreadStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -29984,7 +43881,10 @@ export const V2ThreadStartResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -29994,12 +43894,73 @@ export const V2ThreadStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadStartResponse__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadStartResponse__Turn" }); + +export type V2ThreadTurnsListResponse__Turn = { + readonly completedAt?: number | null; + readonly durationMs?: number | null; + readonly error?: V2ThreadTurnsListResponse__TurnError | null; + readonly id: string; + readonly items: ReadonlyArray; + readonly itemsView?: V2ThreadTurnsListResponse__TurnItemsView; + 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().annotate({ expected: "an integer" })), + 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().annotate({ expected: "an integer" })), + 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.annotate({ + description: "Identifier for this turn. Codex-generated turn IDs are UUIDv7.", + }), + items: Schema.Array(V2ThreadTurnsListResponse__ThreadItem).annotate({ + description: "Thread items currently included in this turn payload.", + }), + itemsView: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadTurnsListResponse__TurnItemsView, + ).annotate({ + description: "Describes how much of `items` has been loaded for this turn.", + default: "full", + }), + ), + startedAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp (in seconds) when the turn started.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + status: V2ThreadTurnsListResponse__TurnStatus, +}).annotate({ identifier: "V2ThreadTurnsListResponse__Turn" }); export type V2ThreadUnarchiveResponse__Turn = { readonly completedAt?: number | null; @@ -30007,7 +43968,7 @@ export type V2ThreadUnarchiveResponse__Turn = { readonly error?: V2ThreadUnarchiveResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2ThreadUnarchiveResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2ThreadUnarchiveResponse__TurnStatus; }; @@ -30017,7 +43978,7 @@ export const V2ThreadUnarchiveResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30026,7 +43987,7 @@ export const V2ThreadUnarchiveResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30042,7 +44003,10 @@ export const V2ThreadUnarchiveResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -30052,12 +44016,12 @@ export const V2ThreadUnarchiveResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2ThreadUnarchiveResponse__TurnStatus, -}); +}).annotate({ identifier: "V2ThreadUnarchiveResponse__Turn" }); export type V2TurnCompletedNotification__Turn = { readonly completedAt?: number | null; @@ -30065,7 +44029,7 @@ export type V2TurnCompletedNotification__Turn = { readonly error?: V2TurnCompletedNotification__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2TurnCompletedNotification__TurnItemsView; readonly startedAt?: number | null; readonly status: V2TurnCompletedNotification__TurnStatus; }; @@ -30075,7 +44039,7 @@ export const V2TurnCompletedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30084,7 +44048,7 @@ export const V2TurnCompletedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30100,7 +44064,10 @@ export const V2TurnCompletedNotification__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2TurnCompletedNotification__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -30110,12 +44077,12 @@ export const V2TurnCompletedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2TurnCompletedNotification__TurnStatus, -}); +}).annotate({ identifier: "V2TurnCompletedNotification__Turn" }); export type V2TurnStartedNotification__Turn = { readonly completedAt?: number | null; @@ -30123,7 +44090,7 @@ export type V2TurnStartedNotification__Turn = { readonly error?: V2TurnStartedNotification__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2TurnStartedNotification__TurnItemsView; readonly startedAt?: number | null; readonly status: V2TurnStartedNotification__TurnStatus; }; @@ -30133,7 +44100,7 @@ export const V2TurnStartedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30142,7 +44109,7 @@ export const V2TurnStartedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30158,7 +44125,10 @@ export const V2TurnStartedNotification__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => + V2TurnStartedNotification__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -30168,12 +44138,12 @@ export const V2TurnStartedNotification__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2TurnStartedNotification__TurnStatus, -}); +}).annotate({ identifier: "V2TurnStartedNotification__Turn" }); export type V2TurnStartResponse__Turn = { readonly completedAt?: number | null; @@ -30181,7 +44151,7 @@ export type V2TurnStartResponse__Turn = { readonly error?: V2TurnStartResponse__TurnError | null; readonly id: string; readonly items: ReadonlyArray; - readonly itemsView?: "notLoaded" | "summary" | "full"; + readonly itemsView?: V2TurnStartResponse__TurnItemsView; readonly startedAt?: number | null; readonly status: V2TurnStartResponse__TurnStatus; }; @@ -30191,7 +44161,7 @@ export const V2TurnStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30200,7 +44170,7 @@ export const V2TurnStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Duration between turn start and completion in milliseconds, if known.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -30216,7 +44186,9 @@ export const V2TurnStartResponse__Turn = Schema.Struct({ description: "Thread items currently included in this turn payload.", }), itemsView: Schema.optionalKey( - Schema.Literals(["notLoaded", "summary", "full"]).annotate({ + Schema.suspend( + (): Schema.Codec => V2TurnStartResponse__TurnItemsView, + ).annotate({ description: "Describes how much of `items` has been loaded for this turn.", default: "full", }), @@ -30226,220 +44198,80 @@ export const V2TurnStartResponse__Turn = Schema.Struct({ Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the turn started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), status: V2TurnStartResponse__TurnStatus, -}); +}).annotate({ identifier: "V2TurnStartResponse__Turn" }); -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__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), - ), - write: Schema.optionalKey( - Schema.Union([ - Schema.Array(CommandExecutionRequestApprovalParams__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), - ), - }, -); - -export type McpServerElicitationRequestParams__McpElicitationEnumSchema = - | McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema - | McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema - | McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema; -export const McpServerElicitationRequestParams__McpElicitationEnumSchema = Schema.Union([ - McpServerElicitationRequestParams__McpElicitationSingleSelectEnumSchema, - McpServerElicitationRequestParams__McpElicitationMultiSelectEnumSchema, - McpServerElicitationRequestParams__McpElicitationLegacyTitledEnumSchema, -]); - -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__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), - ), - write: Schema.optionalKey( - Schema.Union([ - Schema.Array(PermissionsRequestApprovalParams__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), - ), -}); +export type McpServerElicitationRequestParams__McpElicitationPrimitiveSchema = + | McpServerElicitationRequestParams__McpElicitationEnumSchema + | McpServerElicitationRequestParams__McpElicitationStringSchema + | McpServerElicitationRequestParams__McpElicitationNumberSchema + | McpServerElicitationRequestParams__McpElicitationBooleanSchema; +export const McpServerElicitationRequestParams__McpElicitationPrimitiveSchema = Schema.Union([ + McpServerElicitationRequestParams__McpElicitationEnumSchema, + McpServerElicitationRequestParams__McpElicitationStringSchema, + McpServerElicitationRequestParams__McpElicitationNumberSchema, + McpServerElicitationRequestParams__McpElicitationBooleanSchema, +]).annotate({ identifier: "McpServerElicitationRequestParams__McpElicitationPrimitiveSchema" }); -export type PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions = { - readonly entries?: ReadonlyArray | null; - readonly globScanMaxDepth?: number | null; - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; +export type PermissionsRequestApprovalParams__RequestPermissionProfile = { + readonly fileSystem?: PermissionsRequestApprovalParams__AdditionalFileSystemPermissions | null; + readonly network?: PermissionsRequestApprovalParams__AdditionalNetworkPermissions | 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__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), +export const PermissionsRequestApprovalParams__RequestPermissionProfile = Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalParams__AdditionalFileSystemPermissions, Schema.Null]), ), - write: Schema.optionalKey( - Schema.Union([ - Schema.Array(PermissionsRequestApprovalResponse__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), + network: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalParams__AdditionalNetworkPermissions, Schema.Null]), ), -}); +}).annotate({ identifier: "PermissionsRequestApprovalParams__RequestPermissionProfile" }); -export type ServerNotification__AdditionalFileSystemPermissions = { - readonly entries?: ReadonlyArray | null; - readonly globScanMaxDepth?: number | null; - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; +export type PermissionsRequestApprovalResponse__GrantedPermissionProfile = { + readonly fileSystem?: PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions | null; + readonly network?: PermissionsRequestApprovalResponse__AdditionalNetworkPermissions | 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( +export const PermissionsRequestApprovalResponse__GrantedPermissionProfile = Schema.Struct({ + fileSystem: Schema.optionalKey( Schema.Union([ - Schema.Array(ServerNotification__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), + PermissionsRequestApprovalResponse__AdditionalFileSystemPermissions, Schema.Null, ]), ), - write: Schema.optionalKey( - Schema.Union([ - Schema.Array(ServerNotification__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), + network: Schema.optionalKey( + Schema.Union([PermissionsRequestApprovalResponse__AdditionalNetworkPermissions, Schema.Null]), ), -}); - -export type ServerNotification__ThreadSettingsUpdatedNotification = { - readonly threadId: string; - readonly threadSettings: ServerNotification__ThreadSettings; -}; -export const ServerNotification__ThreadSettingsUpdatedNotification = Schema.Struct({ - threadId: Schema.String, - threadSettings: ServerNotification__ThreadSettings, -}); +}).annotate({ identifier: "PermissionsRequestApprovalResponse__GrantedPermissionProfile" }); export type ServerNotification__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: ServerNotification__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; readonly gitInfo?: ServerNotification__GitInfo | null; + readonly historyMode?: ServerNotification__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: ServerNotification__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: ServerNotification__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: ServerNotification__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; + readonly source: ServerNotification__SessionSource; + readonly status: ServerNotification__ThreadStatus; readonly threadSource?: ServerNotification__ThreadSource | null; readonly turns: ReadonlyArray; readonly updatedAt: number; @@ -30468,11 +44300,10 @@ export const ServerNotification__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => ServerNotification__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -30489,9 +44320,27 @@ export const ServerNotification__Thread = Schema.Struct({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + ServerNotification__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -30501,6 +44350,15 @@ export const ServerNotification__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -30519,47 +44377,51 @@ export const ServerNotification__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([ServerNotification__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([ServerNotification__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: ServerNotification__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => ServerNotification__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(ServerNotification__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => ServerNotification__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( Schema.Union([ServerNotification__ThreadSource, Schema.Null]).annotate({ @@ -30568,273 +44430,140 @@ export const ServerNotification__Thread = Schema.Struct({ ), turns: Schema.Array(ServerNotification__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "ServerNotification__Thread" }); -export type ServerNotification__TurnCompletedNotification = { +export type ServerNotification__TurnStartedNotification = { readonly threadId: string; readonly turn: ServerNotification__Turn; }; -export const ServerNotification__TurnCompletedNotification = Schema.Struct({ +export const ServerNotification__TurnStartedNotification = Schema.Struct({ threadId: Schema.String, turn: ServerNotification__Turn, -}); +}).annotate({ identifier: "ServerNotification__TurnStartedNotification" }); -export type ServerNotification__TurnStartedNotification = { +export type ServerNotification__TurnCompletedNotification = { readonly threadId: string; readonly turn: ServerNotification__Turn; }; -export const ServerNotification__TurnStartedNotification = Schema.Struct({ +export const ServerNotification__TurnCompletedNotification = Schema.Struct({ threadId: Schema.String, turn: ServerNotification__Turn, -}); +}).annotate({ identifier: "ServerNotification__TurnCompletedNotification" }); -export type ServerRequest__AdditionalFileSystemPermissions = { - readonly entries?: ReadonlyArray | null; - readonly globScanMaxDepth?: number | null; - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; +export type ServerNotification__RequestPermissionProfile = { + readonly fileSystem?: ServerNotification__AdditionalFileSystemPermissions | null; + readonly network?: ServerNotification__AdditionalNetworkPermissions | null; }; -export const ServerRequest__AdditionalFileSystemPermissions = Schema.Struct({ - entries: Schema.optionalKey( - Schema.Union([Schema.Array(ServerRequest__FileSystemSandboxEntry), Schema.Null]), +export const ServerNotification__RequestPermissionProfile = Schema.Struct({ + fileSystem: Schema.optionalKey( + Schema.Union([ServerNotification__AdditionalFileSystemPermissions, Schema.Null]), ), - globScanMaxDepth: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ format: "uint" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(1)), - Schema.Null, - ]), + network: Schema.optionalKey( + Schema.Union([ServerNotification__AdditionalNetworkPermissions, Schema.Null]), ), - read: Schema.optionalKey( - Schema.Union([ - Schema.Array(ServerRequest__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), +}).annotate({ identifier: "ServerNotification__RequestPermissionProfile" }); + +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]), ), - write: Schema.optionalKey( - Schema.Union([ - Schema.Array(ServerRequest__LegacyAppPathString).annotate({ - description: "This will be removed in favor of `entries`.", - }), - Schema.Null, - ]), + network: Schema.optionalKey( + Schema.Union([ServerRequest__AdditionalNetworkPermissions, Schema.Null]), ), -}); +}).annotate({ identifier: "ServerRequest__RequestPermissionProfile" }); -export type ServerRequest__McpElicitationEnumSchema = - | ServerRequest__McpElicitationSingleSelectEnumSchema - | ServerRequest__McpElicitationMultiSelectEnumSchema - | ServerRequest__McpElicitationLegacyTitledEnumSchema; -export const ServerRequest__McpElicitationEnumSchema = Schema.Union([ - ServerRequest__McpElicitationSingleSelectEnumSchema, - ServerRequest__McpElicitationMultiSelectEnumSchema, - ServerRequest__McpElicitationLegacyTitledEnumSchema, -]); +export type ServerRequest__McpElicitationPrimitiveSchema = + | ServerRequest__McpElicitationEnumSchema + | ServerRequest__McpElicitationStringSchema + | ServerRequest__McpElicitationNumberSchema + | ServerRequest__McpElicitationBooleanSchema; +export const ServerRequest__McpElicitationPrimitiveSchema = Schema.Union([ + ServerRequest__McpElicitationEnumSchema, + ServerRequest__McpElicitationStringSchema, + ServerRequest__McpElicitationNumberSchema, + ServerRequest__McpElicitationBooleanSchema, +]).annotate({ identifier: "ServerRequest__McpElicitationPrimitiveSchema" }); -export type V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions = { - readonly entries?: ReadonlyArray | null; - readonly globScanMaxDepth?: number | null; - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; +export type V2ItemGuardianApprovalReviewCompletedNotification__RequestPermissionProfile = { + readonly fileSystem?: V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions | null; + readonly network?: V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions | null; }; -export const V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions = +export const V2ItemGuardianApprovalReviewCompletedNotification__RequestPermissionProfile = 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( + fileSystem: Schema.optionalKey( Schema.Union([ - Schema.Array( - V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, - ).annotate({ description: "This will be removed in favor of `entries`." }), + V2ItemGuardianApprovalReviewCompletedNotification__AdditionalFileSystemPermissions, Schema.Null, ]), ), - write: Schema.optionalKey( + network: Schema.optionalKey( Schema.Union([ - Schema.Array( - V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, - ).annotate({ description: "This will be removed in favor of `entries`." }), + V2ItemGuardianApprovalReviewCompletedNotification__AdditionalNetworkPermissions, Schema.Null, ]), ), + }).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__RequestPermissionProfile", }); -export type V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions = { - readonly entries?: ReadonlyArray | null; - readonly globScanMaxDepth?: number | null; - readonly read?: ReadonlyArray | null; - readonly write?: ReadonlyArray | null; +export type V2ItemGuardianApprovalReviewStartedNotification__RequestPermissionProfile = { + readonly fileSystem?: V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions | null; + readonly network?: V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions | null; }; -export const V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions = +export const V2ItemGuardianApprovalReviewStartedNotification__RequestPermissionProfile = 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( + fileSystem: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString).annotate( - { description: "This will be removed in favor of `entries`." }, - ), + V2ItemGuardianApprovalReviewStartedNotification__AdditionalFileSystemPermissions, Schema.Null, ]), ), - write: Schema.optionalKey( + network: Schema.optionalKey( Schema.Union([ - Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString).annotate( - { description: "This will be removed in favor of `entries`." }, - ), + V2ItemGuardianApprovalReviewStartedNotification__AdditionalNetworkPermissions, Schema.Null, ]), ), + }).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__RequestPermissionProfile", }); -export type V2PluginInstalledResponse__PluginMarketplaceEntry = { - readonly interface?: V2PluginInstalledResponse__MarketplaceInterface | null; - readonly name: string; - readonly path?: V2PluginInstalledResponse__AbsolutePathBuf | null; - readonly plugins: ReadonlyArray; -}; -export const V2PluginInstalledResponse__PluginMarketplaceEntry = Schema.Struct({ - interface: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__MarketplaceInterface, Schema.Null]), - ), - name: Schema.String, - path: Schema.optionalKey( - Schema.Union([V2PluginInstalledResponse__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(V2PluginInstalledResponse__PluginSummary), -}); - -export type V2PluginListResponse__PluginMarketplaceEntry = { - readonly interface?: V2PluginListResponse__MarketplaceInterface | null; - readonly name: string; - readonly path?: V2PluginListResponse__AbsolutePathBuf | null; - readonly plugins: ReadonlyArray; -}; -export const V2PluginListResponse__PluginMarketplaceEntry = Schema.Struct({ - interface: Schema.optionalKey( - Schema.Union([V2PluginListResponse__MarketplaceInterface, Schema.Null]), - ), - name: Schema.String, - 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), -}); - -export type V2PluginReadResponse__PluginDetail = { - readonly appTemplates: ReadonlyArray; - readonly apps: ReadonlyArray; - readonly description?: string | null; - readonly hooks: ReadonlyArray; - readonly marketplaceName: string; - readonly marketplacePath?: V2PluginReadResponse__AbsolutePathBuf | null; - readonly mcpServers: ReadonlyArray; - readonly scheduledTasks?: ReadonlyArray | null; - readonly shareUrl?: string | null; - readonly skills: ReadonlyArray; - readonly summary: V2PluginReadResponse__PluginSummary; -}; -export const V2PluginReadResponse__PluginDetail = Schema.Struct({ - appTemplates: Schema.Array(V2PluginReadResponse__AppTemplateSummary), - apps: Schema.Array(V2PluginReadResponse__AppSummary), - description: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - hooks: Schema.Array(V2PluginReadResponse__PluginHookSummary), - marketplaceName: Schema.String, - marketplacePath: Schema.optionalKey( - Schema.Union([V2PluginReadResponse__AbsolutePathBuf, Schema.Null]), - ), - mcpServers: Schema.Array(Schema.String), - scheduledTasks: Schema.optionalKey( - Schema.Union([Schema.Array(V2PluginReadResponse__ScheduledTaskSummary), Schema.Null]), - ), - shareUrl: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - skills: Schema.Array(V2PluginReadResponse__SkillSummary), - summary: V2PluginReadResponse__PluginSummary, -}); - -export type V2PluginShareListResponse__PluginShareListItem = { - readonly localPluginPath?: V2PluginShareListResponse__AbsolutePathBuf | null; - readonly plugin: V2PluginShareListResponse__PluginSummary; -}; -export const V2PluginShareListResponse__PluginShareListItem = Schema.Struct({ - localPluginPath: Schema.optionalKey( - Schema.Union([V2PluginShareListResponse__AbsolutePathBuf, Schema.Null]), - ), - plugin: V2PluginShareListResponse__PluginSummary, -}); - export type V2ThreadForkResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadForkResponse__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; readonly gitInfo?: V2ThreadForkResponse__GitInfo | null; + readonly historyMode?: V2ThreadForkResponse__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadForkResponse__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadForkResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadForkResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; + readonly source: V2ThreadForkResponse__SessionSource; + readonly status: V2ThreadForkResponse__ThreadStatus; readonly threadSource?: V2ThreadForkResponse__ThreadSource | null; readonly turns: ReadonlyArray; readonly updatedAt: number; @@ -30863,11 +44592,11 @@ export const V2ThreadForkResponse__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadForkResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -30884,9 +44613,27 @@ export const V2ThreadForkResponse__Thread = Schema.Struct({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadForkResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -30896,6 +44643,15 @@ export const V2ThreadForkResponse__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -30914,47 +44670,51 @@ export const V2ThreadForkResponse__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadForkResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadForkResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => V2ThreadForkResponse__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadForkResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => V2ThreadForkResponse__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( Schema.Union([V2ThreadForkResponse__ThreadSource, Schema.Null]).annotate({ @@ -30963,47 +44723,40 @@ export const V2ThreadForkResponse__Thread = Schema.Struct({ ), turns: Schema.Array(V2ThreadForkResponse__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadForkResponse__Thread" }); export type V2ThreadListResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadListResponse__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; readonly gitInfo?: V2ThreadListResponse__GitInfo | null; + readonly historyMode?: V2ThreadListResponse__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadListResponse__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadListResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadListResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; + readonly source: V2ThreadListResponse__SessionSource; + readonly status: V2ThreadListResponse__ThreadStatus; readonly threadSource?: V2ThreadListResponse__ThreadSource | null; readonly turns: ReadonlyArray; readonly updatedAt: number; @@ -31032,11 +44785,204 @@ export const V2ThreadListResponse__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - cwd: Schema.String.annotate({ + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadListResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), + ephemeral: Schema.Boolean.annotate({ + description: "Whether the thread is ephemeral and should not be materialized on disk.", + }), + forkedFromId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Source thread id when this thread was created by forking another thread.", + }), + Schema.Null, + ]), + ), + gitInfo: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__GitInfo, Schema.Null]).annotate({ + description: "Optional Git metadata captured when the thread was created.", + }), + ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadListResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), + id: Schema.String.annotate({ + description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", + }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), + modelProvider: Schema.String.annotate({ + description: "Model provider used for this thread (for example, 'openai').", + }), + name: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "Optional user-facing thread title." }), + Schema.Null, + ]), + ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), + parentThreadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "The ID of the parent thread. This will only be set if this thread is a subagent.", + }), + Schema.Null, + ]), + ), + path: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ description: "[UNSTABLE] Path to the thread on disk." }), + Schema.Null, + ]), + ), + preview: Schema.String.annotate({ + description: "Usually the first user message in the thread, if available.", + }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), + recencyAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp (in seconds) used for thread recency ordering.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + sessionId: Schema.String.annotate({ + description: "Session id shared by threads that belong to the same session tree.", + }), + source: Schema.suspend( + (): Schema.Codec => V2ThreadListResponse__SessionSource, + ).annotate({ + description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", + }), + status: Schema.suspend( + (): Schema.Codec => V2ThreadListResponse__ThreadStatus, + ).annotate({ description: "Current runtime status for the thread." }), + threadSource: Schema.optionalKey( + Schema.Union([V2ThreadListResponse__ThreadSource, Schema.Null]).annotate({ + description: "Optional analytics source classification for this thread.", + }), + ), + turns: Schema.Array(V2ThreadListResponse__Turn).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.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + }), + updatedAt: Schema.Number.annotate({ + description: "Unix timestamp (in seconds) when the thread was last updated.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadListResponse__Thread" }); + +export type V2ThreadMetadataUpdateResponse__Thread = { + readonly agentNickname?: string | null; + readonly agentRole?: string | null; + readonly cliVersion: string; + readonly createdAt: number; + readonly cwd: V2ThreadMetadataUpdateResponse__AbsolutePathBuf; + readonly ephemeral: boolean; + readonly forkedFromId?: string | null; + readonly gitInfo?: V2ThreadMetadataUpdateResponse__GitInfo | null; + readonly historyMode?: V2ThreadMetadataUpdateResponse__ThreadHistoryMode; + readonly id: string; + readonly model?: string | null; + readonly modelProvider: string; + readonly name?: string | null; + readonly originator?: string | null; + readonly parentThreadId?: string | null; + readonly path?: string | null; + readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadMetadataUpdateResponse__ReasoningEffort | null; + readonly recencyAt?: number | null; + readonly section?: V2ThreadMetadataUpdateResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; + readonly sessionId: string; + readonly source: V2ThreadMetadataUpdateResponse__SessionSource; + readonly status: V2ThreadMetadataUpdateResponse__ThreadStatus; + readonly threadSource?: V2ThreadMetadataUpdateResponse__ThreadSource | null; + readonly turns: ReadonlyArray; + readonly updatedAt: number; +}; +export const V2ThreadMetadataUpdateResponse__Thread = Schema.Struct({ + agentNickname: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional random unique nickname assigned to an AgentControl-spawned sub-agent.", + }), + Schema.Null, + ]), + ), + agentRole: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.", + }), + Schema.Null, + ]), + ), + cliVersion: Schema.String.annotate({ + description: "Version of the CLI that created the thread.", }), + createdAt: Schema.Number.annotate({ + description: "Unix timestamp (in seconds) when the thread was created.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -31049,13 +44995,31 @@ export const V2ThreadListResponse__Thread = Schema.Struct({ ]), ), gitInfo: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__GitInfo, Schema.Null]).annotate({ + Schema.Union([V2ThreadMetadataUpdateResponse__GitInfo, Schema.Null]).annotate({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -31065,6 +45029,15 @@ export const V2ThreadListResponse__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31083,101 +45056,100 @@ export const V2ThreadListResponse__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadMetadataUpdateResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadListResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadListResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => + V2ThreadMetadataUpdateResponse__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( - Schema.Union([V2ThreadListResponse__ThreadSource, Schema.Null]).annotate({ + Schema.Union([V2ThreadMetadataUpdateResponse__ThreadSource, Schema.Null]).annotate({ description: "Optional analytics source classification for this thread.", }), ), - turns: Schema.Array(V2ThreadListResponse__Turn).annotate({ + turns: Schema.Array(V2ThreadMetadataUpdateResponse__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadMetadataUpdateResponse__Thread" }); -export type V2ThreadMetadataUpdateResponse__Thread = { +export type V2ThreadReadResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadReadResponse__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; - readonly gitInfo?: V2ThreadMetadataUpdateResponse__GitInfo | null; + readonly gitInfo?: V2ThreadReadResponse__GitInfo | null; + readonly historyMode?: V2ThreadReadResponse__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadReadResponse__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadReadResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadMetadataUpdateResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; - readonly threadSource?: V2ThreadMetadataUpdateResponse__ThreadSource | null; - readonly turns: ReadonlyArray; + readonly source: V2ThreadReadResponse__SessionSource; + readonly status: V2ThreadReadResponse__ThreadStatus; + readonly threadSource?: V2ThreadReadResponse__ThreadSource | null; + readonly turns: ReadonlyArray; readonly updatedAt: number; }; -export const V2ThreadMetadataUpdateResponse__Thread = Schema.Struct({ +export const V2ThreadReadResponse__Thread = Schema.Struct({ agentNickname: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31201,11 +45173,11 @@ export const V2ThreadMetadataUpdateResponse__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadReadResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -31218,13 +45190,31 @@ export const V2ThreadMetadataUpdateResponse__Thread = Schema.Struct({ ]), ), gitInfo: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__GitInfo, Schema.Null]).annotate({ + Schema.Union([V2ThreadReadResponse__GitInfo, Schema.Null]).annotate({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadReadResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -31234,6 +45224,15 @@ export const V2ThreadMetadataUpdateResponse__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31252,101 +45251,98 @@ export const V2ThreadMetadataUpdateResponse__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadReadResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadMetadataUpdateResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => V2ThreadReadResponse__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadMetadataUpdateResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => V2ThreadReadResponse__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( - Schema.Union([V2ThreadMetadataUpdateResponse__ThreadSource, Schema.Null]).annotate({ + Schema.Union([V2ThreadReadResponse__ThreadSource, Schema.Null]).annotate({ description: "Optional analytics source classification for this thread.", }), ), - turns: Schema.Array(V2ThreadMetadataUpdateResponse__Turn).annotate({ + turns: Schema.Array(V2ThreadReadResponse__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadReadResponse__Thread" }); -export type V2ThreadReadResponse__Thread = { +export type V2ThreadResumeResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadResumeResponse__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; - readonly gitInfo?: V2ThreadReadResponse__GitInfo | null; + readonly gitInfo?: V2ThreadResumeResponse__GitInfo | null; + readonly historyMode?: V2ThreadResumeResponse__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadResumeResponse__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadResumeResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadReadResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; - readonly threadSource?: V2ThreadReadResponse__ThreadSource | null; - readonly turns: ReadonlyArray; + readonly source: V2ThreadResumeResponse__SessionSource; + readonly status: V2ThreadResumeResponse__ThreadStatus; + readonly threadSource?: V2ThreadResumeResponse__ThreadSource | null; + readonly turns: ReadonlyArray; readonly updatedAt: number; }; -export const V2ThreadReadResponse__Thread = Schema.Struct({ +export const V2ThreadResumeResponse__Thread = Schema.Struct({ agentNickname: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31370,11 +45366,11 @@ export const V2ThreadReadResponse__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -31387,13 +45383,31 @@ export const V2ThreadReadResponse__Thread = Schema.Struct({ ]), ), gitInfo: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__GitInfo, Schema.Null]).annotate({ + Schema.Union([V2ThreadResumeResponse__GitInfo, Schema.Null]).annotate({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -31403,6 +45417,15 @@ export const V2ThreadReadResponse__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31421,101 +45444,99 @@ export const V2ThreadReadResponse__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadReadResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadReadResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => V2ThreadResumeResponse__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( - Schema.Union([V2ThreadReadResponse__ThreadSource, Schema.Null]).annotate({ + Schema.Union([V2ThreadResumeResponse__ThreadSource, Schema.Null]).annotate({ description: "Optional analytics source classification for this thread.", }), ), - turns: Schema.Array(V2ThreadReadResponse__Turn).annotate({ + turns: Schema.Array(V2ThreadResumeResponse__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadResumeResponse__Thread" }); -export type V2ThreadResumeResponse__Thread = { +export type V2ThreadRevertResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadRevertResponse__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; - readonly gitInfo?: V2ThreadResumeResponse__GitInfo | null; + readonly gitInfo?: V2ThreadRevertResponse__GitInfo | null; + readonly historyMode?: V2ThreadRevertResponse__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadRevertResponse__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadRevertResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadResumeResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; - readonly threadSource?: V2ThreadResumeResponse__ThreadSource | null; - readonly turns: ReadonlyArray; + readonly source: V2ThreadRevertResponse__SessionSource; + readonly status: V2ThreadRevertResponse__ThreadStatus; + readonly threadSource?: V2ThreadRevertResponse__ThreadSource | null; + readonly turns: ReadonlyArray; readonly updatedAt: number; }; -export const V2ThreadResumeResponse__Thread = Schema.Struct({ +export const V2ThreadRevertResponse__Thread = Schema.Struct({ agentNickname: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31539,11 +45560,11 @@ export const V2ThreadResumeResponse__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -31556,13 +45577,31 @@ export const V2ThreadResumeResponse__Thread = Schema.Struct({ ]), ), gitInfo: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__GitInfo, Schema.Null]).annotate({ + Schema.Union([V2ThreadRevertResponse__GitInfo, Schema.Null]).annotate({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -31572,6 +45611,15 @@ export const V2ThreadResumeResponse__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31590,96 +45638,94 @@ export const V2ThreadResumeResponse__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadRevertResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadResumeResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => + V2ThreadRevertResponse__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadResumeResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => V2ThreadRevertResponse__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__ThreadSource, Schema.Null]).annotate({ + Schema.Union([V2ThreadRevertResponse__ThreadSource, Schema.Null]).annotate({ description: "Optional analytics source classification for this thread.", }), ), - turns: Schema.Array(V2ThreadResumeResponse__Turn).annotate({ + turns: Schema.Array(V2ThreadRevertResponse__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadRevertResponse__Thread" }); export type V2ThreadStartedNotification__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadStartedNotification__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; readonly gitInfo?: V2ThreadStartedNotification__GitInfo | null; + readonly historyMode?: V2ThreadStartedNotification__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadStartedNotification__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadStartedNotification__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadStartedNotification__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; + readonly source: V2ThreadStartedNotification__SessionSource; + readonly status: V2ThreadStartedNotification__ThreadStatus; readonly threadSource?: V2ThreadStartedNotification__ThreadSource | null; readonly turns: ReadonlyArray; readonly updatedAt: number; @@ -31708,11 +45754,11 @@ export const V2ThreadStartedNotification__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -31729,9 +45775,27 @@ export const V2ThreadStartedNotification__Thread = Schema.Struct({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -31741,6 +45805,15 @@ export const V2ThreadStartedNotification__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31759,47 +45832,53 @@ export const V2ThreadStartedNotification__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadStartedNotification__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadStartedNotification__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadStartedNotification__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => + V2ThreadStartedNotification__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( Schema.Union([V2ThreadStartedNotification__ThreadSource, Schema.Null]).annotate({ @@ -31808,47 +45887,40 @@ export const V2ThreadStartedNotification__Thread = Schema.Struct({ ), turns: Schema.Array(V2ThreadStartedNotification__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadStartedNotification__Thread" }); export type V2ThreadStartResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadStartResponse__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; readonly gitInfo?: V2ThreadStartResponse__GitInfo | null; + readonly historyMode?: V2ThreadStartResponse__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadStartResponse__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadStartResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadStartResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; + readonly source: V2ThreadStartResponse__SessionSource; + readonly status: V2ThreadStartResponse__ThreadStatus; readonly threadSource?: V2ThreadStartResponse__ThreadSource | null; readonly turns: ReadonlyArray; readonly updatedAt: number; @@ -31877,11 +45949,11 @@ export const V2ThreadStartResponse__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -31898,9 +45970,27 @@ export const V2ThreadStartResponse__Thread = Schema.Struct({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -31910,6 +46000,15 @@ export const V2ThreadStartResponse__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -31928,47 +46027,51 @@ export const V2ThreadStartResponse__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadStartResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadStartResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => V2ThreadStartResponse__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadStartResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => V2ThreadStartResponse__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( Schema.Union([V2ThreadStartResponse__ThreadSource, Schema.Null]).annotate({ @@ -31977,47 +46080,40 @@ export const V2ThreadStartResponse__Thread = Schema.Struct({ ), turns: Schema.Array(V2ThreadStartResponse__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadStartResponse__Thread" }); export type V2ThreadUnarchiveResponse__Thread = { readonly agentNickname?: string | null; readonly agentRole?: string | null; readonly cliVersion: string; readonly createdAt: number; - readonly cwd: string; + readonly cwd: V2ThreadUnarchiveResponse__AbsolutePathBuf; readonly ephemeral: boolean; readonly forkedFromId?: string | null; readonly gitInfo?: V2ThreadUnarchiveResponse__GitInfo | null; + readonly historyMode?: V2ThreadUnarchiveResponse__ThreadHistoryMode; readonly id: string; + readonly model?: string | null; readonly modelProvider: string; readonly name?: string | null; + readonly originator?: string | null; readonly parentThreadId?: string | null; readonly path?: string | null; readonly preview: string; + readonly projectId: string | null; + readonly reasoningEffort?: V2ThreadUnarchiveResponse__ReasoningEffort | null; readonly recencyAt?: number | null; + readonly section?: V2ThreadUnarchiveResponse__ThreadSection | null; + readonly sectionEnteredAt?: number | null; readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadUnarchiveResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; + readonly source: V2ThreadUnarchiveResponse__SessionSource; + readonly status: V2ThreadUnarchiveResponse__ThreadStatus; readonly threadSource?: V2ThreadUnarchiveResponse__ThreadSource | null; readonly turns: ReadonlyArray; readonly updatedAt: number; @@ -32046,11 +46142,11 @@ export const V2ThreadUnarchiveResponse__Thread = Schema.Struct({ createdAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was created.", format: "int64", - }).check(Schema.isInt()), - 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.", - }), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + cwd: Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__AbsolutePathBuf, + ).annotate({ description: "Working directory captured for the thread." }), ephemeral: Schema.Boolean.annotate({ description: "Whether the thread is ephemeral and should not be materialized on disk.", }), @@ -32067,9 +46163,27 @@ export const V2ThreadUnarchiveResponse__Thread = Schema.Struct({ description: "Optional Git metadata captured when the thread was created.", }), ), + historyMode: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__ThreadHistoryMode, + ).annotate({ + description: "Persisted thread history contract selected when this thread was created.", + default: "legacy", + }), + ), id: Schema.String.annotate({ description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", }), + model: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Current configured model when loaded, otherwise the latest persisted model. Null when unavailable. This is not per-turn execution telemetry.", + }), + Schema.Null, + ]), + ), modelProvider: Schema.String.annotate({ description: "Model provider used for this thread (for example, 'openai').", }), @@ -32079,6 +46193,15 @@ export const V2ThreadUnarchiveResponse__Thread = Schema.Struct({ Schema.Null, ]), ), + originator: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Originator recorded when the thread was created, independent of its current client or executor. Null when the recorded originator is unavailable.", + }), + Schema.Null, + ]), + ), parentThreadId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -32097,47 +46220,53 @@ export const V2ThreadUnarchiveResponse__Thread = Schema.Struct({ preview: Schema.String.annotate({ description: "Usually the first user message in the thread, if available.", }), + projectId: Schema.Union([ + Schema.String.annotate({ + description: "Canonical project assignment owned by app-server, if any.", + }), + Schema.Null, + ]), + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ReasoningEffort, Schema.Null]).annotate({ + description: + "Current configured reasoning effort when loaded, otherwise the latest persisted effort. Null when unset or unavailable. This is not per-turn execution telemetry.", + }), + ), recencyAt: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Unix timestamp (in seconds) used for thread recency ordering.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), + section: Schema.optionalKey( + Schema.Union([V2ThreadUnarchiveResponse__ThreadSection, Schema.Null]).annotate({ + description: "The independently persisted section selected for this thread, if any.", + }), + ), + sectionEnteredAt: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Unix timestamp in seconds when the thread entered its current section.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), sessionId: Schema.String.annotate({ description: "Session id shared by threads that belong to the same session tree.", }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadUnarchiveResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, + source: Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__SessionSource, ).annotate({ description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadUnarchiveResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, + status: Schema.suspend( + (): Schema.Codec => + V2ThreadUnarchiveResponse__ThreadStatus, ).annotate({ description: "Current runtime status for the thread." }), threadSource: Schema.optionalKey( Schema.Union([V2ThreadUnarchiveResponse__ThreadSource, Schema.Null]).annotate({ @@ -32146,139 +46275,13 @@ export const V2ThreadUnarchiveResponse__Thread = Schema.Struct({ ), turns: Schema.Array(V2ThreadUnarchiveResponse__Turn).annotate({ description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", + "Only populated on `thread/resume`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", }), updatedAt: Schema.Number.annotate({ description: "Unix timestamp (in seconds) when the thread was last updated.", format: "int64", - }).check(Schema.isInt()), -}); - -export type McpServerElicitationRequestParams__McpElicitationPrimitiveSchema = - | McpServerElicitationRequestParams__McpElicitationEnumSchema - | McpServerElicitationRequestParams__McpElicitationStringSchema - | McpServerElicitationRequestParams__McpElicitationNumberSchema - | McpServerElicitationRequestParams__McpElicitationBooleanSchema; -export const McpServerElicitationRequestParams__McpElicitationPrimitiveSchema = Schema.Union([ - McpServerElicitationRequestParams__McpElicitationEnumSchema, - McpServerElicitationRequestParams__McpElicitationStringSchema, - McpServerElicitationRequestParams__McpElicitationNumberSchema, - McpServerElicitationRequestParams__McpElicitationBooleanSchema, -]); - -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__ThreadStartedNotification = { - readonly thread: ServerNotification__Thread; -}; -export const ServerNotification__ThreadStartedNotification = Schema.Struct({ - thread: ServerNotification__Thread, -}); - -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__McpElicitationPrimitiveSchema = - | ServerRequest__McpElicitationEnumSchema - | ServerRequest__McpElicitationStringSchema - | ServerRequest__McpElicitationNumberSchema - | ServerRequest__McpElicitationBooleanSchema; -export const ServerRequest__McpElicitationPrimitiveSchema = Schema.Union([ - ServerRequest__McpElicitationEnumSchema, - ServerRequest__McpElicitationStringSchema, - ServerRequest__McpElicitationNumberSchema, - ServerRequest__McpElicitationBooleanSchema, -]); - -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, - ]), - ), - }); + }).check(Schema.isInt().annotate({ expected: "an integer" })), +}).annotate({ identifier: "V2ThreadUnarchiveResponse__Thread" }); export type McpServerElicitationRequestParams__McpElicitationSchema = { readonly $schema?: string | null; @@ -32299,12 +46302,20 @@ export const McpServerElicitationRequestParams__McpElicitationSchema = Schema.St }).annotate({ description: "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.", + identifier: "McpServerElicitationRequestParams__McpElicitationSchema", }); +export type ServerNotification__ThreadStartedNotification = { + readonly thread: ServerNotification__Thread; +}; +export const ServerNotification__ThreadStartedNotification = Schema.Struct({ + thread: ServerNotification__Thread, +}).annotate({ identifier: "ServerNotification__ThreadStartedNotification" }); + export type ServerNotification__GuardianApprovalReviewAction = | { readonly command: string; - readonly cwd: ServerNotification__AbsolutePathBuf; + readonly cwd: ServerNotification__LegacyAppPathString; readonly source: ServerNotification__GuardianCommandSource; readonly type: "command"; } @@ -32316,8 +46327,15 @@ export type ServerNotification__GuardianApprovalReviewAction = readonly type: "execve"; } | { - readonly cwd: ServerNotification__AbsolutePathBuf; - readonly files: ReadonlyArray; + readonly approvalId: string; + readonly cwd: ServerNotification__LegacyAppPathString; + readonly processId: string; + readonly stdin: string; + readonly type: "writeStdin"; + } + | { + readonly cwd: ServerNotification__LegacyAppPathString; + readonly files: ReadonlyArray; readonly type: "applyPatch"; } | { @@ -32344,7 +46362,7 @@ export const ServerNotification__GuardianApprovalReviewAction = Schema.Union( [ Schema.Struct({ command: Schema.String, - cwd: ServerNotification__AbsolutePathBuf, + cwd: ServerNotification__LegacyAppPathString, source: ServerNotification__GuardianCommandSource, type: Schema.Literal("command").annotate({ title: "CommandGuardianApprovalReviewActionType", @@ -32358,8 +46376,20 @@ export const ServerNotification__GuardianApprovalReviewAction = Schema.Union( type: Schema.Literal("execve").annotate({ title: "ExecveGuardianApprovalReviewActionType" }), }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), Schema.Struct({ - cwd: ServerNotification__AbsolutePathBuf, - files: Schema.Array(ServerNotification__AbsolutePathBuf), + approvalId: Schema.String, + cwd: ServerNotification__LegacyAppPathString, + processId: Schema.String, + stdin: Schema.String, + type: Schema.Literal("writeStdin").annotate({ + title: "WriteStdinGuardianApprovalReviewActionType", + }), + }).annotate({ + title: "WriteStdinGuardianApprovalReviewAction", + description: "A child approval for input to an existing command execution item.", + }), + Schema.Struct({ + cwd: ServerNotification__LegacyAppPathString, + files: Schema.Array(ServerNotification__LegacyAppPathString), type: Schema.Literal("applyPatch").annotate({ title: "ApplyPatchGuardianApprovalReviewActionType", }), @@ -32367,8 +46397,12 @@ export const ServerNotification__GuardianApprovalReviewAction = Schema.Union( Schema.Struct({ host: Schema.String, port: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), protocol: ServerNotification__NetworkApprovalProtocol, target: Schema.String, type: Schema.Literal("networkAccess").annotate({ @@ -32394,10 +46428,10 @@ export const ServerNotification__GuardianApprovalReviewAction = Schema.Union( }).annotate({ title: "RequestPermissionsGuardianApprovalReviewAction" }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerNotification__GuardianApprovalReviewAction" }); export type ServerRequest__PermissionsRequestApprovalParams = { - readonly cwd: ServerRequest__AbsolutePathBuf; + readonly cwd: ServerRequest__LegacyAppPathString; readonly environmentId?: string | null; readonly itemId: string; readonly permissions: ServerRequest__RequestPermissionProfile; @@ -32407,7 +46441,7 @@ export type ServerRequest__PermissionsRequestApprovalParams = { readonly turnId: string; }; export const ServerRequest__PermissionsRequestApprovalParams = Schema.Struct({ - cwd: ServerRequest__AbsolutePathBuf, + cwd: ServerRequest__LegacyAppPathString, environmentId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), itemId: Schema.String, permissions: ServerRequest__RequestPermissionProfile, @@ -32415,10 +46449,10 @@ export const ServerRequest__PermissionsRequestApprovalParams = Schema.Struct({ startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this approval request started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), threadId: Schema.String, turnId: Schema.String, -}); +}).annotate({ identifier: "ServerRequest__PermissionsRequestApprovalParams" }); export type ServerRequest__McpElicitationSchema = { readonly $schema?: string | null; @@ -32434,12 +46468,13 @@ export const ServerRequest__McpElicitationSchema = Schema.Struct({ }).annotate({ description: "Typed form schema for MCP `elicitation/create` requests.\n\nThis matches the `requestedSchema` shape from the MCP 2025-11-25 `ElicitRequestFormParams` schema.", + identifier: "ServerRequest__McpElicitationSchema", }); export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewAction = | { readonly command: string; - readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf; + readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString; readonly source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource; readonly type: "command"; } @@ -32451,8 +46486,15 @@ export type V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalR readonly type: "execve"; } | { - readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf; - readonly files: ReadonlyArray; + readonly approvalId: string; + readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString; + readonly processId: string; + readonly stdin: string; + readonly type: "writeStdin"; + } + | { + readonly cwd: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString; + readonly files: ReadonlyArray; readonly type: "applyPatch"; } | { @@ -32480,7 +46522,7 @@ export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApproval [ Schema.Struct({ command: Schema.String, - cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf, + cwd: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, source: V2ItemGuardianApprovalReviewCompletedNotification__GuardianCommandSource, type: Schema.Literal("command").annotate({ title: "CommandGuardianApprovalReviewActionType", @@ -32496,8 +46538,20 @@ export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApproval }), }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), Schema.Struct({ - cwd: V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf, - files: Schema.Array(V2ItemGuardianApprovalReviewCompletedNotification__AbsolutePathBuf), + approvalId: Schema.String, + cwd: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, + processId: Schema.String, + stdin: Schema.String, + type: Schema.Literal("writeStdin").annotate({ + title: "WriteStdinGuardianApprovalReviewActionType", + }), + }).annotate({ + title: "WriteStdinGuardianApprovalReviewAction", + description: "A child approval for input to an existing command execution item.", + }), + Schema.Struct({ + cwd: V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString, + files: Schema.Array(V2ItemGuardianApprovalReviewCompletedNotification__LegacyAppPathString), type: Schema.Literal("applyPatch").annotate({ title: "ApplyPatchGuardianApprovalReviewActionType", }), @@ -32505,8 +46559,12 @@ export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApproval Schema.Struct({ host: Schema.String, port: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), protocol: V2ItemGuardianApprovalReviewCompletedNotification__NetworkApprovalProtocol, target: Schema.String, type: Schema.Literal("networkAccess").annotate({ @@ -32532,12 +46590,14 @@ export const V2ItemGuardianApprovalReviewCompletedNotification__GuardianApproval }).annotate({ title: "RequestPermissionsGuardianApprovalReviewAction" }), ], { mode: "oneOf" }, - ); + ).annotate({ + identifier: "V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewAction", + }); export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewAction = | { readonly command: string; - readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf; + readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString; readonly source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource; readonly type: "command"; } @@ -32549,8 +46609,15 @@ export type V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalRev readonly type: "execve"; } | { - readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf; - readonly files: ReadonlyArray; + readonly approvalId: string; + readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString; + readonly processId: string; + readonly stdin: string; + readonly type: "writeStdin"; + } + | { + readonly cwd: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString; + readonly files: ReadonlyArray; readonly type: "applyPatch"; } | { @@ -32578,7 +46645,7 @@ export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalRe [ Schema.Struct({ command: Schema.String, - cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf, + cwd: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, source: V2ItemGuardianApprovalReviewStartedNotification__GuardianCommandSource, type: Schema.Literal("command").annotate({ title: "CommandGuardianApprovalReviewActionType", @@ -32594,8 +46661,20 @@ export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalRe }), }).annotate({ title: "ExecveGuardianApprovalReviewAction" }), Schema.Struct({ - cwd: V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf, - files: Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__AbsolutePathBuf), + approvalId: Schema.String, + cwd: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, + processId: Schema.String, + stdin: Schema.String, + type: Schema.Literal("writeStdin").annotate({ + title: "WriteStdinGuardianApprovalReviewActionType", + }), + }).annotate({ + title: "WriteStdinGuardianApprovalReviewAction", + description: "A child approval for input to an existing command execution item.", + }), + Schema.Struct({ + cwd: V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString, + files: Schema.Array(V2ItemGuardianApprovalReviewStartedNotification__LegacyAppPathString), type: Schema.Literal("applyPatch").annotate({ title: "ApplyPatchGuardianApprovalReviewActionType", }), @@ -32603,8 +46682,12 @@ export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalRe Schema.Struct({ host: Schema.String, port: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), protocol: V2ItemGuardianApprovalReviewStartedNotification__NetworkApprovalProtocol, target: Schema.String, type: Schema.Literal("networkAccess").annotate({ @@ -32630,12 +46713,12 @@ export const V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalRe }).annotate({ title: "RequestPermissionsGuardianApprovalReviewAction" }), ], { mode: "oneOf" }, - ); + ).annotate({ + identifier: "V2ItemGuardianApprovalReviewStartedNotification__GuardianApprovalReviewAction", + }); -export type ServerNotification__ItemGuardianApprovalReviewCompletedNotification = { +export type ServerNotification__ItemGuardianApprovalReviewStartedNotification = { readonly action: ServerNotification__GuardianApprovalReviewAction; - readonly completedAtMs: number; - readonly decisionSource: ServerNotification__AutoReviewDecisionSource; readonly review: ServerNotification__GuardianApprovalReview; readonly reviewId: string; readonly startedAtMs: number; @@ -32643,24 +46726,19 @@ export type ServerNotification__ItemGuardianApprovalReviewCompletedNotification readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__ItemGuardianApprovalReviewCompletedNotification = Schema.Struct({ +export const ServerNotification__ItemGuardianApprovalReviewStartedNotification = Schema.Struct({ action: ServerNotification__GuardianApprovalReviewAction, - completedAtMs: Schema.Number.annotate({ - description: "Unix timestamp (in milliseconds) when this review completed.", - format: "int64", - }).check(Schema.isInt()), - decisionSource: ServerNotification__AutoReviewDecisionSource, review: ServerNotification__GuardianApprovalReview, reviewId: Schema.String.annotate({ description: "Stable identifier for this review." }), startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this review started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), 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.", + "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) - stdin reviews, which refer to the existing parent command item and have a separate approval ID in the action payload - 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, ]), @@ -32670,10 +46748,13 @@ export const ServerNotification__ItemGuardianApprovalReviewCompletedNotification }).annotate({ description: "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", + identifier: "ServerNotification__ItemGuardianApprovalReviewStartedNotification", }); -export type ServerNotification__ItemGuardianApprovalReviewStartedNotification = { +export type ServerNotification__ItemGuardianApprovalReviewCompletedNotification = { readonly action: ServerNotification__GuardianApprovalReviewAction; + readonly completedAtMs: number; + readonly decisionSource: ServerNotification__AutoReviewDecisionSource; readonly review: ServerNotification__GuardianApprovalReview; readonly reviewId: string; readonly startedAtMs: number; @@ -32681,19 +46762,24 @@ export type ServerNotification__ItemGuardianApprovalReviewStartedNotification = readonly threadId: string; readonly turnId: string; }; -export const ServerNotification__ItemGuardianApprovalReviewStartedNotification = Schema.Struct({ +export const ServerNotification__ItemGuardianApprovalReviewCompletedNotification = Schema.Struct({ action: ServerNotification__GuardianApprovalReviewAction, + completedAtMs: Schema.Number.annotate({ + description: "Unix timestamp (in milliseconds) when this review completed.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + decisionSource: ServerNotification__AutoReviewDecisionSource, review: ServerNotification__GuardianApprovalReview, reviewId: Schema.String.annotate({ description: "Stable identifier for this review." }), startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this review started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), 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.", + "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) - stdin reviews, which refer to the existing parent command item and have a separate approval ID in the action payload - 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, ]), @@ -32703,44 +46789,67 @@ export const ServerNotification__ItemGuardianApprovalReviewStartedNotification = }).annotate({ description: "[UNSTABLE] Temporary notification payload for approval auto-review. This shape is expected to change soon.", + identifier: "ServerNotification__ItemGuardianApprovalReviewCompletedNotification", }); export type ServerRequest__McpServerElicitationRequestParams = | { - readonly _meta?: unknown; + readonly serverName: string; + readonly threadId: string; + readonly turnId?: string | null; + readonly _meta?: Schema.Json; readonly message: string; readonly mode: "form"; readonly requestedSchema: ServerRequest__McpElicitationSchema; + } + | { readonly serverName: string; readonly threadId: string; readonly turnId?: string | null; - } - | { - readonly _meta?: unknown; + readonly _meta?: Schema.Json; readonly message: string; readonly mode: "openai/form"; - readonly requestedSchema: unknown; + readonly requestedSchema: Schema.Json; + } + | { readonly serverName: string; readonly threadId: string; readonly turnId?: string | null; + readonly _meta?: Schema.Json; + readonly message: string; + readonly mode: "openaiForm"; + readonly requestedSchema: Schema.Json; } | { - readonly _meta?: unknown; + readonly serverName: string; + readonly threadId: string; + readonly turnId?: string | null; + readonly _meta?: Schema.Json; readonly elicitationId: string; readonly message: string; readonly mode: "url"; readonly url: string; - readonly serverName: string; - readonly threadId: string; - readonly turnId?: string | null; }; export const ServerRequest__McpServerElicitationRequestParams = Schema.Union( [ Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), + serverName: Schema.String, + threadId: Schema.String, + turnId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Active Codex turn when this elicitation was observed, if app-server could correlate one.\n\nThis is nullable because MCP models elicitation as a standalone server-to-client request identified by the MCP server request id. It may be triggered during a turn, but turn context is app-server correlation rather than part of the protocol identity of the elicitation itself.", + }), + Schema.Null, + ]), + ), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), message: Schema.String, mode: Schema.Literal("form"), requestedSchema: ServerRequest__McpElicitationSchema, + }), + Schema.Struct({ serverName: Schema.String, threadId: Schema.String, turnId: Schema.optionalKey( @@ -32752,12 +46861,12 @@ export const ServerRequest__McpServerElicitationRequestParams = Schema.Union( Schema.Null, ]), ), - }), - Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), message: Schema.String, mode: Schema.Literal("openai/form"), - requestedSchema: Schema.Unknown, + requestedSchema: Schema.Json.annotate({ expected: "JSON value" }), + }), + Schema.Struct({ serverName: Schema.String, threadId: Schema.String, turnId: Schema.optionalKey( @@ -32769,13 +46878,12 @@ export const ServerRequest__McpServerElicitationRequestParams = Schema.Union( Schema.Null, ]), ), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + message: Schema.String, + mode: Schema.Literal("openaiForm"), + requestedSchema: Schema.Json.annotate({ expected: "JSON value" }), }), Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - elicitationId: Schema.String, - message: Schema.String, - mode: Schema.Literal("url"), - url: Schema.String, serverName: Schema.String, threadId: Schema.String, turnId: Schema.optionalKey( @@ -32787,10 +46895,15 @@ export const ServerRequest__McpServerElicitationRequestParams = Schema.Union( Schema.Null, ]), ), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + elicitationId: Schema.String, + message: Schema.String, + mode: Schema.Literal("url"), + url: Schema.String, }), ], { mode: "oneOf" }, -); +).annotate({ identifier: "ServerRequest__McpServerElicitationRequestParams" }); export type ApplyPatchApprovalParams = { readonly callId: string; @@ -32832,10 +46945,11 @@ export const ApplyPatchApprovalResponse = Schema.Struct({ decision: ApplyPatchApprovalResponse__ReviewDecision, }).annotate({ title: "ApplyPatchApprovalResponse" }); -export type AttestationGenerateParams = {}; -export const AttestationGenerateParams = Schema.Struct({}).annotate({ - title: "AttestationGenerateParams", -}); +export type AttestationGenerateParams = { readonly [x: string]: Schema.Json }; +export const AttestationGenerateParams = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "AttestationGenerateParams" }); export type AttestationGenerateResponse = { readonly token: string }; export const AttestationGenerateResponse = Schema.Struct({ @@ -32941,6 +47055,26 @@ export type ClientRequest = readonly method: "thread/metadata/update"; readonly params: ClientRequest__ThreadMetadataUpdateParams; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "thread/attachment/add"; + readonly params: ClientRequest__ThreadAttachmentAddParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "thread/attachment/list"; + readonly params: ClientRequest__ThreadAttachmentListParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "thread/attachment/remove"; + readonly params: ClientRequest__ThreadAttachmentRemoveParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "thread/section/move"; + readonly params: ClientRequest__ThreadSectionMoveParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "thread/unarchive"; @@ -32963,14 +47097,34 @@ export type ClientRequest = } | { readonly id: ClientRequest__RequestId; - readonly method: "thread/rollback"; - readonly params: ClientRequest__ThreadRollbackParams; + readonly method: "thread/revert"; + readonly params: ClientRequest__ThreadRevertParams; } | { readonly id: ClientRequest__RequestId; readonly method: "thread/list"; readonly params: ClientRequest__ThreadListParams; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "threadSection/list"; + readonly params: ClientRequest__ThreadSectionListParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "threadSection/create"; + readonly params: ClientRequest__ThreadSectionCreateParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "threadSection/update"; + readonly params: ClientRequest__ThreadSectionUpdateParams; + } + | { + readonly id: ClientRequest__RequestId; + readonly method: "threadSection/delete"; + readonly params: ClientRequest__ThreadSectionDeleteParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "thread/loaded/list"; @@ -32981,6 +47135,16 @@ 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/items/list"; + readonly params: ClientRequest__ThreadItemsListParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "thread/inject_items"; @@ -33026,6 +47190,11 @@ export type ClientRequest = readonly method: "plugin/installed"; readonly params: ClientRequest__PluginInstalledParams; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "plugin/reconcile"; + readonly params: ClientRequest__PluginReconcileParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "plugin/read"; @@ -33234,7 +47403,7 @@ export type ClientRequest = | { readonly id: ClientRequest__RequestId; readonly method: "account/rateLimits/read"; - readonly params?: null; + readonly params?: ClientRequest__GetAccountRateLimitsParams | null; } | { readonly id: ClientRequest__RequestId; @@ -33244,7 +47413,7 @@ export type ClientRequest = | { readonly id: ClientRequest__RequestId; readonly method: "account/usage/read"; - readonly params?: null; + readonly params?: ClientRequest__GetAccountTokenUsageParams | null; } | { readonly id: ClientRequest__RequestId; @@ -33296,6 +47465,11 @@ export type ClientRequest = readonly method: "externalAgentConfig/import"; readonly params: ClientRequest__ExternalAgentConfigImportParams; } + | { + readonly id: ClientRequest__RequestId; + readonly method: "externalAgentConfig/import/recordHistory"; + readonly params: ClientRequest__ExternalAgentConfigImportHistoryRecordParams; + } | { readonly id: ClientRequest__RequestId; readonly method: "externalAgentConfig/import/readHistories"; @@ -33394,6 +47568,34 @@ export const ClientRequest = Schema.Union( }), params: ClientRequest__ThreadMetadataUpdateParams, }).annotate({ title: "Thread/metadata/updateRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("thread/attachment/add").annotate({ + title: "Thread/attachment/addRequestMethod", + }), + params: ClientRequest__ThreadAttachmentAddParams, + }).annotate({ title: "Thread/attachment/addRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("thread/attachment/list").annotate({ + title: "Thread/attachment/listRequestMethod", + }), + params: ClientRequest__ThreadAttachmentListParams, + }).annotate({ title: "Thread/attachment/listRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("thread/attachment/remove").annotate({ + title: "Thread/attachment/removeRequestMethod", + }), + params: ClientRequest__ThreadAttachmentRemoveParams, + }).annotate({ title: "Thread/attachment/removeRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("thread/section/move").annotate({ + title: "Thread/section/moveRequestMethod", + }), + params: ClientRequest__ThreadSectionMoveParams, + }).annotate({ title: "Thread/section/moveRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("thread/unarchive").annotate({ @@ -33424,14 +47626,42 @@ export const ClientRequest = Schema.Union( }).annotate({ title: "Thread/approveGuardianDeniedActionRequest" }), Schema.Struct({ id: ClientRequest__RequestId, - method: Schema.Literal("thread/rollback").annotate({ title: "Thread/rollbackRequestMethod" }), - params: ClientRequest__ThreadRollbackParams, - }).annotate({ title: "Thread/rollbackRequest" }), + method: Schema.Literal("thread/revert").annotate({ title: "Thread/revertRequestMethod" }), + params: ClientRequest__ThreadRevertParams, + }).annotate({ title: "Thread/revertRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("thread/list").annotate({ title: "Thread/listRequestMethod" }), params: ClientRequest__ThreadListParams, }).annotate({ title: "Thread/listRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("threadSection/list").annotate({ + title: "ThreadSection/listRequestMethod", + }), + params: ClientRequest__ThreadSectionListParams, + }).annotate({ title: "ThreadSection/listRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("threadSection/create").annotate({ + title: "ThreadSection/createRequestMethod", + }), + params: ClientRequest__ThreadSectionCreateParams, + }).annotate({ title: "ThreadSection/createRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("threadSection/update").annotate({ + title: "ThreadSection/updateRequestMethod", + }), + params: ClientRequest__ThreadSectionUpdateParams, + }).annotate({ title: "ThreadSection/updateRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("threadSection/delete").annotate({ + title: "ThreadSection/deleteRequestMethod", + }), + params: ClientRequest__ThreadSectionDeleteParams, + }).annotate({ title: "ThreadSection/deleteRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("thread/loaded/list").annotate({ @@ -33444,6 +47674,20 @@ 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/items/list").annotate({ + title: "Thread/items/listRequestMethod", + }), + params: ClientRequest__ThreadItemsListParams, + }).annotate({ title: "Thread/items/listRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("thread/inject_items").annotate({ @@ -33503,6 +47747,13 @@ export const ClientRequest = Schema.Union( }), params: ClientRequest__PluginInstalledParams, }).annotate({ title: "Plugin/installedRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("plugin/reconcile").annotate({ + title: "Plugin/reconcileRequestMethod", + }), + params: ClientRequest__PluginReconcileParams, + }).annotate({ title: "Plugin/reconcileRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("plugin/read").annotate({ title: "Plugin/readRequestMethod" }), @@ -33759,7 +48010,9 @@ export const ClientRequest = Schema.Union( method: Schema.Literal("account/rateLimits/read").annotate({ title: "Account/rateLimits/readRequestMethod", }), - params: Schema.optionalKey(Schema.Null), + params: Schema.optionalKey( + Schema.Union([ClientRequest__GetAccountRateLimitsParams, Schema.Null]), + ), }).annotate({ title: "Account/rateLimits/readRequest" }), Schema.Struct({ id: ClientRequest__RequestId, @@ -33773,7 +48026,9 @@ export const ClientRequest = Schema.Union( method: Schema.Literal("account/usage/read").annotate({ title: "Account/usage/readRequestMethod", }), - params: Schema.optionalKey(Schema.Null), + params: Schema.optionalKey( + Schema.Union([ClientRequest__GetAccountTokenUsageParams, Schema.Null]), + ), }).annotate({ title: "Account/usage/readRequest" }), Schema.Struct({ id: ClientRequest__RequestId, @@ -33852,6 +48107,13 @@ export const ClientRequest = Schema.Union( }), params: ClientRequest__ExternalAgentConfigImportParams, }).annotate({ title: "ExternalAgentConfig/importRequest" }), + Schema.Struct({ + id: ClientRequest__RequestId, + method: Schema.Literal("externalAgentConfig/import/recordHistory").annotate({ + title: "ExternalAgentConfig/import/recordHistoryRequestMethod", + }), + params: ClientRequest__ExternalAgentConfigImportHistoryRecordParams, + }).annotate({ title: "ExternalAgentConfig/import/recordHistoryRequest" }), Schema.Struct({ id: ClientRequest__RequestId, method: Schema.Literal("externalAgentConfig/import/readHistories").annotate({ @@ -33903,39 +48165,6 @@ export const ClientRequest__AdditionalContextEntry = Schema.Struct({ value: Schema.String, }); -export type ClientRequest__ByteRange = { readonly end: number; readonly start: number }; -export const ClientRequest__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 ClientRequest__CapabilityRootLocation = { - readonly environmentId: string; - readonly path: string; - readonly type: "environment"; -}; -export const ClientRequest__CapabilityRootLocation = Schema.Union( - [ - Schema.Struct({ - environmentId: Schema.String, - path: Schema.String.annotate({ - description: "Absolute path for the root in the selected environment.", - }), - type: Schema.Literal("environment").annotate({ - title: "EnvironmentCapabilityRootLocationType", - }), - }).annotate({ - title: "EnvironmentCapabilityRootLocation", - description: "A path owned by an execution environment.", - }), - ], - { mode: "oneOf" }, -).annotate({ description: "Location used to resolve a selected capability root." }); - export type ClientRequest__CodexResponseHandoffMode = "thinking" | "commentary" | "bemTags"; export const ClientRequest__CodexResponseHandoffMode = Schema.Literals([ "thinking", @@ -33952,11 +48181,21 @@ export const ClientRequest__CollaborationMode = Schema.Struct({ settings: ClientRequest__Settings, }).annotate({ description: "Collaboration mode for a Codex session." }); +export type ClientRequest__CyberAccessProgram = "standard" | "daybreakBlue" | "daybreakRed"; +export const ClientRequest__CyberAccessProgram = Schema.Literals([ + "standard", + "daybreakBlue", + "daybreakRed", +]).annotate({ + description: + "Requested cyber treatment for a ChatGPT-authenticated Codex turn. Authorization and model-tier restrictions remain server-owned.", +}); + export type ClientRequest__DynamicToolSpec = | { readonly deferLoading?: boolean; readonly description: string; - readonly inputSchema: unknown; + readonly inputSchema: Schema.Json; readonly name: string; readonly type: "function"; } @@ -33971,7 +48210,7 @@ export const ClientRequest__DynamicToolSpec = Schema.Union( Schema.Struct({ deferLoading: Schema.optionalKey(Schema.Boolean), description: Schema.String, - inputSchema: Schema.Unknown, + inputSchema: Schema.Json.annotate({ expected: "JSON value" }), name: Schema.String, type: Schema.Literal("function").annotate({ title: "FunctionDynamicToolSpecType" }), }).annotate({ title: "FunctionDynamicToolSpec" }), @@ -34000,8 +48239,12 @@ export const ClientRequest__MultiAgentMode = Schema.Union( "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", }); -export type ClientRequest__NetworkAccess = "restricted" | "enabled"; -export const ClientRequest__NetworkAccess = Schema.Literals(["restricted", "enabled"]); +export type ClientRequest__PluginSearchScope = "global" | "workspace" | "personal"; +export const ClientRequest__PluginSearchScope = Schema.Literals([ + "global", + "workspace", + "personal", +]); export type ClientRequest__ProcessTerminalSize = { readonly cols: number; readonly rows: number }; export const ClientRequest__ProcessTerminalSize = Schema.Struct({ @@ -34009,16 +48252,26 @@ export const ClientRequest__ProcessTerminalSize = Schema.Struct({ description: "Terminal width in character cells.", format: "uint16", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), rows: Schema.Number.annotate({ description: "Terminal height in character cells.", format: "uint16", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), }).annotate({ description: "PTY size in character cells for `process/spawn` PTY sessions." }); +export type ClientRequest__ProjectRoot = { readonly path: ClientRequest__AbsolutePathBuf }; +export const ClientRequest__ProjectRoot = Schema.Struct({ path: ClientRequest__AbsolutePathBuf }); + +export type ClientRequest__ProjectSortKey = "position" | "recencyAt"; +export const ClientRequest__ProjectSortKey = Schema.Literals(["position", "recencyAt"]); + export type ClientRequest__RealtimeConversationVersion = "v1" | "v2" | "v3"; export const ClientRequest__RealtimeConversationVersion = Schema.Literals(["v1", "v2", "v3"]); @@ -34113,6 +48366,7 @@ export type ClientRequest__ResponseItem = | { readonly arguments: string; readonly call_id: string; + readonly encrypted_function_args?: ReadonlyArray | null; readonly id?: string | null; readonly internal_chat_message_metadata_passthrough?: ClientRequest__InternalChatMessageMetadataPassthrough | null; readonly name: string; @@ -34120,7 +48374,7 @@ export type ClientRequest__ResponseItem = readonly type: "function_call"; } | { - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly call_id?: string | null; readonly execution: string; readonly id?: string | null; @@ -34129,9 +48383,11 @@ export type ClientRequest__ResponseItem = readonly type: "tool_search_call"; } | { - readonly call_id: string; + readonly call_id?: string | null; readonly id?: string | null; readonly internal_chat_message_metadata_passthrough?: ClientRequest__InternalChatMessageMetadataPassthrough | null; + readonly name?: string | null; + readonly namespace?: string | null; readonly output: ClientRequest__FunctionCallOutputBody; readonly type: "function_call_output"; } @@ -34159,7 +48415,7 @@ export type ClientRequest__ResponseItem = readonly id?: string | null; readonly internal_chat_message_metadata_passthrough?: ClientRequest__InternalChatMessageMetadataPassthrough | null; readonly status: string; - readonly tools: ReadonlyArray; + readonly tools: ReadonlyArray; readonly type: "tool_search_output"; } | { @@ -34183,6 +48439,10 @@ export type ClientRequest__ResponseItem = readonly internal_chat_message_metadata_passthrough?: ClientRequest__InternalChatMessageMetadataPassthrough | null; readonly type: "compaction"; } + | { + readonly reasoning: ClientRequest__ConfigurationReasoning; + readonly type: "configuration_update"; + } | { readonly type: "compaction_trigger" } | { readonly encrypted_content?: string | null; @@ -34252,6 +48512,9 @@ export const ClientRequest__ResponseItem = Schema.Union( Schema.Struct({ arguments: Schema.String, call_id: Schema.String, + encrypted_function_args: Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]), + ), id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), internal_chat_message_metadata_passthrough: Schema.optionalKey( Schema.Union([ClientRequest__InternalChatMessageMetadataPassthrough, Schema.Null]), @@ -34261,7 +48524,7 @@ export const ClientRequest__ResponseItem = Schema.Union( type: Schema.Literal("function_call").annotate({ title: "FunctionCallResponseItemType" }), }).annotate({ title: "FunctionCallResponseItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), execution: Schema.String, id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -34274,11 +48537,13 @@ export const ClientRequest__ResponseItem = Schema.Union( }), }).annotate({ title: "ToolSearchCallResponseItem" }), Schema.Struct({ - call_id: Schema.String, + call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), internal_chat_message_metadata_passthrough: Schema.optionalKey( Schema.Union([ClientRequest__InternalChatMessageMetadataPassthrough, Schema.Null]), ), + name: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), output: ClientRequest__FunctionCallOutputBody, type: Schema.Literal("function_call_output").annotate({ title: "FunctionCallOutputResponseItemType", @@ -34318,7 +48583,7 @@ export const ClientRequest__ResponseItem = Schema.Union( Schema.Union([ClientRequest__InternalChatMessageMetadataPassthrough, Schema.Null]), ), status: Schema.String, - tools: Schema.Array(Schema.Unknown), + tools: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), type: Schema.Literal("tool_search_output").annotate({ title: "ToolSearchOutputResponseItemType", }), @@ -34354,6 +48619,15 @@ export const ClientRequest__ResponseItem = Schema.Union( ), type: Schema.Literal("compaction").annotate({ title: "CompactionResponseItemType" }), }).annotate({ title: "CompactionResponseItem" }), + Schema.Struct({ + reasoning: ClientRequest__ConfigurationReasoning, + type: Schema.Literal("configuration_update").annotate({ + title: "ConfigurationUpdateResponseItemType", + }), + }).annotate({ + title: "ConfigurationUpdateResponseItem", + description: "A durable input control interpreted by the backend at its position in history.", + }), Schema.Struct({ type: Schema.Literal("compaction_trigger").annotate({ title: "CompactionTriggerResponseItemType", @@ -34378,33 +48652,16 @@ export const ClientRequest__ResponseItem = Schema.Union( export type ClientRequest__SelectedCapabilityRoot = { readonly id: string; - readonly location: { - readonly environmentId: string; - readonly path: string; - readonly type: "environment"; - }; + readonly location: ClientRequest__CapabilityRootLocation; }; export const ClientRequest__SelectedCapabilityRoot = Schema.Struct({ id: Schema.String.annotate({ description: "Stable identifier supplied by the capability selection platform.", }), - location: Schema.Union( - [ - Schema.Struct({ - environmentId: Schema.String, - path: Schema.String.annotate({ - description: "Absolute path for the root in the selected environment.", - }), - type: Schema.Literal("environment").annotate({ - title: "EnvironmentCapabilityRootLocationType", - }), - }).annotate({ - title: "EnvironmentCapabilityRootLocation", - description: "A path owned by an execution environment.", - }), - ], - { mode: "oneOf" }, - ).annotate({ description: "Location used to resolve a selected capability root." }), + location: Schema.suspend( + (): Schema.Codec => + ClientRequest__CapabilityRootLocation, + ).annotate({ description: "Where the selected root can be resolved." }), }).annotate({ description: "A user-selected root that can expose one or more runtime capabilities.", }); @@ -34426,16 +48683,24 @@ export const ClientRequest__ThreadRealtimeAudioChunk = Schema.Struct({ data: Schema.String, itemId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), numChannels: Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), sampleRate: Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), samplesPerChannel: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -34454,7 +48719,8 @@ export const ClientRequest__ThreadRealtimeInitialItem = Schema.Struct({ export type ClientRequest__ThreadRealtimeStartTransport = | { readonly type: "websocket" } - | { readonly sdp: string; readonly type: "webrtc" }; + | { readonly sdp: string; readonly type: "webrtc" } + | { readonly callId: string; readonly type: "existingCall" }; export const ClientRequest__ThreadRealtimeStartTransport = Schema.Union( [ Schema.Struct({ @@ -34469,6 +48735,14 @@ export const ClientRequest__ThreadRealtimeStartTransport = Schema.Union( }), type: Schema.Literal("webrtc").annotate({ title: "WebrtcThreadRealtimeStartTransportType" }), }).annotate({ title: "WebrtcThreadRealtimeStartTransport" }), + Schema.Struct({ + callId: Schema.String.annotate({ + description: "Identifier of a realtime call already created and negotiated by the client.", + }), + type: Schema.Literal("existingCall").annotate({ + title: "ExistingCallThreadRealtimeStartTransportType", + }), + }).annotate({ title: "ExistingCallThreadRealtimeStartTransport" }), ], { mode: "oneOf" }, ).annotate({ description: "EXPERIMENTAL - transport used by thread realtime." }); @@ -34487,8 +48761,12 @@ export const ClientRequest__ThreadResumeInitialTurnsPageParams = Schema.Struct({ limit: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Optional turn page size.", format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -34499,6 +48777,13 @@ export const ClientRequest__ThreadResumeInitialTurnsPageParams = Schema.Struct({ ), }); +export type ClientRequest__ThreadSearchSortKey = "created_at" | "updated_at" | "recency_at"; +export const ClientRequest__ThreadSearchSortKey = Schema.Literals([ + "created_at", + "updated_at", + "recency_at", +]); + export type ClientRequest__TurnEnvironmentParams = { readonly cwd: ClientRequest__LegacyAppPathString; readonly environmentId: string; @@ -34524,6 +48809,7 @@ export type CommandExecutionRequestApprovalParams = { readonly cwd?: CommandExecutionRequestApprovalParams__LegacyAppPathString | null; readonly environmentId?: string | null; readonly itemId: string; + readonly kind?: CommandExecutionRequestApprovalParams__CommandExecutionApprovalKind; readonly networkApprovalContext?: CommandExecutionRequestApprovalParams__NetworkApprovalContext | null; readonly proposedExecpolicyAmendment?: ReadonlyArray | null; readonly proposedNetworkPolicyAmendments?: ReadonlyArray | null; @@ -34537,7 +48823,7 @@ export const CommandExecutionRequestApprovalParams = Schema.Struct({ Schema.Union([ Schema.String.annotate({ description: - "Unique identifier for this specific approval callback.\n\nFor regular shell/unified_exec approvals, this is null.\n\nFor zsh-exec-bridge subcommand approvals, multiple callbacks can belong to one parent `itemId`, so `approvalId` is a distinct opaque callback id (a UUID) used to disambiguate routing.", + "Unique identifier for this specific approval callback.\n\nFor regular shell/unified_exec approvals, this is null.\n\nFor zsh-exec-bridge subcommand approvals, multiple callbacks can belong to one parent `itemId`, so `approvalId` is a distinct opaque callback id (a UUID) used to disambiguate routing. Stdin approvals also use a distinct callback id; inspect `kind` to distinguish them.", }), Schema.Null, ]), @@ -34569,6 +48855,15 @@ export const CommandExecutionRequestApprovalParams = Schema.Struct({ ]), ), itemId: Schema.String, + kind: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + CommandExecutionRequestApprovalParams__CommandExecutionApprovalKind, + ).annotate({ + description: "Kind of action under review. Defaults to `command` for older servers.", + default: "command", + }), + ), networkApprovalContext: Schema.optionalKey( Schema.Union([ CommandExecutionRequestApprovalParams__NetworkApprovalContext, @@ -34604,7 +48899,7 @@ export const CommandExecutionRequestApprovalParams = Schema.Struct({ startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this approval request started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "CommandExecutionRequestApprovalParams" }); @@ -34685,7 +48980,7 @@ export const CommandExecutionRequestApprovalResponse = Schema.Struct({ }).annotate({ title: "CommandExecutionRequestApprovalResponse" }); export type DynamicToolCallParams = { - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly callId: string; readonly namespace?: string | null; readonly threadId: string; @@ -34693,7 +48988,7 @@ export type DynamicToolCallParams = { readonly turnId: string; }; export const DynamicToolCallParams = Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), callId: Schema.String, namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), threadId: Schema.String, @@ -34774,7 +49069,7 @@ export const FileChangeRequestApprovalParams = Schema.Struct({ startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this approval request started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "FileChangeRequestApprovalParams" }); @@ -34830,12 +49125,12 @@ export const GetAuthStatusParams = Schema.Struct({ }).annotate({ title: "GetAuthStatusParams" }); export type GetAuthStatusResponse = { - readonly authMethod: unknown | null; + readonly authMethod: Schema.Json | null; readonly authToken: string | null; readonly requiresOpenaiAuth: boolean | null; }; export const GetAuthStatusResponse = Schema.Struct({ - authMethod: Schema.Union([Schema.Unknown, Schema.Null]), + authMethod: Schema.Union([Schema.Json.annotate({ expected: "JSON value" }), Schema.Null]), authToken: Schema.Union([Schema.String, Schema.Null]), requiresOpenaiAuth: Schema.Union([Schema.Boolean, Schema.Null]), }).annotate({ title: "GetAuthStatusResponse" }); @@ -34848,10 +49143,10 @@ export const GetConversationSummaryParams = Schema.Union( { mode: "oneOf" }, ).annotate({ title: "GetConversationSummaryParams" }); -export type GetConversationSummaryResponse = { readonly summary: unknown }; -export const GetConversationSummaryResponse = Schema.Struct({ summary: Schema.Unknown }).annotate({ - title: "GetConversationSummaryResponse", -}); +export type GetConversationSummaryResponse = { readonly summary: Schema.Json }; +export const GetConversationSummaryResponse = Schema.Struct({ + summary: Schema.Json.annotate({ expected: "JSON value" }), +}).annotate({ title: "GetConversationSummaryResponse" }); export type GitDiffToRemoteParams = { readonly cwd: string }; export const GitDiffToRemoteParams = Schema.Struct({ cwd: Schema.String }).annotate({ @@ -34878,12 +49173,14 @@ export const JSONRPCError = Schema.Struct({ export type JSONRPCErrorError = { readonly code: number; - readonly data?: unknown; + readonly data?: Schema.Json; readonly message: string; }; export const JSONRPCErrorError = Schema.Struct({ - code: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), - data: Schema.optionalKey(Schema.Unknown), + code: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + data: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), message: Schema.String, }).annotate({ title: "JSONRPCErrorError" }); @@ -34903,10 +49200,10 @@ export const JSONRPCMessage = Schema.Union([ "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent.", }); -export type JSONRPCNotification = { readonly method: string; readonly params?: unknown }; +export type JSONRPCNotification = { readonly method: string; readonly params?: Schema.Json }; export const JSONRPCNotification = Schema.Struct({ method: Schema.String, - params: Schema.optionalKey(Schema.Unknown), + params: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), }).annotate({ title: "JSONRPCNotification", description: "A notification which does not expect a response.", @@ -34915,13 +49212,13 @@ export const JSONRPCNotification = Schema.Struct({ export type JSONRPCRequest = { readonly id: JSONRPCRequest__RequestId; readonly method: string; - readonly params?: unknown; + readonly params?: Schema.Json; readonly trace?: JSONRPCRequest__W3cTraceContext | null; }; export const JSONRPCRequest = Schema.Struct({ id: JSONRPCRequest__RequestId, method: Schema.String, - params: Schema.optionalKey(Schema.Unknown), + params: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), trace: Schema.optionalKey( Schema.Union([JSONRPCRequest__W3cTraceContext, Schema.Null]).annotate({ description: "Optional W3C Trace Context for distributed tracing.", @@ -34929,10 +49226,13 @@ export const JSONRPCRequest = Schema.Struct({ ), }).annotate({ title: "JSONRPCRequest", description: "A request that expects a response." }); -export type JSONRPCResponse = { readonly id: JSONRPCResponse__RequestId; readonly result: unknown }; +export type JSONRPCResponse = { + readonly id: JSONRPCResponse__RequestId; + readonly result: Schema.Json; +}; export const JSONRPCResponse = Schema.Struct({ id: JSONRPCResponse__RequestId, - result: Schema.Unknown, + result: Schema.Json.annotate({ expected: "JSON value" }), }).annotate({ title: "JSONRPCResponse", description: "A successful (non-error) response to a request.", @@ -34940,40 +49240,62 @@ export const JSONRPCResponse = Schema.Struct({ export type McpServerElicitationRequestParams = | { - readonly _meta?: unknown; + readonly serverName: string; + readonly threadId: string; + readonly turnId?: string | null; + readonly _meta?: Schema.Json; readonly message: string; readonly mode: "form"; readonly requestedSchema: McpServerElicitationRequestParams__McpElicitationSchema; + } + | { readonly serverName: string; readonly threadId: string; readonly turnId?: string | null; - } - | { - readonly _meta?: unknown; + readonly _meta?: Schema.Json; readonly message: string; readonly mode: "openai/form"; - readonly requestedSchema: unknown; + readonly requestedSchema: Schema.Json; + } + | { readonly serverName: string; readonly threadId: string; readonly turnId?: string | null; + readonly _meta?: Schema.Json; + readonly message: string; + readonly mode: "openaiForm"; + readonly requestedSchema: Schema.Json; } | { - readonly _meta?: unknown; + readonly serverName: string; + readonly threadId: string; + readonly turnId?: string | null; + readonly _meta?: Schema.Json; readonly elicitationId: string; readonly message: string; readonly mode: "url"; readonly url: string; - readonly serverName: string; - readonly threadId: string; - readonly turnId?: string | null; }; export const McpServerElicitationRequestParams = Schema.Union( [ Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), + serverName: Schema.String, + threadId: Schema.String, + turnId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Active Codex turn when this elicitation was observed, if app-server could correlate one.\n\nThis is nullable because MCP models elicitation as a standalone server-to-client request identified by the MCP server request id. It may be triggered during a turn, but turn context is app-server correlation rather than part of the protocol identity of the elicitation itself.", + }), + Schema.Null, + ]), + ), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), message: Schema.String, mode: Schema.Literal("form"), requestedSchema: McpServerElicitationRequestParams__McpElicitationSchema, + }), + Schema.Struct({ serverName: Schema.String, threadId: Schema.String, turnId: Schema.optionalKey( @@ -34985,12 +49307,12 @@ export const McpServerElicitationRequestParams = Schema.Union( Schema.Null, ]), ), - }).annotate({ title: "McpServerElicitationRequestParams" }), - Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), message: Schema.String, mode: Schema.Literal("openai/form"), - requestedSchema: Schema.Unknown, + requestedSchema: Schema.Json.annotate({ expected: "JSON value" }), + }), + Schema.Struct({ serverName: Schema.String, threadId: Schema.String, turnId: Schema.optionalKey( @@ -35002,13 +49324,12 @@ export const McpServerElicitationRequestParams = Schema.Union( Schema.Null, ]), ), - }).annotate({ title: "McpServerElicitationRequestParams" }), - Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - elicitationId: Schema.String, + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), message: Schema.String, - mode: Schema.Literal("url"), - url: Schema.String, + mode: Schema.Literal("openaiForm"), + requestedSchema: Schema.Json.annotate({ expected: "JSON value" }), + }), + Schema.Struct({ serverName: Schema.String, threadId: Schema.String, turnId: Schema.optionalKey( @@ -35020,25 +49341,32 @@ export const McpServerElicitationRequestParams = Schema.Union( Schema.Null, ]), ), - }).annotate({ title: "McpServerElicitationRequestParams" }), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + elicitationId: Schema.String, + message: Schema.String, + mode: Schema.Literal("url"), + url: Schema.String, + }), ], { mode: "oneOf" }, -); +).annotate({ title: "McpServerElicitationRequestParams" }); export type McpServerElicitationRequestResponse = { - readonly _meta?: unknown; + readonly _meta?: Schema.Json; readonly action: McpServerElicitationRequestResponse__McpServerElicitationAction; - readonly content?: unknown; + readonly content?: Schema.Json; }; export const McpServerElicitationRequestResponse = Schema.Struct({ _meta: Schema.optionalKey( - Schema.Unknown.annotate({ + Schema.Json.annotate({ + expected: "JSON value", description: "Optional client metadata for form-mode action handling.", }), ), action: McpServerElicitationRequestResponse__McpServerElicitationAction, content: Schema.optionalKey( - Schema.Unknown.annotate({ + Schema.Json.annotate({ + expected: "JSON value", description: "Structured user input for accepted elicitations, mirroring RMCP `CreateElicitationResult`.\n\nThis is nullable because decline/cancel responses have no content.", }), @@ -35046,7 +49374,7 @@ export const McpServerElicitationRequestResponse = Schema.Struct({ }).annotate({ title: "McpServerElicitationRequestResponse" }); export type PermissionsRequestApprovalParams = { - readonly cwd: PermissionsRequestApprovalParams__AbsolutePathBuf; + readonly cwd: PermissionsRequestApprovalParams__LegacyAppPathString; readonly environmentId?: string | null; readonly itemId: string; readonly permissions: PermissionsRequestApprovalParams__RequestPermissionProfile; @@ -35056,7 +49384,7 @@ export type PermissionsRequestApprovalParams = { readonly turnId: string; }; export const PermissionsRequestApprovalParams = Schema.Struct({ - cwd: PermissionsRequestApprovalParams__AbsolutePathBuf, + cwd: PermissionsRequestApprovalParams__LegacyAppPathString, environmentId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), itemId: Schema.String, permissions: PermissionsRequestApprovalParams__RequestPermissionProfile, @@ -35064,19 +49392,24 @@ export const PermissionsRequestApprovalParams = Schema.Struct({ startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this approval request started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "PermissionsRequestApprovalParams" }); export type PermissionsRequestApprovalResponse = { readonly permissions: PermissionsRequestApprovalResponse__GrantedPermissionProfile; - readonly scope?: "turn" | "session"; + readonly scope?: PermissionsRequestApprovalResponse__PermissionGrantScope; readonly strictAutoReview?: boolean | null; }; export const PermissionsRequestApprovalResponse = Schema.Struct({ permissions: PermissionsRequestApprovalResponse__GrantedPermissionProfile, - scope: Schema.optionalKey(Schema.Literals(["turn", "session"]).annotate({ default: "turn" })), + scope: Schema.optionalKey( + Schema.suspend( + (): Schema.Codec => + PermissionsRequestApprovalResponse__PermissionGrantScope, + ).annotate({ default: "turn" }), + ), strictAutoReview: Schema.optionalKey( Schema.Union([ Schema.Boolean.annotate({ @@ -35088,1571 +49421,1512 @@ export const PermissionsRequestApprovalResponse = Schema.Struct({ ), }).annotate({ title: "PermissionsRequestApprovalResponse" }); -export type PermissionsRequestApprovalResponse__PermissionGrantScope = "turn" | "session"; -export const PermissionsRequestApprovalResponse__PermissionGrantScope = Schema.Literals([ - "turn", - "session", -]); - export type RequestId = string | number; export const RequestId = Schema.Union([ Schema.String, - Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), ]).annotate({ title: "RequestId" }); export type ServerNotification = | { + readonly emittedAtMs?: number; readonly method: "error"; readonly params: ServerNotification__ErrorNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/started"; readonly params: ServerNotification__ThreadStartedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/status/changed"; readonly params: ServerNotification__ThreadStatusChangedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/archived"; readonly params: ServerNotification__ThreadArchivedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/deleted"; readonly params: ServerNotification__ThreadDeletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/unarchived"; readonly params: ServerNotification__ThreadUnarchivedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/closed"; readonly params: ServerNotification__ThreadClosedNotification; + } + | { readonly emittedAtMs?: number; + readonly method: "thread/reverted"; + readonly params: ServerNotification__ThreadRevertedNotification; } | { + readonly emittedAtMs?: number; readonly method: "skills/changed"; readonly params: ServerNotification__SkillsChangedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/name/updated"; readonly params: ServerNotification__ThreadNameUpdatedNotification; + } + | { readonly emittedAtMs?: number; + readonly method: "thread/attachment/updated"; + readonly params: ServerNotification__ThreadAttachmentUpdatedNotification; } | { + readonly emittedAtMs?: number; readonly method: "thread/goal/updated"; readonly params: ServerNotification__ThreadGoalUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/goal/cleared"; readonly params: ServerNotification__ThreadGoalClearedNotification; + } + | { readonly emittedAtMs?: number; + readonly method: "thread/queue/changed"; + readonly params: ServerNotification__ThreadQueueChangedNotification; } | { + readonly emittedAtMs?: number; + readonly method: "project/changed"; + readonly params: ServerNotification__ProjectChangedNotification; + } + | { + readonly emittedAtMs?: number; + readonly method: "thread/project/updated"; + readonly params: ServerNotification__ThreadProjectUpdatedNotification; + } + | { + readonly emittedAtMs?: number; readonly method: "thread/environment/connected"; readonly params: ServerNotification__EnvironmentConnectionNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/environment/disconnected"; readonly params: ServerNotification__EnvironmentConnectionNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/settings/updated"; readonly params: ServerNotification__ThreadSettingsUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/tokenUsage/updated"; readonly params: ServerNotification__ThreadTokenUsageUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "turn/started"; readonly params: ServerNotification__TurnStartedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "hook/started"; readonly params: ServerNotification__HookStartedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "turn/completed"; readonly params: ServerNotification__TurnCompletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "hook/completed"; readonly params: ServerNotification__HookCompletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "turn/diff/updated"; readonly params: ServerNotification__TurnDiffUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "turn/plan/updated"; readonly params: ServerNotification__TurnPlanUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/started"; readonly params: ServerNotification__ItemStartedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/autoApprovalReview/started"; readonly params: ServerNotification__ItemGuardianApprovalReviewStartedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/autoApprovalReview/completed"; readonly params: ServerNotification__ItemGuardianApprovalReviewCompletedNotification; + } + | { readonly emittedAtMs?: number; + readonly method: "autoApprovalReview/strictReviewRequired"; + readonly params: ServerNotification__StrictReviewRequiredNotification; } | { + readonly emittedAtMs?: number; readonly method: "item/completed"; readonly params: ServerNotification__ItemCompletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/agentMessage/delta"; readonly params: ServerNotification__AgentMessageDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/plan/delta"; readonly params: ServerNotification__PlanDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "command/exec/outputDelta"; readonly params: ServerNotification__CommandExecOutputDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "process/outputDelta"; readonly params: ServerNotification__ProcessOutputDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "process/exited"; readonly params: ServerNotification__ProcessExitedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/commandExecution/outputDelta"; readonly params: ServerNotification__CommandExecutionOutputDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/commandExecution/terminalInteraction"; readonly params: ServerNotification__TerminalInteractionNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/fileChange/outputDelta"; readonly params: ServerNotification__FileChangeOutputDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/fileChange/patchUpdated"; readonly params: ServerNotification__FileChangePatchUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "serverRequest/resolved"; readonly params: ServerNotification__ServerRequestResolvedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/mcpToolCall/progress"; readonly params: ServerNotification__McpToolCallProgressNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "mcpServer/oauthLogin/completed"; readonly params: ServerNotification__McpServerOauthLoginCompletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "mcpServer/startupStatus/updated"; readonly params: ServerNotification__McpServerStatusUpdatedNotification; + } + | { readonly emittedAtMs?: number; + readonly method: "mcpServer/event/stream/notification"; + readonly params: ServerNotification__McpServerEventStreamNotification; } | { + readonly emittedAtMs?: number; readonly method: "account/updated"; readonly params: ServerNotification__AccountUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "account/rateLimits/updated"; readonly params: ServerNotification__AccountRateLimitsUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "app/list/updated"; readonly params: ServerNotification__AppListUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "remoteControl/status/changed"; readonly params: ServerNotification__RemoteControlStatusChangedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "externalAgentConfig/import/progress"; readonly params: ServerNotification__ExternalAgentConfigImportProgressNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "externalAgentConfig/import/completed"; readonly params: ServerNotification__ExternalAgentConfigImportCompletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "fs/changed"; readonly params: ServerNotification__FsChangedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/reasoning/summaryTextDelta"; readonly params: ServerNotification__ReasoningSummaryTextDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/reasoning/summaryPartAdded"; readonly params: ServerNotification__ReasoningSummaryPartAddedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "item/reasoning/textDelta"; readonly params: ServerNotification__ReasoningTextDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/compacted"; readonly params: ServerNotification__ContextCompactedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "model/rerouted"; readonly params: ServerNotification__ModelReroutedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "model/verification"; readonly params: ServerNotification__ModelVerificationNotification; + } + | { + readonly emittedAtMs?: number; + readonly method: "modelProvider/authRecoveryStarted"; + readonly params: ServerNotification__AuthRecoveryNotification; + } + | { readonly emittedAtMs?: number; + readonly method: "modelProvider/authRecoveryCompleted"; + readonly params: ServerNotification__AuthRecoveryNotification; } | { + readonly emittedAtMs?: number; readonly method: "turn/moderationMetadata"; readonly params: ServerNotification__TurnModerationMetadataNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "model/safetyBuffering/updated"; readonly params: ServerNotification__ModelSafetyBufferingUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "warning"; readonly params: ServerNotification__WarningNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "guardianWarning"; readonly params: ServerNotification__GuardianWarningNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "deprecationNotice"; readonly params: ServerNotification__DeprecationNoticeNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "configWarning"; readonly params: ServerNotification__ConfigWarningNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "fuzzyFileSearch/sessionUpdated"; readonly params: ServerNotification__FuzzyFileSearchSessionUpdatedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "fuzzyFileSearch/sessionCompleted"; readonly params: ServerNotification__FuzzyFileSearchSessionCompletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/started"; readonly params: ServerNotification__ThreadRealtimeStartedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/itemAdded"; readonly params: ServerNotification__ThreadRealtimeItemAddedNotification; + } + | { + readonly emittedAtMs?: number; + readonly method: "thread/realtime/item/started"; + readonly params: ServerNotification__ThreadRealtimeItemStartedNotification; + } + | { + readonly emittedAtMs?: number; + readonly method: "thread/realtime/item/transcript/delta"; + readonly params: ServerNotification__ThreadRealtimeItemTranscriptDeltaNotification; + } + | { readonly emittedAtMs?: number; + readonly method: "thread/realtime/item/completed"; + readonly params: ServerNotification__ThreadRealtimeItemCompletedNotification; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/transcript/delta"; readonly params: ServerNotification__ThreadRealtimeTranscriptDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/transcript/done"; readonly params: ServerNotification__ThreadRealtimeTranscriptDoneNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/outputAudio/delta"; readonly params: ServerNotification__ThreadRealtimeOutputAudioDeltaNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/sdp"; readonly params: ServerNotification__ThreadRealtimeSdpNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/error"; readonly params: ServerNotification__ThreadRealtimeErrorNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "thread/realtime/closed"; readonly params: ServerNotification__ThreadRealtimeClosedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "windows/worldWritableWarning"; readonly params: ServerNotification__WindowsWorldWritableWarningNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "windowsSandbox/setupCompleted"; readonly params: ServerNotification__WindowsSandboxSetupCompletedNotification; - readonly emittedAtMs?: number; } | { + readonly emittedAtMs?: number; readonly method: "account/login/completed"; readonly params: ServerNotification__AccountLoginCompletedNotification; - readonly emittedAtMs?: number; }; export const ServerNotification = Schema.Union( [ Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("error").annotate({ title: "ErrorNotificationMethod" }), params: ServerNotification__ErrorNotification, + }).annotate({ title: "ErrorNotification", description: "NEW NOTIFICATIONS" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/started").annotate({ title: "Thread/startedNotificationMethod", }), params: ServerNotification__ThreadStartedNotification, + }).annotate({ title: "Thread/startedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/status/changed").annotate({ title: "Thread/status/changedNotificationMethod", }), params: ServerNotification__ThreadStatusChangedNotification, + }).annotate({ title: "Thread/status/changedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/archived").annotate({ title: "Thread/archivedNotificationMethod", }), params: ServerNotification__ThreadArchivedNotification, + }).annotate({ title: "Thread/archivedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/deleted").annotate({ title: "Thread/deletedNotificationMethod", }), params: ServerNotification__ThreadDeletedNotification, + }).annotate({ title: "Thread/deletedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/unarchived").annotate({ title: "Thread/unarchivedNotificationMethod", }), params: ServerNotification__ThreadUnarchivedNotification, + }).annotate({ title: "Thread/unarchivedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/closed").annotate({ title: "Thread/closedNotificationMethod", }), params: ServerNotification__ThreadClosedNotification, + }).annotate({ title: "Thread/closedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + method: Schema.Literal("thread/reverted").annotate({ + title: "Thread/revertedNotificationMethod", + }), + params: ServerNotification__ThreadRevertedNotification, + }).annotate({ title: "Thread/revertedNotification" }), Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("skills/changed").annotate({ title: "Skills/changedNotificationMethod", }), params: ServerNotification__SkillsChangedNotification, + }).annotate({ title: "Skills/changedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/name/updated").annotate({ title: "Thread/name/updatedNotificationMethod", }), params: ServerNotification__ThreadNameUpdatedNotification, + }).annotate({ title: "Thread/name/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + method: Schema.Literal("thread/attachment/updated").annotate({ + title: "Thread/attachment/updatedNotificationMethod", + }), + params: ServerNotification__ThreadAttachmentUpdatedNotification, + }).annotate({ title: "Thread/attachment/updatedNotification" }), Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("thread/goal/updated").annotate({ title: "Thread/goal/updatedNotificationMethod", }), params: ServerNotification__ThreadGoalUpdatedNotification, + }).annotate({ title: "Thread/goal/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/goal/cleared").annotate({ title: "Thread/goal/clearedNotificationMethod", }), params: ServerNotification__ThreadGoalClearedNotification, + }).annotate({ title: "Thread/goal/clearedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + method: Schema.Literal("thread/queue/changed").annotate({ + title: "Thread/queue/changedNotificationMethod", + }), + params: ServerNotification__ThreadQueueChangedNotification, + }).annotate({ title: "Thread/queue/changedNotification" }), Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), + method: Schema.Literal("project/changed").annotate({ + title: "Project/changedNotificationMethod", + }), + params: ServerNotification__ProjectChangedNotification, + }).annotate({ title: "Project/changedNotification" }), + Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), + method: Schema.Literal("thread/project/updated").annotate({ + title: "Thread/project/updatedNotificationMethod", + }), + params: ServerNotification__ThreadProjectUpdatedNotification, + }).annotate({ title: "Thread/project/updatedNotification" }), + Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("thread/environment/connected").annotate({ title: "Thread/environment/connectedNotificationMethod", }), params: ServerNotification__EnvironmentConnectionNotification, + }).annotate({ title: "Thread/environment/connectedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/environment/disconnected").annotate({ title: "Thread/environment/disconnectedNotificationMethod", }), params: ServerNotification__EnvironmentConnectionNotification, + }).annotate({ title: "Thread/environment/disconnectedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/settings/updated").annotate({ title: "Thread/settings/updatedNotificationMethod", }), params: ServerNotification__ThreadSettingsUpdatedNotification, + }).annotate({ title: "Thread/settings/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/tokenUsage/updated").annotate({ title: "Thread/tokenUsage/updatedNotificationMethod", }), params: ServerNotification__ThreadTokenUsageUpdatedNotification, + }).annotate({ title: "Thread/tokenUsage/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("turn/started").annotate({ title: "Turn/startedNotificationMethod" }), params: ServerNotification__TurnStartedNotification, + }).annotate({ title: "Turn/startedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("hook/started").annotate({ title: "Hook/startedNotificationMethod" }), params: ServerNotification__HookStartedNotification, + }).annotate({ title: "Hook/startedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("turn/completed").annotate({ title: "Turn/completedNotificationMethod", }), params: ServerNotification__TurnCompletedNotification, + }).annotate({ title: "Turn/completedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("hook/completed").annotate({ title: "Hook/completedNotificationMethod", }), params: ServerNotification__HookCompletedNotification, + }).annotate({ title: "Hook/completedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("turn/diff/updated").annotate({ title: "Turn/diff/updatedNotificationMethod", }), params: ServerNotification__TurnDiffUpdatedNotification, + }).annotate({ title: "Turn/diff/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("turn/plan/updated").annotate({ title: "Turn/plan/updatedNotificationMethod", }), params: ServerNotification__TurnPlanUpdatedNotification, + }).annotate({ title: "Turn/plan/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/started").annotate({ title: "Item/startedNotificationMethod" }), params: ServerNotification__ItemStartedNotification, + }).annotate({ title: "Item/startedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/autoApprovalReview/started").annotate({ title: "Item/autoApprovalReview/startedNotificationMethod", }), params: ServerNotification__ItemGuardianApprovalReviewStartedNotification, + }).annotate({ title: "Item/autoApprovalReview/startedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/autoApprovalReview/completed").annotate({ title: "Item/autoApprovalReview/completedNotificationMethod", }), params: ServerNotification__ItemGuardianApprovalReviewCompletedNotification, + }).annotate({ title: "Item/autoApprovalReview/completedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + method: Schema.Literal("autoApprovalReview/strictReviewRequired").annotate({ + title: "AutoApprovalReview/strictReviewRequiredNotificationMethod", + }), + params: ServerNotification__StrictReviewRequiredNotification, + }).annotate({ title: "AutoApprovalReview/strictReviewRequiredNotification" }), Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("item/completed").annotate({ title: "Item/completedNotificationMethod", }), params: ServerNotification__ItemCompletedNotification, + }).annotate({ title: "Item/completedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/agentMessage/delta").annotate({ title: "Item/agentMessage/deltaNotificationMethod", }), params: ServerNotification__AgentMessageDeltaNotification, + }).annotate({ title: "Item/agentMessage/deltaNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/plan/delta").annotate({ title: "Item/plan/deltaNotificationMethod", }), params: ServerNotification__PlanDeltaNotification, + }).annotate({ + title: "Item/plan/deltaNotification", + description: "EXPERIMENTAL - proposed plan streaming deltas for plan items.", + }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("command/exec/outputDelta").annotate({ title: "Command/exec/outputDeltaNotificationMethod", }), params: ServerNotification__CommandExecOutputDeltaNotification, + }).annotate({ + title: "Command/exec/outputDeltaNotification", + description: + "Stream base64-encoded stdout/stderr chunks for a running `command/exec` session.", + }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("process/outputDelta").annotate({ title: "Process/outputDeltaNotificationMethod", }), params: ServerNotification__ProcessOutputDeltaNotification, + }).annotate({ + title: "Process/outputDeltaNotification", + description: + "Stream base64-encoded stdout/stderr chunks for a running `process/spawn` session.", + }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("process/exited").annotate({ title: "Process/exitedNotificationMethod", }), params: ServerNotification__ProcessExitedNotification, + }).annotate({ + title: "Process/exitedNotification", + description: "Final exit notification for a `process/spawn` session.", + }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/commandExecution/outputDelta").annotate({ title: "Item/commandExecution/outputDeltaNotificationMethod", }), params: ServerNotification__CommandExecutionOutputDeltaNotification, + }).annotate({ title: "Item/commandExecution/outputDeltaNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/commandExecution/terminalInteraction").annotate({ title: "Item/commandExecution/terminalInteractionNotificationMethod", }), params: ServerNotification__TerminalInteractionNotification, + }).annotate({ title: "Item/commandExecution/terminalInteractionNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/fileChange/outputDelta").annotate({ title: "Item/fileChange/outputDeltaNotificationMethod", }), params: ServerNotification__FileChangeOutputDeltaNotification, + }).annotate({ + title: "Item/fileChange/outputDeltaNotification", + description: "Deprecated legacy apply_patch output stream notification.", + }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/fileChange/patchUpdated").annotate({ title: "Item/fileChange/patchUpdatedNotificationMethod", }), params: ServerNotification__FileChangePatchUpdatedNotification, + }).annotate({ title: "Item/fileChange/patchUpdatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("serverRequest/resolved").annotate({ title: "ServerRequest/resolvedNotificationMethod", }), params: ServerNotification__ServerRequestResolvedNotification, + }).annotate({ title: "ServerRequest/resolvedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/mcpToolCall/progress").annotate({ title: "Item/mcpToolCall/progressNotificationMethod", }), params: ServerNotification__McpToolCallProgressNotification, + }).annotate({ title: "Item/mcpToolCall/progressNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("mcpServer/oauthLogin/completed").annotate({ title: "McpServer/oauthLogin/completedNotificationMethod", }), params: ServerNotification__McpServerOauthLoginCompletedNotification, + }).annotate({ title: "McpServer/oauthLogin/completedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("mcpServer/startupStatus/updated").annotate({ title: "McpServer/startupStatus/updatedNotificationMethod", }), params: ServerNotification__McpServerStatusUpdatedNotification, + }).annotate({ title: "McpServer/startupStatus/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + method: Schema.Literal("mcpServer/event/stream/notification").annotate({ + title: "McpServer/event/stream/notificationNotificationMethod", + }), + params: ServerNotification__McpServerEventStreamNotification, + }).annotate({ title: "McpServer/event/stream/notificationNotification" }), Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("account/updated").annotate({ title: "Account/updatedNotificationMethod", }), params: ServerNotification__AccountUpdatedNotification, + }).annotate({ title: "Account/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("account/rateLimits/updated").annotate({ title: "Account/rateLimits/updatedNotificationMethod", }), params: ServerNotification__AccountRateLimitsUpdatedNotification, + }).annotate({ title: "Account/rateLimits/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("app/list/updated").annotate({ title: "App/list/updatedNotificationMethod", }), params: ServerNotification__AppListUpdatedNotification, + }).annotate({ title: "App/list/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("remoteControl/status/changed").annotate({ title: "RemoteControl/status/changedNotificationMethod", }), params: ServerNotification__RemoteControlStatusChangedNotification, + }).annotate({ title: "RemoteControl/status/changedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("externalAgentConfig/import/progress").annotate({ title: "ExternalAgentConfig/import/progressNotificationMethod", }), params: ServerNotification__ExternalAgentConfigImportProgressNotification, + }).annotate({ title: "ExternalAgentConfig/import/progressNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("externalAgentConfig/import/completed").annotate({ title: "ExternalAgentConfig/import/completedNotificationMethod", }), params: ServerNotification__ExternalAgentConfigImportCompletedNotification, + }).annotate({ title: "ExternalAgentConfig/import/completedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("fs/changed").annotate({ title: "Fs/changedNotificationMethod" }), params: ServerNotification__FsChangedNotification, + }).annotate({ title: "Fs/changedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/reasoning/summaryTextDelta").annotate({ title: "Item/reasoning/summaryTextDeltaNotificationMethod", }), params: ServerNotification__ReasoningSummaryTextDeltaNotification, + }).annotate({ title: "Item/reasoning/summaryTextDeltaNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/reasoning/summaryPartAdded").annotate({ title: "Item/reasoning/summaryPartAddedNotificationMethod", }), params: ServerNotification__ReasoningSummaryPartAddedNotification, + }).annotate({ title: "Item/reasoning/summaryPartAddedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("item/reasoning/textDelta").annotate({ title: "Item/reasoning/textDeltaNotificationMethod", }), params: ServerNotification__ReasoningTextDeltaNotification, + }).annotate({ title: "Item/reasoning/textDeltaNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/compacted").annotate({ title: "Thread/compactedNotificationMethod", }), params: ServerNotification__ContextCompactedNotification, + }).annotate({ + title: "Thread/compactedNotification", + description: "Deprecated: Use `ContextCompaction` item type instead.", + }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("model/rerouted").annotate({ title: "Model/reroutedNotificationMethod", }), params: ServerNotification__ModelReroutedNotification, + }).annotate({ title: "Model/reroutedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("model/verification").annotate({ title: "Model/verificationNotificationMethod", }), params: ServerNotification__ModelVerificationNotification, + }).annotate({ title: "Model/verificationNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + method: Schema.Literal("modelProvider/authRecoveryStarted").annotate({ + title: "ModelProvider/authRecoveryStartedNotificationMethod", + }), + params: ServerNotification__AuthRecoveryNotification, + }).annotate({ title: "ModelProvider/authRecoveryStartedNotification" }), + Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), + method: Schema.Literal("modelProvider/authRecoveryCompleted").annotate({ + title: "ModelProvider/authRecoveryCompletedNotificationMethod", + }), + params: ServerNotification__AuthRecoveryNotification, + }).annotate({ title: "ModelProvider/authRecoveryCompletedNotification" }), Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("turn/moderationMetadata").annotate({ title: "Turn/moderationMetadataNotificationMethod", }), params: ServerNotification__TurnModerationMetadataNotification, + }).annotate({ title: "Turn/moderationMetadataNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("model/safetyBuffering/updated").annotate({ title: "Model/safetyBuffering/updatedNotificationMethod", }), params: ServerNotification__ModelSafetyBufferingUpdatedNotification, + }).annotate({ title: "Model/safetyBuffering/updatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("warning").annotate({ title: "WarningNotificationMethod" }), params: ServerNotification__WarningNotification, + }).annotate({ title: "WarningNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("guardianWarning").annotate({ title: "GuardianWarningNotificationMethod", }), params: ServerNotification__GuardianWarningNotification, + }).annotate({ title: "GuardianWarningNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("deprecationNotice").annotate({ title: "DeprecationNoticeNotificationMethod", }), params: ServerNotification__DeprecationNoticeNotification, + }).annotate({ title: "DeprecationNoticeNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("configWarning").annotate({ title: "ConfigWarningNotificationMethod", }), params: ServerNotification__ConfigWarningNotification, + }).annotate({ title: "ConfigWarningNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("fuzzyFileSearch/sessionUpdated").annotate({ title: "FuzzyFileSearch/sessionUpdatedNotificationMethod", }), params: ServerNotification__FuzzyFileSearchSessionUpdatedNotification, + }).annotate({ title: "FuzzyFileSearch/sessionUpdatedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("fuzzyFileSearch/sessionCompleted").annotate({ title: "FuzzyFileSearch/sessionCompletedNotificationMethod", }), params: ServerNotification__FuzzyFileSearchSessionCompletedNotification, + }).annotate({ title: "FuzzyFileSearch/sessionCompletedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/realtime/started").annotate({ title: "Thread/realtime/startedNotificationMethod", }), params: ServerNotification__ThreadRealtimeStartedNotification, + }).annotate({ title: "Thread/realtime/startedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/realtime/itemAdded").annotate({ title: "Thread/realtime/itemAddedNotificationMethod", }), params: ServerNotification__ThreadRealtimeItemAddedNotification, + }).annotate({ title: "Thread/realtime/itemAddedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + method: Schema.Literal("thread/realtime/item/started").annotate({ + title: "Thread/realtime/item/startedNotificationMethod", + }), + params: ServerNotification__ThreadRealtimeItemStartedNotification, + }).annotate({ title: "Thread/realtime/item/startedNotification" }), + Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), + method: Schema.Literal("thread/realtime/item/transcript/delta").annotate({ + title: "Thread/realtime/item/transcript/deltaNotificationMethod", + }), + params: ServerNotification__ThreadRealtimeItemTranscriptDeltaNotification, + }).annotate({ title: "Thread/realtime/item/transcript/deltaNotification" }), Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), + method: Schema.Literal("thread/realtime/item/completed").annotate({ + title: "Thread/realtime/item/completedNotificationMethod", + }), + params: ServerNotification__ThreadRealtimeItemCompletedNotification, + }).annotate({ title: "Thread/realtime/item/completedNotification" }), + Schema.Struct({ + emittedAtMs: Schema.optionalKey( + Schema.Number.annotate({ + description: + "Unix timestamp (in milliseconds) when app-server emitted this notification.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + ), method: Schema.Literal("thread/realtime/transcript/delta").annotate({ title: "Thread/realtime/transcript/deltaNotificationMethod", }), params: ServerNotification__ThreadRealtimeTranscriptDeltaNotification, + }).annotate({ title: "Thread/realtime/transcript/deltaNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/realtime/transcript/done").annotate({ title: "Thread/realtime/transcript/doneNotificationMethod", }), params: ServerNotification__ThreadRealtimeTranscriptDoneNotification, + }).annotate({ title: "Thread/realtime/transcript/doneNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/realtime/outputAudio/delta").annotate({ title: "Thread/realtime/outputAudio/deltaNotificationMethod", }), params: ServerNotification__ThreadRealtimeOutputAudioDeltaNotification, + }).annotate({ title: "Thread/realtime/outputAudio/deltaNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/realtime/sdp").annotate({ title: "Thread/realtime/sdpNotificationMethod", }), params: ServerNotification__ThreadRealtimeSdpNotification, + }).annotate({ title: "Thread/realtime/sdpNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/realtime/error").annotate({ title: "Thread/realtime/errorNotificationMethod", }), params: ServerNotification__ThreadRealtimeErrorNotification, + }).annotate({ title: "Thread/realtime/errorNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("thread/realtime/closed").annotate({ title: "Thread/realtime/closedNotificationMethod", }), params: ServerNotification__ThreadRealtimeClosedNotification, + }).annotate({ title: "Thread/realtime/closedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("windows/worldWritableWarning").annotate({ title: "Windows/worldWritableWarningNotificationMethod", }), params: ServerNotification__WindowsWorldWritableWarningNotification, + }).annotate({ + title: "Windows/worldWritableWarningNotification", + description: + "Notifies the user of world-writable directories on Windows, which cannot be protected by the sandbox.", + }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("windowsSandbox/setupCompleted").annotate({ title: "WindowsSandbox/setupCompletedNotificationMethod", }), params: ServerNotification__WindowsSandboxSetupCompletedNotification, + }).annotate({ title: "WindowsSandbox/setupCompletedNotification" }), + Schema.Struct({ emittedAtMs: Schema.optionalKey( Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when app-server emitted this notification.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), - Schema.Struct({ method: Schema.Literal("account/login/completed").annotate({ title: "Account/login/completedNotificationMethod", }), params: ServerNotification__AccountLoginCompletedNotification, - emittedAtMs: Schema.optionalKey( - Schema.Number.annotate({ - description: - "Unix timestamp (in milliseconds) when app-server emitted this notification.", - format: "int64", - }).check(Schema.isInt()), - ), - }).annotate({ - title: "ServerNotification", - description: "Notification sent from the server to the client.", - }), + }).annotate({ title: "Account/login/completedNotification" }), ], { mode: "oneOf" }, -); - -export type ServerNotification__ByteRange = { readonly end: number; readonly start: number }; -export const ServerNotification__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({ + title: "ServerNotification", + description: "Notification sent from the server to the client.", }); -export type ServerNotification__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const ServerNotification__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type ServerNotification__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const ServerNotification__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type ServerNotification__CommandExecOutputStream = "stdout" | "stderr"; -export const ServerNotification__CommandExecOutputStream = Schema.Literals([ - "stdout", - "stderr", -]).annotate({ description: "Stream label for `command/exec/outputDelta` notifications." }); - -export type ServerNotification__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const ServerNotification__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type ServerNotification__HookSource = - | "system" - | "user" - | "project" - | "mdm" - | "sessionFlags" - | "plugin" - | "cloudRequirements" - | "cloudManagedConfig" - | "legacyManagedConfigFile" - | "legacyManagedConfigMdm" - | "unknown"; -export const ServerNotification__HookSource = Schema.Literals([ - "system", - "user", - "project", - "mdm", - "sessionFlags", - "plugin", - "cloudRequirements", - "cloudManagedConfig", - "legacyManagedConfigFile", - "legacyManagedConfigMdm", - "unknown", -]); - export type ServerNotification__MultiAgentMode = | "explicitRequestOnly" | "proactive" @@ -36668,44 +50942,24 @@ export const ServerNotification__MultiAgentMode = Schema.Union( "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", }); -export type ServerNotification__NetworkAccess = "restricted" | "enabled"; -export const ServerNotification__NetworkAccess = Schema.Literals(["restricted", "enabled"]); - -export type ServerNotification__ProcessOutputStream = "stdout" | "stderr"; -export const ServerNotification__ProcessOutputStream = Schema.Literals([ - "stdout", - "stderr", -]).annotate({ description: "Stream label for `process/outputDelta` notifications." }); - -export type ServerNotification__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: ServerNotification__SubAgentSource }; -export const ServerNotification__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: ServerNotification__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type ServerNotification__ThreadExtra = {}; -export const ServerNotification__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export type ServerNotification__ThreadEnvironment = { + readonly cwd: ServerNotification__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const ServerNotification__ThreadEnvironment = Schema.Struct({ + cwd: ServerNotification__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(ServerNotification__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", }); -export type ServerNotification__ThreadHistoryMode = "legacy" | "paginated"; -export const ServerNotification__ThreadHistoryMode = Schema.Literals(["legacy", "paginated"]); - -export type ServerNotification__TurnItemsView = "notLoaded" | "summary" | "full"; -export const ServerNotification__TurnItemsView = Schema.Literals(["notLoaded", "summary", "full"]); +export type ServerNotification__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const ServerNotification__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); export type ServerRequest = | { @@ -36932,6 +51186,7 @@ export const ServerRequest__CommandExecutionApprovalDecision = Schema.Union( export type ToolRequestUserInputParams = { readonly autoResolutionMs?: number | null; + readonly isBlocking: boolean; readonly itemId: string; readonly questions: ReadonlyArray; readonly threadId: string; @@ -36940,12 +51195,20 @@ export type ToolRequestUserInputParams = { export const ToolRequestUserInputParams = Schema.Struct({ autoResolutionMs: Schema.optionalKey( Schema.Union([ - Schema.Number.annotate({ format: "uint64" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + Schema.Number.annotate({ + description: "@deprecated Use `isBlocking` to decide whether the request should block.", + format: "uint64", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), + isBlocking: Schema.Boolean, itemId: Schema.String, questions: Schema.Array(ToolRequestUserInputParams__ToolRequestUserInputQuestion), threadId: Schema.String, @@ -36979,16 +51242,16 @@ export const V1InitializeParams = Schema.Struct({ }).annotate({ title: "InitializeParams" }); export type V1InitializeResponse = { - readonly codexHome: string; + readonly codexHome: V1InitializeResponse__AbsolutePathBuf; readonly platformFamily: string; readonly platformOs: string; readonly userAgent: string; }; export const V1InitializeResponse = Schema.Struct({ - codexHome: 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.", - }), + codexHome: Schema.suspend( + (): Schema.Codec => + V1InitializeResponse__AbsolutePathBuf, + ).annotate({ description: "Absolute path to the server's $CODEX_HOME directory." }), platformFamily: Schema.String.annotate({ description: 'Platform family for the running app-server target, for example `"unix"` or `"windows"`.', @@ -37000,20 +51263,18 @@ export const V1InitializeResponse = Schema.Struct({ userAgent: Schema.String, }).annotate({ title: "InitializeResponse" }); -export type V1InitializeResponse__AbsolutePathBuf = string; -export const V1InitializeResponse__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 V2AccountLoginCompletedNotification = { readonly error?: string | null; readonly loginId?: string | null; + readonly onboardingEntrypoint?: V2AccountLoginCompletedNotification__DesktopOnboardingEntrypoint | null; readonly success: boolean; }; export const V2AccountLoginCompletedNotification = Schema.Struct({ error: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), loginId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + onboardingEntrypoint: Schema.optionalKey( + Schema.Union([V2AccountLoginCompletedNotification__DesktopOnboardingEntrypoint, Schema.Null]), + ), success: Schema.Boolean, }).annotate({ title: "AccountLoginCompletedNotification" }); @@ -37120,8 +51381,12 @@ export const V2AppsListParams = Schema.Struct({ description: "Optional page size; defaults to a reasonable server-side value.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -37159,6 +51424,7 @@ export const V2AppsListResponse = Schema.Struct({ export type V2AppsReadParams = { readonly appIds: ReadonlyArray; readonly includeTools?: boolean; + readonly threadId?: string | null; }; export const V2AppsReadParams = Schema.Struct({ appIds: Schema.Array(Schema.String).annotate({ @@ -37171,6 +51437,14 @@ export const V2AppsReadParams = Schema.Struct({ "When true, include display-only public tool summaries in the returned metadata.", }), ), + threadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional loaded thread id used to evaluate effective app configuration.", + }), + Schema.Null, + ]), + ), }).annotate({ title: "AppsReadParams", description: "EXPERIMENTAL - read metadata for specific apps/connectors.", @@ -37185,6 +51459,19 @@ export const V2AppsReadResponse = Schema.Struct({ missingAppIds: Schema.Array(Schema.String), }).annotate({ title: "AppsReadResponse", description: "EXPERIMENTAL - app/read response." }); +export type V2AuthRecoveryNotification = { + readonly message: string; + readonly provider: string; + readonly threadId: string; + readonly turnId: string; +}; +export const V2AuthRecoveryNotification = Schema.Struct({ + message: Schema.String, + provider: Schema.String, + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ title: "AuthRecoveryNotification" }); + export type V2CancelLoginAccountParams = { readonly loginId: string }; export const V2CancelLoginAccountParams = Schema.Struct({ loginId: Schema.String }).annotate({ title: "CancelLoginAccountParams", @@ -37201,7 +51488,7 @@ export type V2CommandExecOutputDeltaNotification = { readonly capReached: boolean; readonly deltaBase64: string; readonly processId: string; - readonly stream: "stdout" | "stderr"; + readonly stream: V2CommandExecOutputDeltaNotification__CommandExecOutputStream; }; export const V2CommandExecOutputDeltaNotification = Schema.Struct({ capReached: Schema.Boolean.annotate({ @@ -37213,21 +51500,16 @@ export const V2CommandExecOutputDeltaNotification = Schema.Struct({ description: "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", }), - stream: Schema.Literals(["stdout", "stderr"]).annotate({ - description: "Stream label for `command/exec/outputDelta` notifications.", - }), + stream: Schema.suspend( + (): Schema.Codec => + V2CommandExecOutputDeltaNotification__CommandExecOutputStream, + ).annotate({ description: "Output stream for this chunk." }), }).annotate({ title: "CommandExecOutputDeltaNotification", description: "Base64-encoded output chunk emitted for a streaming `command/exec` request.\n\nThese notifications are connection-scoped. If the originating connection closes, the server terminates the process.", }); -export type V2CommandExecOutputDeltaNotification__CommandExecOutputStream = "stdout" | "stderr"; -export const V2CommandExecOutputDeltaNotification__CommandExecOutputStream = Schema.Literals([ - "stdout", - "stderr", -]).annotate({ description: "Stream label for `command/exec/outputDelta` notifications." }); - export type V2CommandExecParams = { readonly command: ReadonlyArray; readonly cwd?: string | null; @@ -37283,8 +51565,12 @@ export const V2CommandExecParams = Schema.Struct({ "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)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -37326,7 +51612,7 @@ export const V2CommandExecParams = Schema.Struct({ description: "Optional timeout in milliseconds.\n\nWhen omitted, the server default applies. Cannot be combined with `disableTimeout`.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), Schema.Null, ]), ), @@ -37341,58 +51627,29 @@ export const V2CommandExecParams = 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 V2CommandExecParams__NetworkAccess = "restricted" | "enabled"; -export const V2CommandExecParams__NetworkAccess = Schema.Literals(["restricted", "enabled"]); - export type V2CommandExecResizeParams = { readonly processId: string; - readonly size: { readonly cols: number; readonly rows: number }; + readonly size: V2CommandExecResizeParams__CommandExecTerminalSize; }; export const V2CommandExecResizeParams = Schema.Struct({ processId: Schema.String.annotate({ description: "Client-supplied, connection-scoped `processId` from the original `command/exec` request.", }), - size: Schema.Struct({ - cols: Schema.Number.annotate({ - description: "Terminal width in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - rows: Schema.Number.annotate({ - description: "Terminal height in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - }).annotate({ description: "PTY size in character cells for `command/exec` PTY sessions." }), + size: Schema.suspend( + (): Schema.Codec => + V2CommandExecResizeParams__CommandExecTerminalSize, + ).annotate({ description: "New PTY size in character cells." }), }).annotate({ title: "CommandExecResizeParams", description: "Resize a running PTY-backed `command/exec` session.", }); -export type V2CommandExecResizeParams__CommandExecTerminalSize = { - readonly cols: number; - readonly rows: number; -}; -export const V2CommandExecResizeParams__CommandExecTerminalSize = Schema.Struct({ - cols: Schema.Number.annotate({ - description: "Terminal width in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - rows: Schema.Number.annotate({ - description: "Terminal height in character cells.", - format: "uint16", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), -}).annotate({ description: "PTY size in character cells for `command/exec` PTY sessions." }); - -export type V2CommandExecResizeResponse = {}; -export const V2CommandExecResizeResponse = Schema.Struct({}).annotate({ +export type V2CommandExecResizeResponse = { readonly [x: string]: Schema.Json }; +export const V2CommandExecResizeResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "CommandExecResizeResponse", description: "Empty success response for `command/exec/resize`.", }); @@ -37404,7 +51661,7 @@ export type V2CommandExecResponse = { }; export const V2CommandExecResponse = Schema.Struct({ exitCode: Schema.Number.annotate({ description: "Process exit code.", format: "int32" }).check( - Schema.isInt(), + Schema.isInt().annotate({ expected: "an integer" }), ), stderr: Schema.String.annotate({ description: @@ -37430,8 +51687,11 @@ export const V2CommandExecTerminateParams = Schema.Struct({ description: "Terminate a running `command/exec` session.", }); -export type V2CommandExecTerminateResponse = {}; -export const V2CommandExecTerminateResponse = Schema.Struct({}).annotate({ +export type V2CommandExecTerminateResponse = { readonly [x: string]: Schema.Json }; +export const V2CommandExecTerminateResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "CommandExecTerminateResponse", description: "Empty success response for `command/exec/terminate`.", }); @@ -37475,8 +51735,11 @@ export const V2CommandExecWriteParams = Schema.Struct({ description: "Write stdin bytes to a running `command/exec` session, close stdin, or both.", }); -export type V2CommandExecWriteResponse = {}; -export const V2CommandExecWriteResponse = Schema.Struct({}).annotate({ +export type V2CommandExecWriteResponse = { readonly [x: string]: Schema.Json }; +export const V2CommandExecWriteResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "CommandExecWriteResponse", description: "Empty success response for `command/exec/write`.", }); @@ -37502,7 +51765,7 @@ export const V2ConfigBatchWriteParams = Schema.Struct({ reloadUserConfig: Schema.optionalKey( Schema.Boolean.annotate({ description: - "When true, hot-reload the updated user config into all loaded threads after writing.", + "When true, hot-reload updated runtime settings into loaded threads after writing. Session-static model, reasoning-effort, Plan-mode reasoning-effort, and service-tier defaults are not reloaded. The deprecated personality setting is also not reloaded.", }), ), }).annotate({ title: "ConfigBatchWriteParams" }); @@ -37540,6 +51803,8 @@ export type V2ConfigReadResponse__AppConfig = { readonly default_tools_enabled?: boolean | null; readonly destructive_enabled?: boolean | null; readonly enabled?: boolean; + readonly links?: V2ConfigReadResponse__AppLinksConfig | null; + readonly omit_tools_from?: ReadonlyArray | null; readonly open_world_enabled?: boolean | null; readonly tools?: V2ConfigReadResponse__AppToolsConfig | null; }; @@ -37553,10 +51818,36 @@ export const V2ConfigReadResponse__AppConfig = Schema.Struct({ default_tools_enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), destructive_enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), enabled: Schema.optionalKey(Schema.Boolean.annotate({ default: true })), + links: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AppLinksConfig, Schema.Null]).annotate({ + description: "Per-account approval settings keyed by link ID.", + }), + ), + omit_tools_from: Schema.optionalKey( + Schema.Union([ + Schema.Array(V2ConfigReadResponse__ToolExposureSurface).annotate({ + description: "Additional model-facing surfaces omitted for this connector's tools.", + }), + Schema.Null, + ]), + ), open_world_enabled: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), tools: Schema.optionalKey(Schema.Union([V2ConfigReadResponse__AppToolsConfig, Schema.Null])), }); +export type V2ConfigReadResponse__AppLinkConfig = { + readonly approvals_reviewer?: V2ConfigReadResponse__ApprovalsReviewer | null; + readonly default_tools_approval_mode?: V2ConfigReadResponse__AppToolApproval | null; +}; +export const V2ConfigReadResponse__AppLinkConfig = Schema.Struct({ + approvals_reviewer: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__ApprovalsReviewer, Schema.Null]), + ), + default_tools_approval_mode: Schema.optionalKey( + Schema.Union([V2ConfigReadResponse__AppToolApproval, Schema.Null]), + ), +}).annotate({ description: "Approval settings for a connected account within an app." }); + export type V2ConfigReadResponse__AppsConfig = { readonly _default?: V2ConfigReadResponse__AppsDefaultConfig | null; }; @@ -37589,6 +51880,15 @@ export const V2ConfigRequirementsReadResponse = Schema.Struct({ ), }).annotate({ title: "ConfigRequirementsReadResponse" }); +export type V2ConfigRequirementsReadResponse__ApplicationRequirements = { + readonly network?: V2ConfigRequirementsReadResponse__ApplicationNetworkRequirements | null; +}; +export const V2ConfigRequirementsReadResponse__ApplicationRequirements = Schema.Struct({ + network: Schema.optionalKey( + Schema.Union([V2ConfigRequirementsReadResponse__ApplicationNetworkRequirements, Schema.Null]), + ), +}); + export type V2ConfigRequirementsReadResponse__ApprovalsReviewer = | "user" | "auto_review" @@ -37603,6 +51903,7 @@ export const V2ConfigRequirementsReadResponse__ApprovalsReviewer = Schema.Litera }); export type V2ConfigRequirementsReadResponse__ManagedHooksRequirements = { + readonly Interrupt?: ReadonlyArray; readonly PermissionRequest: ReadonlyArray; readonly PostCompact: ReadonlyArray; readonly PostToolUse: ReadonlyArray; @@ -37618,6 +51919,11 @@ export type V2ConfigRequirementsReadResponse__ManagedHooksRequirements = { readonly windowsManagedDir?: string | null; }; export const V2ConfigRequirementsReadResponse__ManagedHooksRequirements = Schema.Struct({ + Interrupt: Schema.optionalKey( + Schema.Array(V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup).annotate({ + default: [], + }), + ), PermissionRequest: Schema.Array(V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup), PostCompact: Schema.Array(V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup), PostToolUse: Schema.Array(V2ConfigRequirementsReadResponse__ConfiguredHookMatcherGroup), @@ -37698,8 +52004,12 @@ export const V2ConfigRequirementsReadResponse__NetworkRequirements = Schema.Stru httpPort: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -37715,8 +52025,12 @@ export const V2ConfigRequirementsReadResponse__NetworkRequirements = Schema.Stru socksPort: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ format: "uint16" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -37738,7 +52052,7 @@ export type V2ConfigValueWriteParams = { readonly filePath?: string | null; readonly keyPath: string; readonly mergeStrategy: V2ConfigValueWriteParams__MergeStrategy; - readonly value: unknown; + readonly value: Schema.Json; }; export const V2ConfigValueWriteParams = Schema.Struct({ expectedVersion: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -37753,7 +52067,7 @@ export const V2ConfigValueWriteParams = Schema.Struct({ ), keyPath: Schema.String, mergeStrategy: V2ConfigValueWriteParams__MergeStrategy, - value: Schema.Unknown, + value: Schema.Json.annotate({ expected: "JSON value" }), }).annotate({ title: "ConfigValueWriteParams" }); export type V2ConfigWarningNotification = { @@ -37786,16 +52100,16 @@ export const V2ConfigWarningNotification = Schema.Struct({ }).annotate({ title: "ConfigWarningNotification" }); export type V2ConfigWriteResponse = { - readonly filePath: string; + readonly filePath: V2ConfigWriteResponse__AbsolutePathBuf; readonly overriddenMetadata?: V2ConfigWriteResponse__OverriddenMetadata | null; readonly status: V2ConfigWriteResponse__WriteStatus; readonly version: string; }; export const V2ConfigWriteResponse = Schema.Struct({ - filePath: 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.", - }), + filePath: Schema.suspend( + (): Schema.Codec => + V2ConfigWriteResponse__AbsolutePathBuf, + ).annotate({ description: "Canonical path to the config file that was written." }), overriddenMetadata: Schema.optionalKey( Schema.Union([V2ConfigWriteResponse__OverriddenMetadata, Schema.Null]), ), @@ -37916,8 +52230,12 @@ export const V2ExperimentalFeatureListParams = Schema.Struct({ description: "Optional page size; defaults to a reasonable server-side value.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -37949,23 +52267,11 @@ export const V2ExperimentalFeatureListResponse = Schema.Struct({ ), }).annotate({ title: "ExperimentalFeatureListResponse" }); -export type V2ExperimentalFeatureListResponse__ExperimentalFeatureStage = - | "beta" - | "underDevelopment" - | "stable" - | "deprecated" - | "removed"; -export const V2ExperimentalFeatureListResponse__ExperimentalFeatureStage = Schema.Literals([ - "beta", - "underDevelopment", - "stable", - "deprecated", - "removed", -]); - export type V2ExternalAgentConfigDetectParams = { readonly cwds?: ReadonlyArray | null; readonly includeHome?: boolean; + readonly maxSessionAgeDays?: number | null; + readonly maxSessions?: number | null; readonly migrationSource?: string | null; readonly source?: string | null; }; @@ -37983,6 +52289,37 @@ export const V2ExternalAgentConfigDetectParams = Schema.Struct({ description: "If true, include detection under the user's home directory.", }), ), + maxSessionAgeDays: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Maximum age in days for detected sessions. Missing values use the default limit.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + maxSessions: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: "Maximum number of sessions to detect. Missing values use the default limit.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), migrationSource: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -38004,9 +52341,15 @@ export const V2ExternalAgentConfigDetectParams = Schema.Struct({ }).annotate({ title: "ExternalAgentConfigDetectParams" }); export type V2ExternalAgentConfigDetectResponse = { + readonly connectors?: ReadonlyArray; readonly items: ReadonlyArray; }; export const V2ExternalAgentConfigDetectResponse = Schema.Struct({ + connectors: Schema.optionalKey( + Schema.Array( + V2ExternalAgentConfigDetectResponse__ExternalAgentDetectedConnectorCandidate, + ).annotate({ default: [] }), + ), items: Schema.Array(V2ExternalAgentConfigDetectResponse__ExternalAgentConfigMigrationItem), }).annotate({ title: "ExternalAgentConfigDetectResponse" }); @@ -38034,9 +52377,28 @@ export const V2ExternalAgentConfigImportHistoriesReadResponse = Schema.Struct({ ), }).annotate({ title: "ExternalAgentConfigImportHistoriesReadResponse" }); +export type V2ExternalAgentConfigImportHistoryRecordParams = { + readonly itemTypeResults: ReadonlyArray; + readonly providerId: string; +}; +export const V2ExternalAgentConfigImportHistoryRecordParams = Schema.Struct({ + itemTypeResults: Schema.Array( + V2ExternalAgentConfigImportHistoryRecordParams__ExternalAgentConfigImportHistoryRecordTypeResultParams, + ).annotate({ description: "Completed results grouped by imported item type." }), + providerId: Schema.String.annotate({ + description: "Opaque provider identifier for the externally completed import.", + }), +}).annotate({ title: "ExternalAgentConfigImportHistoryRecordParams" }); + +export type V2ExternalAgentConfigImportHistoryRecordResponse = { readonly importId: string }; +export const V2ExternalAgentConfigImportHistoryRecordResponse = Schema.Struct({ + importId: Schema.String, +}).annotate({ title: "ExternalAgentConfigImportHistoryRecordResponse" }); + export type V2ExternalAgentConfigImportParams = { readonly migrationItems: ReadonlyArray; readonly migrationSource?: string | null; + readonly providerId?: string | null; readonly source?: string | null; }; export const V2ExternalAgentConfigImportParams = Schema.Struct({ @@ -38050,6 +52412,15 @@ export const V2ExternalAgentConfigImportParams = Schema.Struct({ Schema.Null, ]), ), + providerId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Opaque provider identifier supplied by the caller for analytics attribution and import history display. This does not select the migration source.", + }), + Schema.Null, + ]), + ), source: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -38095,10 +52466,22 @@ export const V2FeedbackUploadParams = Schema.Struct({ threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), }).annotate({ title: "FeedbackUploadParams" }); -export type V2FeedbackUploadResponse = { readonly threadId: string }; -export const V2FeedbackUploadResponse = Schema.Struct({ threadId: Schema.String }).annotate({ - title: "FeedbackUploadResponse", -}); +export type V2FeedbackUploadResponse = { + readonly promptHash?: string | null; + readonly threadId: string; +}; +export const V2FeedbackUploadResponse = Schema.Struct({ + promptHash: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Whitespace-normalized SHA-256 of the session base instructions, matching the uploaded `prompt_hash` tag. Does not include later developer messages. Null when the reported rollout has no prompt metadata.", + }), + Schema.Null, + ]), + ), + threadId: Schema.String, +}).annotate({ title: "FeedbackUploadResponse" }); export type V2FileChangeOutputDeltaNotification = { readonly delta: string; @@ -38147,50 +52530,42 @@ export const V2FsChangedNotification = Schema.Struct({ }); export type V2FsCopyParams = { - readonly destinationPath: string; + readonly destinationPath: V2FsCopyParams__AbsolutePathBuf; readonly recursive?: boolean; - readonly sourcePath: string; + readonly sourcePath: V2FsCopyParams__AbsolutePathBuf; }; export const V2FsCopyParams = Schema.Struct({ - destinationPath: 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.", - }), + destinationPath: Schema.suspend( + (): Schema.Codec => V2FsCopyParams__AbsolutePathBuf, + ).annotate({ description: "Absolute destination path." }), recursive: Schema.optionalKey( Schema.Boolean.annotate({ description: "Required for directory copies; ignored for file copies.", }), ), - sourcePath: 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.", - }), + sourcePath: Schema.suspend( + (): Schema.Codec => V2FsCopyParams__AbsolutePathBuf, + ).annotate({ description: "Absolute source path." }), }).annotate({ title: "FsCopyParams", description: "Copy a file or directory tree on the host filesystem.", }); -export type V2FsCopyParams__AbsolutePathBuf = string; -export const V2FsCopyParams__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 V2FsCopyResponse = {}; -export const V2FsCopyResponse = Schema.Struct({}).annotate({ - title: "FsCopyResponse", - description: "Successful response for `fs/copy`.", -}); +export type V2FsCopyResponse = { readonly [x: string]: Schema.Json }; +export const V2FsCopyResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "FsCopyResponse", description: "Successful response for `fs/copy`." }); export type V2FsCreateDirectoryParams = { - readonly path: string; + readonly path: V2FsCreateDirectoryParams__AbsolutePathBuf; readonly recursive?: boolean | null; }; export const V2FsCreateDirectoryParams = Schema.Struct({ - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => + V2FsCreateDirectoryParams__AbsolutePathBuf, + ).annotate({ description: "Absolute directory path to create." }), recursive: Schema.optionalKey( Schema.Union([ Schema.Boolean.annotate({ @@ -38204,35 +52579,26 @@ export const V2FsCreateDirectoryParams = Schema.Struct({ description: "Create a directory on the host filesystem.", }); -export type V2FsCreateDirectoryParams__AbsolutePathBuf = string; -export const V2FsCreateDirectoryParams__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 V2FsCreateDirectoryResponse = {}; -export const V2FsCreateDirectoryResponse = Schema.Struct({}).annotate({ +export type V2FsCreateDirectoryResponse = { readonly [x: string]: Schema.Json }; +export const V2FsCreateDirectoryResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "FsCreateDirectoryResponse", description: "Successful response for `fs/createDirectory`.", }); -export type V2FsGetMetadataParams = { readonly path: string }; +export type V2FsGetMetadataParams = { readonly path: V2FsGetMetadataParams__AbsolutePathBuf }; export const V2FsGetMetadataParams = Schema.Struct({ - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => + V2FsGetMetadataParams__AbsolutePathBuf, + ).annotate({ description: "Absolute path to inspect." }), }).annotate({ title: "FsGetMetadataParams", description: "Request metadata for an absolute path.", }); -export type V2FsGetMetadataParams__AbsolutePathBuf = string; -export const V2FsGetMetadataParams__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 V2FsGetMetadataResponse = { readonly createdAtMs: number; readonly isDirectory: boolean; @@ -38244,7 +52610,7 @@ export const V2FsGetMetadataResponse = Schema.Struct({ createdAtMs: Schema.Number.annotate({ description: "File creation time in Unix milliseconds when available, otherwise `0`.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), isDirectory: Schema.Boolean.annotate({ description: "Whether the path resolves to a directory.", }), @@ -38255,29 +52621,23 @@ export const V2FsGetMetadataResponse = Schema.Struct({ modifiedAtMs: Schema.Number.annotate({ description: "File modification time in Unix milliseconds when available, otherwise `0`.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), }).annotate({ title: "FsGetMetadataResponse", description: "Metadata returned by `fs/getMetadata`.", }); -export type V2FsReadDirectoryParams = { readonly path: string }; +export type V2FsReadDirectoryParams = { readonly path: V2FsReadDirectoryParams__AbsolutePathBuf }; export const V2FsReadDirectoryParams = Schema.Struct({ - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => + V2FsReadDirectoryParams__AbsolutePathBuf, + ).annotate({ description: "Absolute directory path to read." }), }).annotate({ title: "FsReadDirectoryParams", description: "List direct child names for a directory.", }); -export type V2FsReadDirectoryParams__AbsolutePathBuf = string; -export const V2FsReadDirectoryParams__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 V2FsReadDirectoryResponse = { readonly entries: ReadonlyArray; }; @@ -38290,20 +52650,13 @@ export const V2FsReadDirectoryResponse = Schema.Struct({ description: "Directory entries returned by `fs/readDirectory`.", }); -export type V2FsReadFileParams = { readonly path: string }; +export type V2FsReadFileParams = { readonly path: V2FsReadFileParams__AbsolutePathBuf }; export const V2FsReadFileParams = Schema.Struct({ - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => V2FsReadFileParams__AbsolutePathBuf, + ).annotate({ description: "Absolute path to read." }), }).annotate({ title: "FsReadFileParams", description: "Read a file from the host filesystem." }); -export type V2FsReadFileParams__AbsolutePathBuf = string; -export const V2FsReadFileParams__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 V2FsReadFileResponse = { readonly dataBase64: string }; export const V2FsReadFileResponse = Schema.Struct({ dataBase64: Schema.String.annotate({ description: "File contents encoded as base64." }), @@ -38314,7 +52667,7 @@ export const V2FsReadFileResponse = Schema.Struct({ export type V2FsRemoveParams = { readonly force?: boolean | null; - readonly path: string; + readonly path: V2FsRemoveParams__AbsolutePathBuf; readonly recursive?: boolean | null; }; export const V2FsRemoveParams = Schema.Struct({ @@ -38326,10 +52679,9 @@ export const V2FsRemoveParams = Schema.Struct({ Schema.Null, ]), ), - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => V2FsRemoveParams__AbsolutePathBuf, + ).annotate({ description: "Absolute path to remove." }), recursive: Schema.optionalKey( Schema.Union([ Schema.Boolean.annotate({ @@ -38343,17 +52695,11 @@ export const V2FsRemoveParams = Schema.Struct({ description: "Remove a file or directory tree from the host filesystem.", }); -export type V2FsRemoveParams__AbsolutePathBuf = string; -export const V2FsRemoveParams__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 V2FsRemoveResponse = {}; -export const V2FsRemoveResponse = Schema.Struct({}).annotate({ - title: "FsRemoveResponse", - description: "Successful response for `fs/remove`.", -}); +export type V2FsRemoveResponse = { readonly [x: string]: Schema.Json }; +export const V2FsRemoveResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "FsRemoveResponse", description: "Successful response for `fs/remove`." }); export type V2FsUnwatchParams = { readonly watchId: string }; export const V2FsUnwatchParams = Schema.Struct({ @@ -38365,18 +52711,20 @@ export const V2FsUnwatchParams = Schema.Struct({ description: "Stop filesystem watch notifications for a prior `fs/watch`.", }); -export type V2FsUnwatchResponse = {}; -export const V2FsUnwatchResponse = Schema.Struct({}).annotate({ - title: "FsUnwatchResponse", - description: "Successful response for `fs/unwatch`.", -}); +export type V2FsUnwatchResponse = { readonly [x: string]: Schema.Json }; +export const V2FsUnwatchResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "FsUnwatchResponse", description: "Successful response for `fs/unwatch`." }); -export type V2FsWatchParams = { readonly path: string; readonly watchId: string }; +export type V2FsWatchParams = { + readonly path: V2FsWatchParams__AbsolutePathBuf; + readonly watchId: string; +}; export const V2FsWatchParams = Schema.Struct({ - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => V2FsWatchParams__AbsolutePathBuf, + ).annotate({ description: "Absolute file or directory path to watch." }), watchId: Schema.String.annotate({ description: "Connection-scoped watch identifier used for `fs/unwatch` and `fs/changed`.", }), @@ -38385,43 +52733,29 @@ export const V2FsWatchParams = Schema.Struct({ description: "Start filesystem watch notifications for an absolute path.", }); -export type V2FsWatchParams__AbsolutePathBuf = string; -export const V2FsWatchParams__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 V2FsWatchResponse = { readonly path: string }; +export type V2FsWatchResponse = { readonly path: V2FsWatchResponse__AbsolutePathBuf }; export const V2FsWatchResponse = Schema.Struct({ - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => V2FsWatchResponse__AbsolutePathBuf, + ).annotate({ description: "Canonicalized path associated with the watch." }), }).annotate({ title: "FsWatchResponse", description: "Successful response for `fs/watch`." }); -export type V2FsWatchResponse__AbsolutePathBuf = string; -export const V2FsWatchResponse__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 V2FsWriteFileParams = { readonly dataBase64: string; readonly path: string }; +export type V2FsWriteFileParams = { + readonly dataBase64: string; + readonly path: V2FsWriteFileParams__AbsolutePathBuf; +}; export const V2FsWriteFileParams = Schema.Struct({ dataBase64: Schema.String.annotate({ description: "File contents encoded as base64." }), - path: 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.", - }), + path: Schema.suspend( + (): Schema.Codec => V2FsWriteFileParams__AbsolutePathBuf, + ).annotate({ description: "Absolute path to write." }), }).annotate({ title: "FsWriteFileParams", description: "Write a file on the host filesystem." }); -export type V2FsWriteFileParams__AbsolutePathBuf = string; -export const V2FsWriteFileParams__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 V2FsWriteFileResponse = {}; -export const V2FsWriteFileResponse = Schema.Struct({}).annotate({ +export type V2FsWriteFileResponse = { readonly [x: string]: Schema.Json }; +export const V2FsWriteFileResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "FsWriteFileResponse", description: "Successful response for `fs/writeFile`.", }); @@ -38437,57 +52771,47 @@ export const V2GetAccountParams = Schema.Struct({ }).annotate({ title: "GetAccountParams" }); export type V2GetAccountRateLimitsResponse = { + readonly accountId?: string | null; + readonly ordinaryUsageAllowed?: boolean | null; readonly rateLimitResetCredits?: V2GetAccountRateLimitsResponse__RateLimitResetCreditsSummary | null; - readonly rateLimits: { - readonly credits?: V2GetAccountRateLimitsResponse__CreditsSnapshot | null; - readonly individualLimit?: V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot | null; - readonly limitId?: string | null; - 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 spendControlReached?: boolean | null; - }; + readonly rateLimitUpsell?: Schema.Json; + readonly rateLimits: V2GetAccountRateLimitsResponse__RateLimitSnapshot; readonly rateLimitsByLimitId?: { readonly [x: string]: V2GetAccountRateLimitsResponse__RateLimitSnapshot; } | null; }; export const V2GetAccountRateLimitsResponse = Schema.Struct({ + accountId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Account associated with this usage snapshot, when supplied by the backend.", + }), + Schema.Null, + ]), + ), + ordinaryUsageAllowed: Schema.optionalKey( + Schema.Union([ + Schema.Boolean.annotate({ + description: + "Backend permission for ordinary included usage, validated against the active account. Null means unavailable; clients must not infer recovery from percentages or reset times.", + }), + Schema.Null, + ]), + ), rateLimitResetCredits: Schema.optionalKey( Schema.Union([V2GetAccountRateLimitsResponse__RateLimitResetCreditsSummary, Schema.Null]), ), - rateLimits: Schema.Struct({ - credits: Schema.optionalKey( - Schema.Union([V2GetAccountRateLimitsResponse__CreditsSnapshot, Schema.Null]), - ), - individualLimit: Schema.optionalKey( - Schema.Union([V2GetAccountRateLimitsResponse__SpendControlLimitSnapshot, Schema.Null]), - ), - limitId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - limitName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - planType: Schema.optionalKey( - Schema.Union([V2GetAccountRateLimitsResponse__PlanType, Schema.Null]), - ), - 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]), - ), - spendControlReached: Schema.optionalKey( - Schema.Union([ - Schema.Boolean.annotate({ - description: - "Backend-reported spend-control state. `None` is unavailable, not a sparse-update recovery.", - }), - Schema.Null, - ]), - ), - }).annotate({ + rateLimitUpsell: Schema.optionalKey( + Schema.Json.annotate({ + expected: "JSON value", + description: + "Optional backend-owned banner from the same usage read. Its nested keys retain the backend's snake_case contract; an absent banner leaves the client's existing UI unchanged.", + }), + ), + rateLimits: Schema.suspend( + (): Schema.Codec => + V2GetAccountRateLimitsResponse__RateLimitSnapshot, + ).annotate({ description: "Backward-compatible single-bucket view; mirrors the historical payload.", }), rateLimitsByLimitId: Schema.optionalKey( @@ -38509,9 +52833,21 @@ export const V2GetAccountResponse = Schema.Struct({ requiresOpenaiAuth: Schema.Boolean, }).annotate({ title: "GetAccountResponse" }); +export type V2GetAccountResponse__WorkspaceRouting = { + readonly accountRoutingOverride: V2GetAccountResponse__AccountRoutingOverride; + readonly backendOrigin: string; + readonly chatgptAccountId: string; +}; +export const V2GetAccountResponse__WorkspaceRouting = Schema.Struct({ + accountRoutingOverride: V2GetAccountResponse__AccountRoutingOverride, + backendOrigin: Schema.String, + chatgptAccountId: Schema.String, +}); + export type V2GetAccountTokenUsageResponse = { readonly dailyUsageBuckets?: ReadonlyArray | null; readonly summary: V2GetAccountTokenUsageResponse__AccountTokenUsageSummary; + readonly threadUsage?: V2GetAccountTokenUsageResponse__ThreadUsage | null; }; export const V2GetAccountTokenUsageResponse = Schema.Struct({ dailyUsageBuckets: Schema.optionalKey( @@ -38521,6 +52857,12 @@ export const V2GetAccountTokenUsageResponse = Schema.Struct({ ]), ), summary: V2GetAccountTokenUsageResponse__AccountTokenUsageSummary, + threadUsage: Schema.optionalKey( + Schema.Union([V2GetAccountTokenUsageResponse__ThreadUsage, Schema.Null]).annotate({ + description: + "Estimated usage when a thread was requested and its billing route is available.", + }), + ), }).annotate({ title: "GetAccountTokenUsageResponse" }); export type V2GetWorkspaceMessagesResponse = { @@ -38555,32 +52897,6 @@ 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" - | "plugin" - | "cloudRequirements" - | "cloudManagedConfig" - | "legacyManagedConfigFile" - | "legacyManagedConfigMdm" - | "unknown"; -export const V2HookCompletedNotification__HookSource = Schema.Literals([ - "system", - "user", - "project", - "mdm", - "sessionFlags", - "plugin", - "cloudRequirements", - "cloudManagedConfig", - "legacyManagedConfigFile", - "legacyManagedConfigMdm", - "unknown", -]); - export type V2HooksListParams = { readonly cwds?: ReadonlyArray }; export const V2HooksListParams = Schema.Struct({ cwds: Schema.optionalKey( @@ -38608,32 +52924,6 @@ 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" - | "plugin" - | "cloudRequirements" - | "cloudManagedConfig" - | "legacyManagedConfigFile" - | "legacyManagedConfigMdm" - | "unknown"; -export const V2HookStartedNotification__HookSource = Schema.Literals([ - "system", - "user", - "project", - "mdm", - "sessionFlags", - "plugin", - "cloudRequirements", - "cloudManagedConfig", - "legacyManagedConfigFile", - "legacyManagedConfigMdm", - "unknown", -]); - export type V2ItemCompletedNotification = { readonly completedAtMs: number; readonly item: V2ItemCompletedNotification__ThreadItem; @@ -38644,71 +52934,12 @@ export const V2ItemCompletedNotification = Schema.Struct({ completedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this item lifecycle completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), item: V2ItemCompletedNotification__ThreadItem, threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "ItemCompletedNotification" }); -export type V2ItemCompletedNotification__ByteRange = { - readonly end: number; - readonly start: number; -}; -export const V2ItemCompletedNotification__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 V2ItemCompletedNotification__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ItemCompletedNotification__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ItemCompletedNotification__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ItemCompletedNotification__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ItemCompletedNotification__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ItemCompletedNotification__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - export type V2ItemGuardianApprovalReviewCompletedNotification = { readonly action: V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReviewAction; readonly completedAtMs: number; @@ -38725,19 +52956,19 @@ export const V2ItemGuardianApprovalReviewCompletedNotification = Schema.Struct({ completedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this review completed.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), decisionSource: V2ItemGuardianApprovalReviewCompletedNotification__AutoReviewDecisionSource, review: V2ItemGuardianApprovalReviewCompletedNotification__GuardianApprovalReview, reviewId: Schema.String.annotate({ description: "Stable identifier for this review." }), startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this review started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), 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.", + "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) - stdin reviews, which refer to the existing parent command item and have a separate approval ID in the action payload - 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, ]), @@ -38766,12 +52997,12 @@ export const V2ItemGuardianApprovalReviewStartedNotification = Schema.Struct({ startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this review started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), 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.", + "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) - stdin reviews, which refer to the existing parent command item and have a separate approval ID in the action payload - 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, ]), @@ -38795,67 +53026,11 @@ export const V2ItemStartedNotification = Schema.Struct({ startedAtMs: Schema.Number.annotate({ description: "Unix timestamp (in milliseconds) when this item lifecycle started.", format: "int64", - }).check(Schema.isInt()), + }).check(Schema.isInt().annotate({ expected: "an integer" })), threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "ItemStartedNotification" }); -export type V2ItemStartedNotification__ByteRange = { readonly end: number; readonly start: number }; -export const V2ItemStartedNotification__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 V2ItemStartedNotification__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ItemStartedNotification__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ItemStartedNotification__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ItemStartedNotification__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ItemStartedNotification__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ItemStartedNotification__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - export type V2ListMcpServerStatusParams = { readonly cursor?: string | null; readonly detail?: V2ListMcpServerStatusParams__McpServerStatusDetail | null; @@ -38883,8 +53058,12 @@ export const V2ListMcpServerStatusParams = Schema.Struct({ description: "Optional page size; defaults to a server-defined value.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -38923,7 +53102,14 @@ export type V2LoginAccountParams = readonly chatgptPlanType?: string | null; readonly type: "chatgptAuthTokens"; } - | { readonly apiKey: string; readonly region: string; readonly type: "amazonBedrock" }; + | { readonly apiKey: string; readonly region: string; readonly type: "amazonBedrock" } + | { + readonly accessKeyId: string; + readonly region: string; + readonly secretAccessKey: string; + readonly sessionToken?: string | null; + readonly type: "amazonBedrockAccessKeys"; + }; export const V2LoginAccountParams = Schema.Union( [ Schema.Struct({ @@ -38978,6 +53164,18 @@ export const V2LoginAccountParams = Schema.Union( title: "AmazonBedrockv2::LoginAccountParams", description: "[UNSTABLE] Managed Amazon Bedrock login is experimental.", }), + Schema.Struct({ + accessKeyId: Schema.String, + region: Schema.String, + secretAccessKey: Schema.String, + sessionToken: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + type: Schema.Literal("amazonBedrockAccessKeys").annotate({ + title: "AmazonBedrockAccessKeysv2::LoginAccountParamsType", + }), + }).annotate({ + title: "AmazonBedrockAccessKeysv2::LoginAccountParams", + description: "[UNSTABLE] Managed Amazon Bedrock AWS access key login is experimental.", + }), ], { mode: "oneOf" }, ).annotate({ title: "LoginAccountParams" }); @@ -39032,10 +53230,11 @@ export const V2LoginAccountResponse = Schema.Union( { mode: "oneOf" }, ).annotate({ title: "LoginAccountResponse" }); -export type V2LogoutAccountResponse = {}; -export const V2LogoutAccountResponse = Schema.Struct({}).annotate({ - title: "LogoutAccountResponse", -}); +export type V2LogoutAccountResponse = { readonly [x: string]: Schema.Json }; +export const V2LogoutAccountResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "LogoutAccountResponse" }); export type V2MarketplaceAddParams = { readonly refName?: string | null; @@ -39092,11 +53291,22 @@ export const V2MarketplaceUpgradeResponse = Schema.Struct({ }).annotate({ title: "MarketplaceUpgradeResponse" }); export type V2McpResourceReadParams = { + readonly connectorId?: string | null; + readonly originCallId?: string | null; readonly server: string; readonly threadId?: string | null; readonly uri: string; }; export const V2McpResourceReadParams = Schema.Struct({ + connectorId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + originCallId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Originating MCP tool call used to select the resource's app.", + }), + Schema.Null, + ]), + ), server: Schema.String, threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), uri: Schema.String, @@ -39104,11 +53314,29 @@ export const V2McpResourceReadParams = Schema.Struct({ export type V2McpResourceReadResponse = { readonly contents: ReadonlyArray; + readonly originCallId?: string | null; }; export const V2McpResourceReadResponse = Schema.Struct({ contents: Schema.Array(V2McpResourceReadResponse__ResourceContent), + originCallId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Originating call when the server applied app-specific resource scoping.", + }), + Schema.Null, + ]), + ), }).annotate({ title: "McpResourceReadResponse" }); +export type V2McpServerEventStreamNotification = { + readonly notification: V2McpServerEventStreamNotification__McpServerEventNotification; + readonly subscriptionId: string; +}; +export const V2McpServerEventStreamNotification = Schema.Struct({ + notification: V2McpServerEventStreamNotification__McpServerEventNotification, + subscriptionId: Schema.String, +}).annotate({ title: "McpServerEventStreamNotification" }); + export type V2McpServerOauthLoginCompletedNotification = { readonly error?: string | null; readonly name: string; @@ -39123,17 +53351,32 @@ export const V2McpServerOauthLoginCompletedNotification = Schema.Struct({ }).annotate({ title: "McpServerOauthLoginCompletedNotification" }); export type V2McpServerOauthLoginParams = { + readonly clientRegistration?: V2McpServerOauthLoginParams__McpServerOauthClientRegistration | null; readonly name: string; readonly scopes?: ReadonlyArray | null; readonly threadId?: string | null; readonly timeoutSecs?: number | null; }; export const V2McpServerOauthLoginParams = Schema.Struct({ + clientRegistration: Schema.optionalKey( + Schema.Union([ + V2McpServerOauthLoginParams__McpServerOauthClientRegistration, + Schema.Null, + ]).annotate({ + description: + "Registration strategy for this login only; omission selects automatic discovery.", + }), + ), name: Schema.String, scopes: Schema.optionalKey(Schema.Union([Schema.Array(Schema.String), Schema.Null])), threadId: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), timeoutSecs: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), }).annotate({ title: "McpServerOauthLoginParams" }); @@ -39142,10 +53385,11 @@ export const V2McpServerOauthLoginResponse = Schema.Struct({ authorizationUrl: Schema.String, }).annotate({ title: "McpServerOauthLoginResponse" }); -export type V2McpServerRefreshResponse = {}; -export const V2McpServerRefreshResponse = Schema.Struct({}).annotate({ - title: "McpServerRefreshResponse", -}); +export type V2McpServerRefreshResponse = { readonly [x: string]: Schema.Json }; +export const V2McpServerRefreshResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "McpServerRefreshResponse" }); export type V2McpServerStatusUpdatedNotification = { readonly error?: string | null; @@ -39168,31 +53412,31 @@ export const V2McpServerStatusUpdatedNotification = Schema.Struct({ }).annotate({ title: "McpServerStatusUpdatedNotification" }); export type V2McpServerToolCallParams = { - readonly _meta?: unknown; - readonly arguments?: unknown; + readonly _meta?: Schema.Json; + readonly arguments?: Schema.Json; readonly server: string; readonly threadId: string; readonly tool: string; }; export const V2McpServerToolCallParams = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - arguments: Schema.optionalKey(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + arguments: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), server: Schema.String, threadId: Schema.String, tool: Schema.String, }).annotate({ title: "McpServerToolCallParams" }); export type V2McpServerToolCallResponse = { - readonly _meta?: unknown; - readonly content: ReadonlyArray; + readonly _meta?: Schema.Json; + readonly content: ReadonlyArray; readonly isError?: boolean | null; - readonly structuredContent?: unknown; + readonly structuredContent?: Schema.Json; }; export const V2McpServerToolCallResponse = Schema.Struct({ - _meta: Schema.optionalKey(Schema.Unknown), - content: Schema.Array(Schema.Unknown), + _meta: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), + content: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), isError: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), - structuredContent: Schema.optionalKey(Schema.Unknown), + structuredContent: Schema.optionalKey(Schema.Json.annotate({ expected: "JSON value" })), }).annotate({ title: "McpServerToolCallResponse" }); export type V2McpToolCallProgressNotification = { @@ -39236,8 +53480,12 @@ export const V2ModelListParams = Schema.Struct({ description: "Optional page size; defaults to a reasonable server-side value.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -39260,10 +53508,11 @@ export const V2ModelListResponse = Schema.Struct({ ), }).annotate({ title: "ModelListResponse" }); -export type V2ModelProviderCapabilitiesReadParams = {}; -export const V2ModelProviderCapabilitiesReadParams = Schema.Struct({}).annotate({ - title: "ModelProviderCapabilitiesReadParams", -}); +export type V2ModelProviderCapabilitiesReadParams = { readonly [x: string]: Schema.Json }; +export const V2ModelProviderCapabilitiesReadParams = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ModelProviderCapabilitiesReadParams" }); export type V2ModelProviderCapabilitiesReadResponse = { readonly imageGeneration: boolean; @@ -39321,6 +53570,18 @@ export const V2ModelVerificationNotification = Schema.Struct({ verifications: Schema.Array(V2ModelVerificationNotification__ModelVerification), }).annotate({ title: "ModelVerificationNotification" }); +export type V2NullableGetAccountRateLimitsParams = V2NullableGetAccountRateLimitsParams__GetAccountRateLimitsParams | null; +export const V2NullableGetAccountRateLimitsParams = Schema.Union([ + V2NullableGetAccountRateLimitsParams__GetAccountRateLimitsParams, + Schema.Null, +]).annotate({ title: "Nullable_GetAccountRateLimitsParams" }); + +export type V2NullableGetAccountTokenUsageParams = V2NullableGetAccountTokenUsageParams__GetAccountTokenUsageParams | null; +export const V2NullableGetAccountTokenUsageParams = Schema.Union([ + V2NullableGetAccountTokenUsageParams__GetAccountTokenUsageParams, + Schema.Null, +]).annotate({ title: "Nullable_GetAccountTokenUsageParams" }); + export type V2PermissionProfileListParams = { readonly cursor?: string | null; readonly cwd?: string | null; @@ -39349,8 +53610,12 @@ export const V2PermissionProfileListParams = Schema.Struct({ description: "Optional page size; defaults to the full result set.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -39425,18 +53690,21 @@ export const V2PluginInstalledResponse = Schema.Struct({ marketplaces: Schema.Array(V2PluginInstalledResponse__PluginMarketplaceEntry), }).annotate({ title: "PluginInstalledResponse" }); -export type V2PluginInstalledResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; -export const V2PluginInstalledResponse__PluginAvailability = Schema.Literals([ - "DISABLED_BY_ADMIN", - "AVAILABLE", -]); - export type V2PluginInstallParams = { + readonly installAttemptId?: string | null; readonly marketplacePath?: V2PluginInstallParams__AbsolutePathBuf | null; readonly pluginName: string; readonly remoteMarketplaceName?: string | null; }; export const V2PluginInstallParams = Schema.Struct({ + installAttemptId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Client-generated identifier used to correlate one installation attempt.", + }), + Schema.Null, + ]), + ), marketplacePath: Schema.optionalKey( Schema.Union([V2PluginInstallParams__AbsolutePathBuf, Schema.Null]), ), @@ -39455,6 +53723,7 @@ export const V2PluginInstallResponse = Schema.Struct({ export type V2PluginListParams = { readonly cwds?: ReadonlyArray | null; + readonly forceRefetch?: boolean; readonly marketplaceKinds?: ReadonlyArray | null; }; export const V2PluginListParams = Schema.Struct({ @@ -39467,6 +53736,11 @@ export const V2PluginListParams = Schema.Struct({ Schema.Null, ]), ), + forceRefetch: Schema.optionalKey( + Schema.Boolean.annotate({ + description: "Whether the client requests a fresh remote plugin catalog fetch.", + }), + ), marketplaceKinds: Schema.optionalKey( Schema.Union([ Schema.Array(V2PluginListParams__PluginListMarketplaceKind).annotate({ @@ -39491,12 +53765,6 @@ export const V2PluginListResponse = Schema.Struct({ marketplaces: Schema.Array(V2PluginListResponse__PluginMarketplaceEntry), }).annotate({ title: "PluginListResponse" }); -export type V2PluginListResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; -export const V2PluginListResponse__PluginAvailability = Schema.Literals([ - "DISABLED_BY_ADMIN", - "AVAILABLE", -]); - export type V2PluginReadParams = { readonly marketplacePath?: V2PluginReadParams__AbsolutePathBuf | null; readonly pluginName: string; @@ -39515,11 +53783,40 @@ export const V2PluginReadResponse = Schema.Struct({ plugin: V2PluginReadResponse__PluginDetail, }).annotate({ title: "PluginReadResponse" }); -export type V2PluginReadResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; -export const V2PluginReadResponse__PluginAvailability = Schema.Literals([ - "DISABLED_BY_ADMIN", - "AVAILABLE", -]); +export type V2PluginReconcileParams = { readonly reason?: string | null }; +export const V2PluginReconcileParams = Schema.Struct({ + reason: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Optional client-provided reason recorded with the reconciliation attempt.", + }), + Schema.Null, + ]), + ), +}).annotate({ title: "PluginReconcileParams" }); + +export type V2PluginReconcileResponse = { + readonly changedPlugins: ReadonlyArray; + readonly failedMaterializationRemotePluginIds: ReadonlyArray; + readonly failedRemotePluginIds: ReadonlyArray; +}; +export const V2PluginReconcileResponse = Schema.Struct({ + changedPlugins: Schema.Array(V2PluginReconcileResponse__PluginReconcileChangedPlugin).annotate({ + description: + "Plugins affected by bundle changes, enablement changes, or removals. Installed-state changes compare against the previous cached snapshot, including cached reinstalls. Removal hints survive cache cleanup failures; unchanged plugins are omitted.", + }), + failedMaterializationRemotePluginIds: Schema.Array(Schema.String).annotate({ + description: + "Subset of failures for which the requested bundle could not be materialized. A previously cached version may still be available.", + }), + failedRemotePluginIds: Schema.Array(Schema.String).annotate({ + description: "Backend remote plugin IDs whose bundle or identity update failed.", + }), +}).annotate({ + title: "PluginReconcileResponse", + description: + "Bundle and installed-state changes observed by this pass, not a runtime-readiness acknowledgement or a cumulative diff since the client's last request. Other metadata-only changes are not listed.", +}); export type V2PluginShareCheckoutParams = { readonly remotePluginId: string }; export const V2PluginShareCheckoutParams = Schema.Struct({ @@ -39550,15 +53847,17 @@ export const V2PluginShareDeleteParams = Schema.Struct({ remotePluginId: Schema. title: "PluginShareDeleteParams", }); -export type V2PluginShareDeleteResponse = {}; -export const V2PluginShareDeleteResponse = Schema.Struct({}).annotate({ - title: "PluginShareDeleteResponse", -}); +export type V2PluginShareDeleteResponse = { readonly [x: string]: Schema.Json }; +export const V2PluginShareDeleteResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "PluginShareDeleteResponse" }); -export type V2PluginShareListParams = {}; -export const V2PluginShareListParams = Schema.Struct({}).annotate({ - title: "PluginShareListParams", -}); +export type V2PluginShareListParams = { readonly [x: string]: Schema.Json }; +export const V2PluginShareListParams = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "PluginShareListParams" }); export type V2PluginShareListResponse = { readonly data: ReadonlyArray; @@ -39567,12 +53866,6 @@ export const V2PluginShareListResponse = Schema.Struct({ data: Schema.Array(V2PluginShareListResponse__PluginShareListItem), }).annotate({ title: "PluginShareListResponse" }); -export type V2PluginShareListResponse__PluginAvailability = "DISABLED_BY_ADMIN" | "AVAILABLE"; -export const V2PluginShareListResponse__PluginAvailability = Schema.Literals([ - "DISABLED_BY_ADMIN", - "AVAILABLE", -]); - export type V2PluginShareSaveParams = { readonly discoverability?: V2PluginShareSaveParams__PluginShareDiscoverability | null; readonly pluginPath: V2PluginShareSaveParams__AbsolutePathBuf; @@ -39591,10 +53884,12 @@ export const V2PluginShareSaveParams = Schema.Struct({ }).annotate({ title: "PluginShareSaveParams" }); export type V2PluginShareSaveResponse = { + readonly canPublishToWorkspace?: boolean | null; readonly remotePluginId: string; readonly shareUrl: string; }; export const V2PluginShareSaveResponse = Schema.Struct({ + canPublishToWorkspace: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), remotePluginId: Schema.String, shareUrl: Schema.String, }).annotate({ title: "PluginShareSaveResponse" }); @@ -39640,10 +53935,11 @@ export const V2PluginUninstallParams = Schema.Struct({ pluginId: Schema.String } title: "PluginUninstallParams", }); -export type V2PluginUninstallResponse = {}; -export const V2PluginUninstallResponse = Schema.Struct({}).annotate({ - title: "PluginUninstallResponse", -}); +export type V2PluginUninstallResponse = { readonly [x: string]: Schema.Json }; +export const V2PluginUninstallResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "PluginUninstallResponse" }); export type V2ProcessExitedNotification = { readonly exitCode: number; @@ -39655,7 +53951,7 @@ export type V2ProcessExitedNotification = { }; export const V2ProcessExitedNotification = Schema.Struct({ exitCode: Schema.Number.annotate({ description: "Process exit code.", format: "int32" }).check( - Schema.isInt(), + Schema.isInt().annotate({ expected: "an integer" }), ), processHandle: Schema.String.annotate({ description: "Client-supplied, connection-scoped `processHandle` from `process/spawn`.", @@ -39685,7 +53981,7 @@ export type V2ProcessOutputDeltaNotification = { readonly capReached: boolean; readonly deltaBase64: string; readonly processHandle: string; - readonly stream: "stdout" | "stderr"; + readonly stream: V2ProcessOutputDeltaNotification__ProcessOutputStream; }; export const V2ProcessOutputDeltaNotification = Schema.Struct({ capReached: Schema.Boolean.annotate({ @@ -39696,25 +53992,30 @@ export const V2ProcessOutputDeltaNotification = Schema.Struct({ processHandle: Schema.String.annotate({ description: "Client-supplied, connection-scoped `processHandle` from `process/spawn`.", }), - stream: Schema.Literals(["stdout", "stderr"]).annotate({ - description: "Stream label for `process/outputDelta` notifications.", - }), + stream: Schema.suspend( + (): Schema.Codec => + V2ProcessOutputDeltaNotification__ProcessOutputStream, + ).annotate({ description: "Output stream this chunk belongs to." }), }).annotate({ title: "ProcessOutputDeltaNotification", description: "Base64-encoded output chunk emitted for a streaming `process/spawn` request.", }); -export type V2ProcessOutputDeltaNotification__ProcessOutputStream = "stdout" | "stderr"; -export const V2ProcessOutputDeltaNotification__ProcessOutputStream = Schema.Literals([ - "stdout", - "stderr", -]).annotate({ description: "Stream label for `process/outputDelta` notifications." }); +export type V2ProjectChangedNotification = { + readonly changeType: V2ProjectChangedNotification__ProjectChangeType; + readonly projectId: string; +}; +export const V2ProjectChangedNotification = Schema.Struct({ + changeType: V2ProjectChangedNotification__ProjectChangeType, + projectId: Schema.String, +}).annotate({ title: "ProjectChangedNotification" }); export type V2RawResponseCompletedNotification = { readonly responseId: string; readonly threadId: string; readonly turnId: string; readonly usage?: V2RawResponseCompletedNotification__TokenUsageBreakdown | null; + readonly usageMetadata?: V2RawResponseCompletedNotification__ResponseUsageMetadata | null; }; export const V2RawResponseCompletedNotification = Schema.Struct({ responseId: Schema.String, @@ -39723,6 +54024,9 @@ export const V2RawResponseCompletedNotification = Schema.Struct({ usage: Schema.optionalKey( Schema.Union([V2RawResponseCompletedNotification__TokenUsageBreakdown, Schema.Null]), ), + usageMetadata: Schema.optionalKey( + Schema.Union([V2RawResponseCompletedNotification__ResponseUsageMetadata, Schema.Null]), + ), }).annotate({ title: "RawResponseCompletedNotification", description: @@ -39748,7 +54052,9 @@ export type V2ReasoningSummaryPartAddedNotification = { }; export const V2ReasoningSummaryPartAddedNotification = Schema.Struct({ itemId: Schema.String, - summaryIndex: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + summaryIndex: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "ReasoningSummaryPartAddedNotification" }); @@ -39763,7 +54069,9 @@ export type V2ReasoningSummaryTextDeltaNotification = { export const V2ReasoningSummaryTextDeltaNotification = Schema.Struct({ delta: Schema.String, itemId: Schema.String, - summaryIndex: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + summaryIndex: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "ReasoningSummaryTextDeltaNotification" }); @@ -39776,7 +54084,9 @@ export type V2ReasoningTextDeltaNotification = { readonly turnId: string; }; export const V2ReasoningTextDeltaNotification = Schema.Struct({ - contentIndex: Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), + contentIndex: Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), delta: Schema.String, itemId: Schema.String, threadId: Schema.String, @@ -39808,7 +54118,7 @@ export const V2ReviewStartParams = Schema.Struct({ delivery: Schema.optionalKey( Schema.Union([V2ReviewStartParams__ReviewDelivery, Schema.Null]).annotate({ description: - "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`).", + "Where to run the review: inline (default) on the current thread or detached on a new thread (returned in `reviewThreadId`). Detached delivery is deprecated and emits `deprecationNotice`. Use `thread/start` followed by an inline review for a separate review thread.", }), ), target: V2ReviewStartParams__ReviewTarget, @@ -39827,69 +54137,6 @@ export const V2ReviewStartResponse = Schema.Struct({ turn: V2ReviewStartResponse__Turn, }).annotate({ title: "ReviewStartResponse" }); -export type V2ReviewStartResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ReviewStartResponse__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 V2ReviewStartResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ReviewStartResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ReviewStartResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ReviewStartResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ReviewStartResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ReviewStartResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ReviewStartResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ReviewStartResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); - export type V2SendAddCreditsNudgeEmailParams = { readonly creditType: V2SendAddCreditsNudgeEmailParams__AddCreditsNudgeCreditType; }; @@ -39913,8 +54160,11 @@ export const V2ServerRequestResolvedNotification = Schema.Struct({ threadId: Schema.String, }).annotate({ title: "ServerRequestResolvedNotification" }); -export type V2SkillsChangedNotification = {}; -export const V2SkillsChangedNotification = Schema.Struct({}).annotate({ +export type V2SkillsChangedNotification = { readonly [x: string]: Schema.Json }; +export const V2SkillsChangedNotification = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "SkillsChangedNotification", description: "Notification emitted when watched local skill files change.\n\nTreat this as an invalidation signal and re-run `skills/list` with the client's current parameters when refreshed skill metadata is needed.", @@ -39949,10 +54199,11 @@ export const V2SkillsExtraRootsSetParams = Schema.Struct({ extraRoots: Schema.Array(V2SkillsExtraRootsSetParams__AbsolutePathBuf), }).annotate({ title: "SkillsExtraRootsSetParams" }); -export type V2SkillsExtraRootsSetResponse = {}; -export const V2SkillsExtraRootsSetResponse = Schema.Struct({}).annotate({ - title: "SkillsExtraRootsSetResponse", -}); +export type V2SkillsExtraRootsSetResponse = { readonly [x: string]: Schema.Json }; +export const V2SkillsExtraRootsSetResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "SkillsExtraRootsSetResponse" }); export type V2SkillsListParams = { readonly cwds?: ReadonlyArray; @@ -39978,6 +54229,20 @@ export const V2SkillsListResponse = Schema.Struct({ data: Schema.Array(V2SkillsListResponse__SkillsListEntry), }).annotate({ title: "SkillsListResponse" }); +export type V2StrictReviewRequiredNotification = { + readonly startedAtMs: number; + readonly threadId: string; + readonly turnId: string; +}; +export const V2StrictReviewRequiredNotification = Schema.Struct({ + startedAtMs: Schema.Number.annotate({ + description: "Unix timestamp (in milliseconds) when this review started.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + threadId: Schema.String, + turnId: Schema.String, +}).annotate({ title: "StrictReviewRequiredNotification" }); + export type V2TerminalInteractionNotification = { readonly itemId: string; readonly processId: string; @@ -39994,20 +54259,22 @@ export const V2TerminalInteractionNotification = Schema.Struct({ }).annotate({ title: "TerminalInteractionNotification" }); export type V2ThreadApproveGuardianDeniedActionParams = { - readonly event: unknown; + readonly event: Schema.Json; readonly threadId: string; }; export const V2ThreadApproveGuardianDeniedActionParams = Schema.Struct({ - event: Schema.Unknown.annotate({ + event: Schema.Json.annotate({ + expected: "JSON value", description: "Serialized `codex_protocol::protocol::GuardianAssessmentEvent`.", }), threadId: Schema.String, }).annotate({ title: "ThreadApproveGuardianDeniedActionParams" }); -export type V2ThreadApproveGuardianDeniedActionResponse = {}; -export const V2ThreadApproveGuardianDeniedActionResponse = Schema.Struct({}).annotate({ - title: "ThreadApproveGuardianDeniedActionResponse", -}); +export type V2ThreadApproveGuardianDeniedActionResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadApproveGuardianDeniedActionResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadApproveGuardianDeniedActionResponse" }); export type V2ThreadArchivedNotification = { readonly threadId: string }; export const V2ThreadArchivedNotification = Schema.Struct({ threadId: Schema.String }).annotate({ @@ -40019,9 +54286,116 @@ export const V2ThreadArchiveParams = Schema.Struct({ threadId: Schema.String }). title: "ThreadArchiveParams", }); -export type V2ThreadArchiveResponse = {}; -export const V2ThreadArchiveResponse = Schema.Struct({}).annotate({ - title: "ThreadArchiveResponse", +export type V2ThreadArchiveResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadArchiveResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadArchiveResponse" }); + +export type V2ThreadAttachmentAddParams = { + readonly attachmentType: string; + readonly identityKey: string; + readonly payload: Schema.Json; + readonly threadId: string; +}; +export const V2ThreadAttachmentAddParams = Schema.Struct({ + attachmentType: Schema.String, + identityKey: Schema.String, + payload: Schema.Json.annotate({ expected: "JSON value" }), + threadId: Schema.String, +}).annotate({ + title: "ThreadAttachmentAddParams", + description: "Parameters for creating or locating an attachment on its owning thread.", +}); + +export type V2ThreadAttachmentAddResponse = { + readonly attachment: V2ThreadAttachmentAddResponse__ThreadAttachment; + readonly outcome: V2ThreadAttachmentAddResponse__ThreadAttachmentAddOutcome; +}; +export const V2ThreadAttachmentAddResponse = Schema.Struct({ + attachment: V2ThreadAttachmentAddResponse__ThreadAttachment, + outcome: V2ThreadAttachmentAddResponse__ThreadAttachmentAddOutcome, +}).annotate({ + title: "ThreadAttachmentAddResponse", + description: "The created or existing attachment.", +}); + +export type V2ThreadAttachmentListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; + readonly threadId: string; +}; +export const V2ThreadAttachmentListParams = Schema.Struct({ + cursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + threadId: Schema.String, +}).annotate({ + title: "ThreadAttachmentListParams", + description: "Parameters for listing attachments from one thread.", +}); + +export type V2ThreadAttachmentListResponse = { + readonly data: ReadonlyArray; + readonly nextCursor?: string | null; +}; +export const V2ThreadAttachmentListResponse = Schema.Struct({ + data: Schema.Array(V2ThreadAttachmentListResponse__ThreadAttachment), + nextCursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ + title: "ThreadAttachmentListResponse", + description: "One page of attachments associated with the requested thread.", +}); + +export type V2ThreadAttachmentRemoveParams = { + readonly attachmentType: string; + readonly identityKey: string; + readonly threadId: string; +}; +export const V2ThreadAttachmentRemoveParams = Schema.Struct({ + attachmentType: Schema.String, + identityKey: Schema.String, + threadId: Schema.String, +}).annotate({ + title: "ThreadAttachmentRemoveParams", + description: "Parameters for deleting an attachment by its stable thread-local identity.", +}); + +export type V2ThreadAttachmentRemoveResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadAttachmentRemoveResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ + title: "ThreadAttachmentRemoveResponse", + description: "Successful deletion does not return additional attachment data.", +}); + +export type V2ThreadAttachmentUpdatedNotification = { + readonly attachmentId: string; + readonly attachmentType: string; + readonly identityKey: string; + readonly operation: V2ThreadAttachmentUpdatedNotification__ThreadAttachmentOperation; + readonly threadId: string; +}; +export const V2ThreadAttachmentUpdatedNotification = Schema.Struct({ + attachmentId: Schema.String, + attachmentType: Schema.String, + identityKey: Schema.String, + operation: V2ThreadAttachmentUpdatedNotification__ThreadAttachmentOperation, + threadId: Schema.String, +}).annotate({ + title: "ThreadAttachmentUpdatedNotification", + description: "Notification published after a thread attachment is created or deleted.", }); export type V2ThreadClosedNotification = { readonly threadId: string }; @@ -40034,10 +54408,11 @@ export const V2ThreadCompactStartParams = Schema.Struct({ threadId: Schema.Strin title: "ThreadCompactStartParams", }); -export type V2ThreadCompactStartResponse = {}; -export const V2ThreadCompactStartResponse = Schema.Struct({}).annotate({ - title: "ThreadCompactStartResponse", -}); +export type V2ThreadCompactStartResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadCompactStartResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadCompactStartResponse" }); export type V2ThreadDeletedNotification = { readonly threadId: string }; export const V2ThreadDeletedNotification = Schema.Struct({ threadId: Schema.String }).annotate({ @@ -40049,17 +54424,21 @@ export const V2ThreadDeleteParams = Schema.Struct({ threadId: Schema.String }).a title: "ThreadDeleteParams", }); -export type V2ThreadDeleteResponse = {}; -export const V2ThreadDeleteResponse = Schema.Struct({}).annotate({ title: "ThreadDeleteResponse" }); +export type V2ThreadDeleteResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadDeleteResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadDeleteResponse" }); export type V2ThreadForkParams = { readonly approvalPolicy?: V2ThreadForkParams__AskForApproval | null; readonly approvalsReviewer?: V2ThreadForkParams__ApprovalsReviewer | null; readonly baseInstructions?: string | null; - readonly config?: { readonly [x: string]: unknown } | null; + readonly config?: { readonly [x: string]: Schema.Json } | null; readonly cwd?: string | null; readonly developerInstructions?: string | null; readonly ephemeral?: boolean; + readonly excludeTurns?: boolean; readonly lastTurnId?: string | null; readonly model?: string | null; readonly modelProvider?: string | null; @@ -40080,11 +54459,20 @@ export const V2ThreadForkParams = Schema.Struct({ ), baseInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), config: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.Unknown), Schema.Null]), + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + Schema.Null, + ]), ), cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), developerInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), ephemeral: Schema.optionalKey(Schema.Boolean), + excludeTurns: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.", + }), + ), lastTurnId: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -40126,33 +54514,30 @@ export const V2ThreadForkParams__AbsolutePathBuf = Schema.String.annotate({ export type V2ThreadForkResponse = { readonly approvalPolicy: V2ThreadForkResponse__AskForApproval; - readonly approvalsReviewer: "user" | "auto_review" | "guardian_subagent"; + readonly approvalsReviewer: V2ThreadForkResponse__ApprovalsReviewer; readonly cwd: V2ThreadForkResponse__AbsolutePathBuf; + readonly disabledPluginIds?: ReadonlyArray; readonly instructionSources?: ReadonlyArray; readonly model: string; readonly modelProvider: string; readonly reasoningEffort?: V2ThreadForkResponse__ReasoningEffort | null; - readonly sandbox: - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; + readonly sandbox: V2ThreadForkResponse__SandboxPolicy; readonly serviceTier?: string | null; readonly thread: V2ThreadForkResponse__Thread; }; export const V2ThreadForkResponse = Schema.Struct({ approvalPolicy: V2ThreadForkResponse__AskForApproval, - approvalsReviewer: Schema.Literals(["user", "auto_review", "guardian_subagent"]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", - }), + approvalsReviewer: Schema.suspend( + (): Schema.Codec => + V2ThreadForkResponse__ApprovalsReviewer, + ).annotate({ description: "Reviewer currently used for approval requests on this thread." }), cwd: V2ThreadForkResponse__AbsolutePathBuf, + disabledPluginIds: Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + description: "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.", + default: [], + }), + ), instructionSources: Schema.optionalKey( Schema.Array(V2ThreadForkResponse__LegacyAppPathString).annotate({ description: @@ -40165,38 +54550,8 @@ export const V2ThreadForkResponse = Schema.Struct({ reasoningEffort: Schema.optionalKey( Schema.Union([V2ThreadForkResponse__ReasoningEffort, Schema.Null]), ), - sandbox: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ - title: "WorkspaceWriteSandboxPolicyType", - }), - writableRoots: Schema.optionalKey( - Schema.Array(V2ThreadForkResponse__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, + sandbox: Schema.suspend( + (): Schema.Codec => V2ThreadForkResponse__SandboxPolicy, ).annotate({ description: "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", @@ -40212,84 +54567,18 @@ export type V2ThreadForkResponse__ActivePermissionProfile = { export const V2ThreadForkResponse__ActivePermissionProfile = Schema.Struct({ extends: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ - description: - "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", - }), - Schema.Null, - ]), - ), - id: Schema.String.annotate({ - description: - "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", - }), -}); - -export type V2ThreadForkResponse__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const V2ThreadForkResponse__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); - -export type V2ThreadForkResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ThreadForkResponse__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 V2ThreadForkResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadForkResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadForkResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadForkResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadForkResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadForkResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); + Schema.String.annotate({ + description: + "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", + }), + Schema.Null, + ]), + ), + id: Schema.String.annotate({ + description: + "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", + }), +}); export type V2ThreadForkResponse__MultiAgentMode = | "explicitRequestOnly" @@ -40306,112 +54595,24 @@ export const V2ThreadForkResponse__MultiAgentMode = Schema.Union( "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", }); -export type V2ThreadForkResponse__NetworkAccess = "restricted" | "enabled"; -export const V2ThreadForkResponse__NetworkAccess = Schema.Literals(["restricted", "enabled"]); - -export type V2ThreadForkResponse__SandboxPolicy = - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const V2ThreadForkResponse__SandboxPolicy = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(V2ThreadForkResponse__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadForkResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadForkResponse__SubAgentSource }; -export const V2ThreadForkResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadForkResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadForkResponse__ThreadExtra = {}; -export const V2ThreadForkResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export type V2ThreadForkResponse__ThreadEnvironment = { + readonly cwd: V2ThreadForkResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadForkResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadForkResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadForkResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", }); -export type V2ThreadForkResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadForkResponse__ThreadHistoryMode = Schema.Literals(["legacy", "paginated"]); - -export type V2ThreadForkResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadForkResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadForkResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadForkResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadForkResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export type V2ThreadForkResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadForkResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); export type V2ThreadGoalClearedNotification = { readonly threadId: string }; export const V2ThreadGoalClearedNotification = Schema.Struct({ threadId: Schema.String }).annotate({ @@ -40451,7 +54652,12 @@ export const V2ThreadGoalSetParams = Schema.Struct({ status: Schema.optionalKey(Schema.Union([V2ThreadGoalSetParams__ThreadGoalStatus, Schema.Null])), threadId: Schema.String, tokenBudget: Schema.optionalKey( - Schema.Union([Schema.Number.annotate({ format: "int64" }).check(Schema.isInt()), Schema.Null]), + Schema.Union([ + Schema.Number.annotate({ format: "int64" }).check( + Schema.isInt().annotate({ expected: "an integer" }), + ), + Schema.Null, + ]), ), }).annotate({ title: "ThreadGoalSetParams" }); @@ -40472,20 +54678,93 @@ export const V2ThreadGoalUpdatedNotification = Schema.Struct({ }).annotate({ title: "ThreadGoalUpdatedNotification" }); export type V2ThreadInjectItemsParams = { - readonly items: ReadonlyArray; + readonly items: ReadonlyArray; readonly threadId: string; }; export const V2ThreadInjectItemsParams = Schema.Struct({ - items: Schema.Array(Schema.Unknown).annotate({ + items: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })).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 V2ThreadInjectItemsResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadInjectItemsResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadInjectItemsResponse" }); + +export type V2ThreadItemsListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; + readonly sortDirection?: V2ThreadItemsListParams__SortDirection | null; + readonly threadId: string; + readonly turnId?: string | null; +}; +export const V2ThreadItemsListParams = Schema.Struct({ + cursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque cursor to pass to the next call to continue after the last item.", + }), + Schema.Null, + ]), + ), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ description: "Optional item page size.", format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), + Schema.Null, + ]), + ), + sortDirection: Schema.optionalKey( + Schema.Union([V2ThreadItemsListParams__SortDirection, Schema.Null]).annotate({ + description: "Optional item pagination direction; defaults to ascending.", + }), + ), + threadId: Schema.String, + turnId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional turn id to filter by. When omitted, returns items across the thread.", + }), + Schema.Null, + ]), + ), +}).annotate({ title: "ThreadItemsListParams" }); + +export type V2ThreadItemsListResponse = { + readonly backwardsCursor?: string | null; + readonly data: ReadonlyArray; + readonly nextCursor?: string | null; +}; +export const V2ThreadItemsListResponse = 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 item.", + }), + Schema.Null, + ]), + ), + data: Schema.Array(V2ThreadItemsListResponse__ThreadItemEntry), + nextCursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Opaque cursor to pass to the next call to continue after the last item. if None, there are no more items to return.", + }), + Schema.Null, + ]), + ), +}).annotate({ title: "ThreadItemsListResponse" }); export type V2ThreadListParams = { readonly archived?: boolean | null; @@ -40493,7 +54772,9 @@ export type V2ThreadListParams = { readonly cwd?: V2ThreadListParams__ThreadListCwdFilter | null; readonly limit?: number | null; readonly modelProviders?: ReadonlyArray | null; + readonly originators?: ReadonlyArray | null; readonly searchTerm?: string | null; + readonly sectionId?: string | null; readonly sortDirection?: V2ThreadListParams__SortDirection | null; readonly sortKey?: V2ThreadListParams__ThreadSortKey | null; readonly sourceKinds?: ReadonlyArray | null; @@ -40529,8 +54810,12 @@ export const V2ThreadListParams = Schema.Struct({ description: "Optional page size; defaults to a reasonable server-side value.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -40543,6 +54828,15 @@ export const V2ThreadListParams = Schema.Struct({ Schema.Null, ]), ), + originators: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + description: + "Optional originator allowlist, matching any supplied value exactly. Supported by hosted backends only; the local app-server rejects a nonempty list. Omitted or empty lists leave originators unrestricted.", + }), + Schema.Null, + ]), + ), searchTerm: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -40551,6 +54845,15 @@ export const V2ThreadListParams = Schema.Struct({ Schema.Null, ]), ), + sectionId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Omit to include every section, set to `null` for unsectioned threads, or provide a section ID to return only threads in that section.", + }), + Schema.Null, + ]), + ), sortDirection: Schema.optionalKey( Schema.Union([V2ThreadListParams__SortDirection, Schema.Null]).annotate({ description: "Optional sort direction; defaults to descending (newest first).", @@ -40605,122 +54908,24 @@ export const V2ThreadListResponse = Schema.Struct({ ), }).annotate({ title: "ThreadListResponse" }); -export type V2ThreadListResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ThreadListResponse__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 V2ThreadListResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadListResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadListResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadListResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadListResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadListResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ThreadListResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadListResponse__SubAgentSource }; -export const V2ThreadListResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadListResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadListResponse__ThreadExtra = {}; -export const V2ThreadListResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export type V2ThreadListResponse__ThreadEnvironment = { + readonly cwd: V2ThreadListResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadListResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadListResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadListResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", }); -export type V2ThreadListResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadListResponse__ThreadHistoryMode = Schema.Literals(["legacy", "paginated"]); - -export type V2ThreadListResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadListResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadListResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadListResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadListResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export type V2ThreadListResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadListResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); export type V2ThreadLoadedListParams = { readonly cursor?: string | null; @@ -40741,8 +54946,12 @@ export const V2ThreadLoadedListParams = Schema.Struct({ description: "Optional page size; defaults to no limit.", format: "uint32", }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -40784,276 +54993,89 @@ export const V2ThreadMetadataUpdateParams = Schema.Struct({ threadId: Schema.String, }).annotate({ title: "ThreadMetadataUpdateParams" }); -export type V2ThreadMetadataUpdateResponse = { - readonly thread: V2ThreadMetadataUpdateResponse__Thread; -}; -export const V2ThreadMetadataUpdateResponse = Schema.Struct({ - thread: V2ThreadMetadataUpdateResponse__Thread, -}).annotate({ title: "ThreadMetadataUpdateResponse" }); - -export type V2ThreadMetadataUpdateResponse__ByteRange = { - readonly end: number; - readonly start: number; -}; -export const V2ThreadMetadataUpdateResponse__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 V2ThreadMetadataUpdateResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadMetadataUpdateResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadMetadataUpdateResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadMetadataUpdateResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadMetadataUpdateResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadMetadataUpdateResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ThreadMetadataUpdateResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadMetadataUpdateResponse__SubAgentSource }; -export const V2ThreadMetadataUpdateResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadMetadataUpdateResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadMetadataUpdateResponse__ThreadExtra = {}; -export const V2ThreadMetadataUpdateResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", -}); - -export type V2ThreadMetadataUpdateResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadMetadataUpdateResponse__ThreadHistoryMode = Schema.Literals([ - "legacy", - "paginated", -]); - -export type V2ThreadMetadataUpdateResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadMetadataUpdateResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadMetadataUpdateResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadMetadataUpdateResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadMetadataUpdateResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); - -export type V2ThreadNameUpdatedNotification = { - readonly threadId: string; - readonly threadName?: string | null; -}; -export const V2ThreadNameUpdatedNotification = Schema.Struct({ - threadId: Schema.String, - threadName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}).annotate({ title: "ThreadNameUpdatedNotification" }); - -export type V2ThreadReadParams = { readonly includeTurns?: boolean; readonly threadId: string }; -export const V2ThreadReadParams = Schema.Struct({ - includeTurns: Schema.optionalKey( - Schema.Boolean.annotate({ - description: "When true, include turns and their items from rollout history.", - }), - ), - threadId: Schema.String, -}).annotate({ title: "ThreadReadParams" }); - -export type V2ThreadReadResponse = { readonly thread: V2ThreadReadResponse__Thread }; -export const V2ThreadReadResponse = Schema.Struct({ - thread: V2ThreadReadResponse__Thread, -}).annotate({ title: "ThreadReadResponse" }); - -export type V2ThreadReadResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ThreadReadResponse__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 V2ThreadReadResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadReadResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadReadResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadReadResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadReadResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadReadResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ThreadReadResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadReadResponse__SubAgentSource }; -export const V2ThreadReadResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadReadResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadReadResponse__ThreadExtra = {}; -export const V2ThreadReadResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export type V2ThreadMetadataUpdateResponse = { + readonly thread: V2ThreadMetadataUpdateResponse__Thread; +}; +export const V2ThreadMetadataUpdateResponse = Schema.Struct({ + thread: V2ThreadMetadataUpdateResponse__Thread, +}).annotate({ title: "ThreadMetadataUpdateResponse" }); + +export type V2ThreadMetadataUpdateResponse__ThreadEnvironment = { + readonly cwd: V2ThreadMetadataUpdateResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadMetadataUpdateResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadMetadataUpdateResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadMetadataUpdateResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", }); -export type V2ThreadReadResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadReadResponse__ThreadHistoryMode = Schema.Literals(["legacy", "paginated"]); +export type V2ThreadMetadataUpdateResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadMetadataUpdateResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); -export type V2ThreadReadResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadReadResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadReadResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, +export type V2ThreadNameUpdatedNotification = { + readonly threadId: string; + readonly threadName?: string | null; +}; +export const V2ThreadNameUpdatedNotification = Schema.Struct({ + threadId: Schema.String, + threadName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}).annotate({ title: "ThreadNameUpdatedNotification" }); + +export type V2ThreadProjectUpdatedNotification = { + readonly projectId: string | null; + readonly threadId: string; +}; +export const V2ThreadProjectUpdatedNotification = Schema.Struct({ + projectId: Schema.Union([Schema.String, Schema.Null]), + threadId: Schema.String, +}).annotate({ title: "ThreadProjectUpdatedNotification" }); + +export type V2ThreadQueueChangedNotification = { readonly threadId: string }; +export const V2ThreadQueueChangedNotification = Schema.Struct({ threadId: Schema.String }).annotate( + { title: "ThreadQueueChangedNotification" }, ); -export type V2ThreadReadResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadReadResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export type V2ThreadReadParams = { readonly includeTurns?: boolean; readonly threadId: string }; +export const V2ThreadReadParams = Schema.Struct({ + includeTurns: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When true, include turns and their items from rollout history. Full-history hydration is deprecated for paginated threads; prefer a metadata-only read and page with `thread/turns/list` and `thread/items/list`.", + }), + ), + threadId: Schema.String, +}).annotate({ title: "ThreadReadParams" }); + +export type V2ThreadReadResponse = { readonly thread: V2ThreadReadResponse__Thread }; +export const V2ThreadReadResponse = Schema.Struct({ + thread: V2ThreadReadResponse__Thread, +}).annotate({ title: "ThreadReadResponse" }); + +export type V2ThreadReadResponse__ThreadEnvironment = { + readonly cwd: V2ThreadReadResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadReadResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadReadResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadReadResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", +}); + +export type V2ThreadReadResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadReadResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); export type V2ThreadRealtimeClosedNotification = { readonly reason?: string | null; @@ -41080,17 +55102,55 @@ export const V2ThreadRealtimeErrorNotification = Schema.Struct({ }); export type V2ThreadRealtimeItemAddedNotification = { - readonly item: unknown; + readonly item: Schema.Json; readonly threadId: string; }; export const V2ThreadRealtimeItemAddedNotification = Schema.Struct({ - item: Schema.Unknown, + item: Schema.Json.annotate({ expected: "JSON value" }), threadId: Schema.String, }).annotate({ title: "ThreadRealtimeItemAddedNotification", description: "EXPERIMENTAL - raw non-audio thread realtime item emitted by the backend.", }); +export type V2ThreadRealtimeItemCompletedNotification = { + readonly item: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeItem; + readonly threadId: string; +}; +export const V2ThreadRealtimeItemCompletedNotification = Schema.Struct({ + item: V2ThreadRealtimeItemCompletedNotification__ThreadRealtimeItem, + threadId: Schema.String, +}).annotate({ + title: "ThreadRealtimeItemCompletedNotification", + description: "EXPERIMENTAL - a realtime timeline item published after canonical commit.", +}); + +export type V2ThreadRealtimeItemStartedNotification = { + readonly item: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeItem; + readonly threadId: string; +}; +export const V2ThreadRealtimeItemStartedNotification = Schema.Struct({ + item: V2ThreadRealtimeItemStartedNotification__ThreadRealtimeItem, + threadId: Schema.String, +}).annotate({ + title: "ThreadRealtimeItemStartedNotification", + description: "EXPERIMENTAL - a realtime timeline item started before its content streams.", +}); + +export type V2ThreadRealtimeItemTranscriptDeltaNotification = { + readonly delta: string; + readonly itemId: string; + readonly threadId: string; +}; +export const V2ThreadRealtimeItemTranscriptDeltaNotification = Schema.Struct({ + delta: Schema.String, + itemId: Schema.String, + threadId: Schema.String, +}).annotate({ + title: "ThreadRealtimeItemTranscriptDeltaNotification", + description: "EXPERIMENTAL - text appended to an active realtime transcript item.", +}); + export type V2ThreadRealtimeOutputAudioDeltaNotification = { readonly audio: V2ThreadRealtimeOutputAudioDeltaNotification__ThreadRealtimeAudioChunk; readonly threadId: string; @@ -41160,9 +55220,10 @@ export type V2ThreadResumeParams = { readonly approvalPolicy?: V2ThreadResumeParams__AskForApproval | null; readonly approvalsReviewer?: V2ThreadResumeParams__ApprovalsReviewer | null; readonly baseInstructions?: string | null; - readonly config?: { readonly [x: string]: unknown } | null; + readonly config?: { readonly [x: string]: Schema.Json } | null; readonly cwd?: string | null; readonly developerInstructions?: string | null; + readonly excludeTurns?: boolean; readonly model?: string | null; readonly modelProvider?: string | null; readonly personality?: V2ThreadResumeParams__Personality | null; @@ -41182,10 +55243,19 @@ export const V2ThreadResumeParams = Schema.Struct({ ), baseInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), config: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.Unknown), Schema.Null]), + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + Schema.Null, + ]), ), cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), developerInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + excludeTurns: Schema.optionalKey( + Schema.Boolean.annotate({ + description: + "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.", + }), + ), model: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ @@ -41195,7 +55265,12 @@ export const V2ThreadResumeParams = Schema.Struct({ ]), ), modelProvider: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - personality: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__Personality, Schema.Null])), + personality: Schema.optionalKey( + Schema.Union([V2ThreadResumeParams__Personality, Schema.Null]).annotate({ + description: + "@deprecated `friendly` and `pragmatic` no longer select a style. Changing this does not rewrite the thread's existing instructions.", + }), + ), sandbox: Schema.optionalKey(Schema.Union([V2ThreadResumeParams__SandboxMode, Schema.Null])), serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), threadId: Schema.String, @@ -41247,6 +55322,7 @@ export type V2ThreadResumeParams__ResponseItem = | { readonly arguments: string; readonly call_id: string; + readonly encrypted_function_args?: ReadonlyArray | null; readonly id?: string | null; readonly internal_chat_message_metadata_passthrough?: V2ThreadResumeParams__InternalChatMessageMetadataPassthrough | null; readonly name: string; @@ -41254,7 +55330,7 @@ export type V2ThreadResumeParams__ResponseItem = readonly type: "function_call"; } | { - readonly arguments: unknown; + readonly arguments: Schema.Json; readonly call_id?: string | null; readonly execution: string; readonly id?: string | null; @@ -41263,9 +55339,11 @@ export type V2ThreadResumeParams__ResponseItem = readonly type: "tool_search_call"; } | { - readonly call_id: string; + readonly call_id?: string | null; readonly id?: string | null; readonly internal_chat_message_metadata_passthrough?: V2ThreadResumeParams__InternalChatMessageMetadataPassthrough | null; + readonly name?: string | null; + readonly namespace?: string | null; readonly output: V2ThreadResumeParams__FunctionCallOutputBody; readonly type: "function_call_output"; } @@ -41293,7 +55371,7 @@ export type V2ThreadResumeParams__ResponseItem = readonly id?: string | null; readonly internal_chat_message_metadata_passthrough?: V2ThreadResumeParams__InternalChatMessageMetadataPassthrough | null; readonly status: string; - readonly tools: ReadonlyArray; + readonly tools: ReadonlyArray; readonly type: "tool_search_output"; } | { @@ -41317,6 +55395,10 @@ export type V2ThreadResumeParams__ResponseItem = readonly internal_chat_message_metadata_passthrough?: V2ThreadResumeParams__InternalChatMessageMetadataPassthrough | null; readonly type: "compaction"; } + | { + readonly reasoning: V2ThreadResumeParams__ConfigurationReasoning; + readonly type: "configuration_update"; + } | { readonly type: "compaction_trigger" } | { readonly encrypted_content?: string | null; @@ -41386,6 +55468,9 @@ export const V2ThreadResumeParams__ResponseItem = Schema.Union( Schema.Struct({ arguments: Schema.String, call_id: Schema.String, + encrypted_function_args: Schema.optionalKey( + Schema.Union([Schema.Array(Schema.String), Schema.Null]), + ), id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), internal_chat_message_metadata_passthrough: Schema.optionalKey( Schema.Union([V2ThreadResumeParams__InternalChatMessageMetadataPassthrough, Schema.Null]), @@ -41395,7 +55480,7 @@ export const V2ThreadResumeParams__ResponseItem = Schema.Union( type: Schema.Literal("function_call").annotate({ title: "FunctionCallResponseItemType" }), }).annotate({ title: "FunctionCallResponseItem" }), Schema.Struct({ - arguments: Schema.Unknown, + arguments: Schema.Json.annotate({ expected: "JSON value" }), call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), execution: Schema.String, id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -41408,11 +55493,13 @@ export const V2ThreadResumeParams__ResponseItem = Schema.Union( }), }).annotate({ title: "ToolSearchCallResponseItem" }), Schema.Struct({ - call_id: Schema.String, + call_id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), id: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), internal_chat_message_metadata_passthrough: Schema.optionalKey( Schema.Union([V2ThreadResumeParams__InternalChatMessageMetadataPassthrough, Schema.Null]), ), + name: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + namespace: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), output: V2ThreadResumeParams__FunctionCallOutputBody, type: Schema.Literal("function_call_output").annotate({ title: "FunctionCallOutputResponseItemType", @@ -41452,7 +55539,7 @@ export const V2ThreadResumeParams__ResponseItem = Schema.Union( Schema.Union([V2ThreadResumeParams__InternalChatMessageMetadataPassthrough, Schema.Null]), ), status: Schema.String, - tools: Schema.Array(Schema.Unknown), + tools: Schema.Array(Schema.Json.annotate({ expected: "JSON value" })), type: Schema.Literal("tool_search_output").annotate({ title: "ToolSearchOutputResponseItemType", }), @@ -41488,6 +55575,15 @@ export const V2ThreadResumeParams__ResponseItem = Schema.Union( ), type: Schema.Literal("compaction").annotate({ title: "CompactionResponseItemType" }), }).annotate({ title: "CompactionResponseItem" }), + Schema.Struct({ + reasoning: V2ThreadResumeParams__ConfigurationReasoning, + type: Schema.Literal("configuration_update").annotate({ + title: "ConfigurationUpdateResponseItemType", + }), + }).annotate({ + title: "ConfigurationUpdateResponseItem", + description: "A durable input control interpreted by the backend at its position in history.", + }), Schema.Struct({ type: Schema.Literal("compaction_trigger").annotate({ title: "CompactionTriggerResponseItemType", @@ -41524,8 +55620,12 @@ export const V2ThreadResumeParams__ThreadResumeInitialTurnsPageParams = Schema.S limit: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ description: "Optional turn page size.", format: "uint32" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), @@ -41538,783 +55638,370 @@ export const V2ThreadResumeParams__ThreadResumeInitialTurnsPageParams = Schema.S export type V2ThreadResumeResponse = { readonly approvalPolicy: V2ThreadResumeResponse__AskForApproval; - readonly approvalsReviewer: "user" | "auto_review" | "guardian_subagent"; + readonly approvalsReviewer: V2ThreadResumeResponse__ApprovalsReviewer; + readonly collaborationMode?: V2ThreadResumeResponse__CollaborationMode | null; readonly cwd: V2ThreadResumeResponse__AbsolutePathBuf; + readonly disabledPluginIds?: ReadonlyArray; readonly instructionSources?: ReadonlyArray; + readonly itemsBackwardsCursor?: string | null; readonly model: string; readonly modelProvider: string; readonly reasoningEffort?: V2ThreadResumeResponse__ReasoningEffort | null; - readonly sandbox: - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; + readonly sandbox: V2ThreadResumeResponse__SandboxPolicy; readonly serviceTier?: string | null; readonly thread: V2ThreadResumeResponse__Thread; + readonly turnsBackwardsCursor?: string | null; }; export const V2ThreadResumeResponse = Schema.Struct({ approvalPolicy: V2ThreadResumeResponse__AskForApproval, - approvalsReviewer: Schema.Literals(["user", "auto_review", "guardian_subagent"]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", - }), - cwd: V2ThreadResumeResponse__AbsolutePathBuf, - instructionSources: Schema.optionalKey( - Schema.Array(V2ThreadResumeResponse__LegacyAppPathString).annotate({ - description: - "Environment-native paths to instruction source files currently loaded for this thread.", - default: [], + approvalsReviewer: Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__ApprovalsReviewer, + ).annotate({ description: "Reviewer currently used for approval requests on this thread." }), + collaborationMode: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__CollaborationMode, Schema.Null]).annotate({ + description: "Effective collaboration mode. Absent when resuming from an older server.", }), ), - model: Schema.String, - modelProvider: Schema.String, - reasoningEffort: Schema.optionalKey( - Schema.Union([V2ThreadResumeResponse__ReasoningEffort, Schema.Null]), - ), - sandbox: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ - title: "WorkspaceWriteSandboxPolicyType", - }), - writableRoots: Schema.optionalKey( - Schema.Array(V2ThreadResumeResponse__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, - ).annotate({ - description: - "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", - }), - serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - thread: V2ThreadResumeResponse__Thread, -}).annotate({ title: "ThreadResumeResponse" }); - -export type V2ThreadResumeResponse__ActivePermissionProfile = { - readonly extends?: string | null; - readonly id: string; -}; -export const V2ThreadResumeResponse__ActivePermissionProfile = Schema.Struct({ - extends: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", - }), - Schema.Null, - ]), - ), - id: Schema.String.annotate({ - description: - "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", - }), -}); - -export type V2ThreadResumeResponse__ApprovalsReviewer = - | "user" - | "auto_review" - | "guardian_subagent"; -export const V2ThreadResumeResponse__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); - -export type V2ThreadResumeResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ThreadResumeResponse__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 V2ThreadResumeResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadResumeResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadResumeResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadResumeResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadResumeResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadResumeResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ThreadResumeResponse__MultiAgentMode = - | "explicitRequestOnly" - | "proactive" - | { readonly custom: string }; -export const V2ThreadResumeResponse__MultiAgentMode = Schema.Union( - [ - Schema.Literals(["explicitRequestOnly", "proactive"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomMultiAgentMode" }), - ], - { mode: "oneOf" }, -).annotate({ - description: - "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", -}); - -export type V2ThreadResumeResponse__NetworkAccess = "restricted" | "enabled"; -export const V2ThreadResumeResponse__NetworkAccess = Schema.Literals(["restricted", "enabled"]); - -export type V2ThreadResumeResponse__SandboxPolicy = - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const V2ThreadResumeResponse__SandboxPolicy = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(V2ThreadResumeResponse__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadResumeResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadResumeResponse__SubAgentSource }; -export const V2ThreadResumeResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadResumeResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadResumeResponse__ThreadExtra = {}; -export const V2ThreadResumeResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", -}); - -export type V2ThreadResumeResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadResumeResponse__ThreadHistoryMode = Schema.Literals(["legacy", "paginated"]); - -export type V2ThreadResumeResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadResumeResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadResumeResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadResumeResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadResumeResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); - -export type V2ThreadResumeResponse__TurnsPage = { - readonly backwardsCursor?: string | null; - readonly data: ReadonlyArray; - readonly nextCursor?: string | null; -}; -export const V2ThreadResumeResponse__TurnsPage = Schema.Struct({ - backwardsCursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - data: Schema.Array(V2ThreadResumeResponse__Turn), - nextCursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), -}); - -export type V2ThreadRollbackParams = { readonly numTurns: number; readonly threadId: string }; -export const V2ThreadRollbackParams = Schema.Struct({ - numTurns: Schema.Number.annotate({ - description: - "The number of turns to drop from the end of the thread. Must be >= 1.\n\nThis only modifies the thread's history and does not revert local file changes that have been made by the agent. Clients are responsible for reverting these changes.", - format: "uint32", - }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), - threadId: Schema.String, -}).annotate({ - title: "ThreadRollbackParams", - description: "DEPRECATED: `thread/rollback` will be removed soon.", -}); - -export type V2ThreadRollbackResponse = { - readonly thread: { - readonly agentNickname?: string | null; - readonly agentRole?: string | null; - readonly cliVersion: string; - readonly createdAt: number; - readonly cwd: string; - readonly ephemeral: boolean; - readonly forkedFromId?: string | null; - readonly gitInfo?: V2ThreadRollbackResponse__GitInfo | null; - readonly id: string; - readonly modelProvider: string; - readonly name?: string | null; - readonly parentThreadId?: string | null; - readonly path?: string | null; - readonly preview: string; - readonly recencyAt?: number | null; - readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadRollbackResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; - readonly threadSource?: V2ThreadRollbackResponse__ThreadSource | null; - readonly turns: ReadonlyArray; - readonly updatedAt: number; - }; -}; -export const V2ThreadRollbackResponse = Schema.Struct({ - thread: Schema.Struct({ - agentNickname: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Optional random unique nickname assigned to an AgentControl-spawned sub-agent.", - }), - Schema.Null, - ]), - ), - agentRole: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.", - }), - Schema.Null, - ]), - ), - cliVersion: Schema.String.annotate({ - description: "Version of the CLI that created the thread.", - }), - createdAt: Schema.Number.annotate({ - description: "Unix timestamp (in seconds) when the thread was created.", - format: "int64", - }).check(Schema.isInt()), - 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.", - }), - forkedFromId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: "Source thread id when this thread was created by forking another thread.", - }), - Schema.Null, - ]), - ), - gitInfo: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__GitInfo, Schema.Null]).annotate({ - description: "Optional Git metadata captured when the thread was created.", - }), - ), - id: Schema.String.annotate({ - description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", - }), - modelProvider: Schema.String.annotate({ - description: "Model provider used for this thread (for example, 'openai').", - }), - name: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "Optional user-facing thread title." }), - Schema.Null, - ]), - ), - parentThreadId: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "The ID of the parent thread. This will only be set if this thread is a subagent.", - }), - Schema.Null, - ]), - ), - path: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ description: "[UNSTABLE] Path to the thread on disk." }), - Schema.Null, - ]), - ), - preview: Schema.String.annotate({ - description: "Usually the first user message in the thread, if available.", - }), - recencyAt: Schema.optionalKey( - Schema.Union([ - Schema.Number.annotate({ - description: "Unix timestamp (in seconds) used for thread recency ordering.", - format: "int64", - }).check(Schema.isInt()), - Schema.Null, - ]), - ), - sessionId: Schema.String.annotate({ - description: "Session id shared by threads that belong to the same session tree.", - }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadRollbackResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, - ).annotate({ - description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", - }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadRollbackResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, - ).annotate({ description: "Current runtime status for the thread." }), - threadSource: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__ThreadSource, Schema.Null]).annotate({ - description: "Optional analytics source classification for this thread.", - }), - ), - turns: Schema.Array(V2ThreadRollbackResponse__Turn).annotate({ - description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", - }), - updatedAt: Schema.Number.annotate({ - description: "Unix timestamp (in seconds) when the thread was last updated.", - format: "int64", - }).check(Schema.isInt()), - }).annotate({ - description: - "The updated thread after applying the rollback, with `turns` populated.\n\nThe ThreadItems stored in each Turn are lossy since we explicitly do not persist all agent interactions, such as command executions. This is the same behavior as `thread/resume`.", - }), -}).annotate({ title: "ThreadRollbackResponse" }); - -export type V2ThreadRollbackResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ThreadRollbackResponse__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 V2ThreadRollbackResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadRollbackResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadRollbackResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadRollbackResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadRollbackResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadRollbackResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ThreadRollbackResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadRollbackResponse__SubAgentSource }; -export const V2ThreadRollbackResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadRollbackResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadRollbackResponse__Thread = { - readonly agentNickname?: string | null; - readonly agentRole?: string | null; - readonly cliVersion: string; - readonly createdAt: number; - readonly cwd: string; - readonly ephemeral: boolean; - readonly forkedFromId?: string | null; - readonly gitInfo?: V2ThreadRollbackResponse__GitInfo | null; - readonly id: string; - readonly modelProvider: string; - readonly name?: string | null; - readonly parentThreadId?: string | null; - readonly path?: string | null; - readonly preview: string; - readonly recencyAt?: number | null; - readonly sessionId: string; - readonly source: - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadRollbackResponse__SubAgentSource }; - readonly status: - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; - readonly threadSource?: V2ThreadRollbackResponse__ThreadSource | null; - readonly turns: ReadonlyArray; - readonly updatedAt: number; -}; -export const V2ThreadRollbackResponse__Thread = Schema.Struct({ - agentNickname: Schema.optionalKey( + cwd: V2ThreadResumeResponse__AbsolutePathBuf, + disabledPluginIds: Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + description: "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.", + default: [], + }), + ), + instructionSources: Schema.optionalKey( + Schema.Array(V2ThreadResumeResponse__LegacyAppPathString).annotate({ + description: + "Environment-native paths to instruction source files currently loaded for this thread.", + default: [], + }), + ), + itemsBackwardsCursor: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ description: - "Optional random unique nickname assigned to an AgentControl-spawned sub-agent.", + 'Opaque cursor for hydrating paginated items backwards.\n\nPass this as `cursor` to `thread/items/list` with `sortDirection: "desc"`. The first page includes the item identified by the cursor.', }), Schema.Null, ]), ), - agentRole: Schema.optionalKey( + model: Schema.String, + modelProvider: Schema.String, + reasoningEffort: Schema.optionalKey( + Schema.Union([V2ThreadResumeResponse__ReasoningEffort, Schema.Null]), + ), + sandbox: Schema.suspend( + (): Schema.Codec => + V2ThreadResumeResponse__SandboxPolicy, + ).annotate({ + description: + "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", + }), + serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + thread: V2ThreadResumeResponse__Thread, + turnsBackwardsCursor: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.", + description: + 'Opaque cursor for hydrating paginated turns backwards.\n\nPass this as `cursor` to `thread/turns/list` with `sortDirection: "desc"`. The first page includes the turn identified by the cursor.', }), Schema.Null, ]), ), - cliVersion: Schema.String.annotate({ - description: "Version of the CLI that created the thread.", - }), - createdAt: Schema.Number.annotate({ - description: "Unix timestamp (in seconds) when the thread was created.", - format: "int64", - }).check(Schema.isInt()), - 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.", - }), - forkedFromId: Schema.optionalKey( +}).annotate({ title: "ThreadResumeResponse" }); + +export type V2ThreadResumeResponse__ActivePermissionProfile = { + readonly extends?: string | null; + readonly id: string; +}; +export const V2ThreadResumeResponse__ActivePermissionProfile = Schema.Struct({ + extends: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ - description: "Source thread id when this thread was created by forking another thread.", + description: + "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", }), Schema.Null, ]), ), - gitInfo: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__GitInfo, Schema.Null]).annotate({ - description: "Optional Git metadata captured when the thread was created.", - }), - ), id: Schema.String.annotate({ - description: "Identifier for this thread. Codex-generated thread IDs are UUIDv7.", + description: + "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", }), - modelProvider: Schema.String.annotate({ - description: "Model provider used for this thread (for example, 'openai').", +}); + +export type V2ThreadResumeResponse__MultiAgentMode = + | "explicitRequestOnly" + | "proactive" + | { readonly custom: string }; +export const V2ThreadResumeResponse__MultiAgentMode = Schema.Union( + [ + Schema.Literals(["explicitRequestOnly", "proactive"]), + Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomMultiAgentMode" }), + ], + { mode: "oneOf" }, +).annotate({ + description: + "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", +}); + +export type V2ThreadResumeResponse__ThreadEnvironment = { + readonly cwd: V2ThreadResumeResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadResumeResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadResumeResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadResumeResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", +}); + +export type V2ThreadResumeResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadResumeResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); + +export type V2ThreadResumeResponse__TurnsPage = { + readonly backwardsCursor?: string | null; + readonly data: ReadonlyArray; + readonly nextCursor?: string | null; +}; +export const V2ThreadResumeResponse__TurnsPage = Schema.Struct({ + backwardsCursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + data: Schema.Array(V2ThreadResumeResponse__Turn), + nextCursor: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), +}); + +export type V2ThreadRevertedNotification = { readonly threadId: string }; +export const V2ThreadRevertedNotification = Schema.Struct({ threadId: Schema.String }).annotate({ + title: "ThreadRevertedNotification", +}); + +export type V2ThreadRevertParams = { readonly beforeTurnId: string; readonly threadId: string }; +export const V2ThreadRevertParams = Schema.Struct({ + beforeTurnId: Schema.String.annotate({ + description: "Turn excluded from the replacement history, together with every later turn.", }), - name: Schema.optionalKey( + threadId: Schema.String, +}).annotate({ + title: "ThreadRevertParams", + description: + "Replace a paginated thread's durable history with the prefix before one turn.\n\nThis only changes persisted conversation history. It does not revert local file changes.", +}); + +export type V2ThreadRevertResponse = { + readonly itemsBackwardsCursor?: string | null; + readonly thread: V2ThreadRevertResponse__Thread; + readonly turnsBackwardsCursor?: string | null; +}; +export const V2ThreadRevertResponse = Schema.Struct({ + itemsBackwardsCursor: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "Optional user-facing thread title." }), + Schema.String.annotate({ + description: + 'Opaque cursor for hydrating paginated items backwards.\n\nPass this as `cursor` to `thread/items/list` with `sortDirection: "desc"`. The first page includes the item identified by the cursor.', + }), Schema.Null, ]), ), - parentThreadId: Schema.optionalKey( + thread: Schema.suspend( + (): Schema.Codec => V2ThreadRevertResponse__Thread, + ).annotate({ + description: + "Updated loaded thread metadata. `turns` is always empty; hydrate retained history through `thread/turns/list`.", + }), + turnsBackwardsCursor: Schema.optionalKey( Schema.Union([ Schema.String.annotate({ description: - "The ID of the parent thread. This will only be set if this thread is a subagent.", + 'Opaque cursor for hydrating paginated turns backwards.\n\nPass this as `cursor` to `thread/turns/list` with `sortDirection: "desc"`. The first page includes the turn identified by the cursor.', }), Schema.Null, ]), ), - path: Schema.optionalKey( +}).annotate({ title: "ThreadRevertResponse" }); + +export type V2ThreadRevertResponse__ThreadEnvironment = { + readonly cwd: V2ThreadRevertResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadRevertResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadRevertResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadRevertResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", +}); + +export type V2ThreadRevertResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadRevertResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); + +export type V2ThreadSectionCreateParams = { + readonly appearance?: V2ThreadSectionCreateParams__ThreadSectionAppearance | null; + readonly name: string; +}; +export const V2ThreadSectionCreateParams = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadSectionCreateParams__ThreadSectionAppearance, Schema.Null]), + ), + name: Schema.String.annotate({ description: "The user-visible name of the section." }), +}).annotate({ + title: "ThreadSectionCreateParams", + description: "Parameters for creating an independently persisted thread section.", +}); + +export type V2ThreadSectionCreateResponse = { + readonly section: V2ThreadSectionCreateResponse__ThreadSection; +}; +export const V2ThreadSectionCreateResponse = Schema.Struct({ + section: V2ThreadSectionCreateResponse__ThreadSection, +}).annotate({ + title: "ThreadSectionCreateResponse", + description: "The independently persisted section created by the server.", +}); + +export type V2ThreadSectionDeleteParams = { readonly sectionId: string }; +export const V2ThreadSectionDeleteParams = Schema.Struct({ + sectionId: Schema.String.annotate({ + description: "The stable, server-generated identity of the section to delete.", + }), +}).annotate({ + title: "ThreadSectionDeleteParams", + description: "Parameters for deleting an independently persisted thread section.", +}); + +export type V2ThreadSectionDeleteResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadSectionDeleteResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ + title: "ThreadSectionDeleteResponse", + description: "Successful deletion does not return additional section data.", +}); + +export type V2ThreadSectionListParams = { + readonly cursor?: string | null; + readonly limit?: number | null; +}; +export const V2ThreadSectionListParams = Schema.Struct({ + cursor: Schema.optionalKey( Schema.Union([ - Schema.String.annotate({ description: "[UNSTABLE] Path to the thread on disk." }), + Schema.String.annotate({ + description: "Opaque pagination cursor returned by a previous call.", + }), Schema.Null, ]), ), - preview: Schema.String.annotate({ - description: "Usually the first user message in the thread, if available.", - }), - recencyAt: Schema.optionalKey( + limit: Schema.optionalKey( Schema.Union([ Schema.Number.annotate({ - description: "Unix timestamp (in seconds) used for thread recency ordering.", - format: "int64", - }).check(Schema.isInt()), + description: "Maximum number of sections to return.", + format: "uint32", + }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 0", + }), + ), Schema.Null, ]), ), - sessionId: Schema.String.annotate({ - description: "Session id shared by threads that belong to the same session tree.", - }), - source: Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadRollbackResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", +}).annotate({ + title: "ThreadSectionListParams", + description: "Parameters for listing independently persisted thread sections.", +}); + +export type V2ThreadSectionListResponse = { + readonly data: ReadonlyArray; + readonly nextCursor?: string | null; +}; +export const V2ThreadSectionListResponse = Schema.Struct({ + data: Schema.Array(V2ThreadSectionListResponse__ThreadSection), + nextCursor: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Opaque cursor for the next page, or `null` when no sections remain.", }), - ], - { mode: "oneOf" }, - ).annotate({ - description: "Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.).", - }), - status: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadRollbackResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, - ).annotate({ description: "Current runtime status for the thread." }), - threadSource: Schema.optionalKey( - Schema.Union([V2ThreadRollbackResponse__ThreadSource, Schema.Null]).annotate({ - description: "Optional analytics source classification for this thread.", - }), + Schema.Null, + ]), ), - turns: Schema.Array(V2ThreadRollbackResponse__Turn).annotate({ - description: - "Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` (when `includeTurns` is true) responses. For all other responses and notifications returning a Thread, the turns field will be an empty list.", - }), - updatedAt: Schema.Number.annotate({ - description: "Unix timestamp (in seconds) when the thread was last updated.", - format: "int64", - }).check(Schema.isInt()), +}).annotate({ + title: "ThreadSectionListResponse", + description: "One page of independently persisted thread sections.", }); -export type V2ThreadRollbackResponse__ThreadExtra = {}; -export const V2ThreadRollbackResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export type V2ThreadSectionMoveParams = { + readonly beforeThreadId?: string | null; + readonly sectionId: string | null; + readonly threadId: string; +}; +export const V2ThreadSectionMoveParams = Schema.Struct({ + beforeThreadId: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: "Existing thread to insert before; omission or null appends to the section.", + }), + Schema.Null, + ]), + ), + sectionId: Schema.Union([ + Schema.String.annotate({ + description: "Destination section, or `null` to remove the thread from its section.", + }), + Schema.Null, + ]), + threadId: Schema.String.annotate({ + description: "Thread to move into, within, or out of a section.", + }), +}).annotate({ + title: "ThreadSectionMoveParams", + description: "Parameters for moving a thread within a server-owned section ordering.", }); -export type V2ThreadRollbackResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadRollbackResponse__ThreadHistoryMode = Schema.Literals(["legacy", "paginated"]); +export type V2ThreadSectionMoveResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadSectionMoveResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadSectionMoveResponse" }); -export type V2ThreadRollbackResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadRollbackResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadRollbackResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); +export type V2ThreadSectionUpdateParams = { + readonly appearance?: V2ThreadSectionUpdateParams__ThreadSectionAppearance | null; + readonly name: string; + readonly sectionId: string; +}; +export const V2ThreadSectionUpdateParams = Schema.Struct({ + appearance: Schema.optionalKey( + Schema.Union([V2ThreadSectionUpdateParams__ThreadSectionAppearance, Schema.Null]).annotate({ + description: "Omit to preserve appearance, use `null` to clear it, or provide a replacement.", + }), + ), + name: Schema.String.annotate({ description: "The updated user-visible name of the section." }), + sectionId: Schema.String.annotate({ + description: "The stable, server-generated identity of the section to update.", + }), +}).annotate({ + title: "ThreadSectionUpdateParams", + description: "Parameters for updating an independently persisted thread section.", +}); -export type V2ThreadRollbackResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadRollbackResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export type V2ThreadSectionUpdateResponse = { + readonly section: V2ThreadSectionUpdateResponse__ThreadSection; +}; +export const V2ThreadSectionUpdateResponse = Schema.Struct({ + section: V2ThreadSectionUpdateResponse__ThreadSection, +}).annotate({ + title: "ThreadSectionUpdateResponse", + description: "The independently persisted section after its name is updated.", +}); export type V2ThreadSetNameParams = { readonly name: string; readonly threadId: string }; export const V2ThreadSetNameParams = Schema.Struct({ @@ -42322,10 +56009,11 @@ export const V2ThreadSetNameParams = Schema.Struct({ threadId: Schema.String, }).annotate({ title: "ThreadSetNameParams" }); -export type V2ThreadSetNameResponse = {}; -export const V2ThreadSetNameResponse = Schema.Struct({}).annotate({ - title: "ThreadSetNameResponse", -}); +export type V2ThreadSetNameResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadSetNameResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadSetNameResponse" }); export type V2ThreadSettingsUpdatedNotification = { readonly threadId: string; @@ -42351,159 +56039,64 @@ export const V2ThreadSettingsUpdatedNotification__MultiAgentMode = Schema.Union( "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", }); -export type V2ThreadSettingsUpdatedNotification__NetworkAccess = "restricted" | "enabled"; -export const V2ThreadSettingsUpdatedNotification__NetworkAccess = Schema.Literals([ - "restricted", - "enabled", -]); - -export type V2ThreadShellCommandParams = { readonly command: string; readonly threadId: string }; +export type V2ThreadShellCommandParams = { + readonly command: string; + readonly threadId: string; + readonly timeoutMs?: number | null; +}; export const V2ThreadShellCommandParams = Schema.Struct({ command: Schema.String.annotate({ description: "Shell command string evaluated by the thread's configured shell. Unlike `command/exec`, this intentionally preserves shell syntax such as pipes, redirects, and quoting. This runs unsandboxed with full access rather than inheriting the thread sandbox policy.", }), threadId: Schema.String, + timeoutMs: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ + description: + "Maximum execution time in milliseconds. Defaults to one hour when omitted or null. Must be non-negative; zero requests an immediate timeout, not unlimited execution. Does not affect the immediate RPC acknowledgement.", + format: "int64", + }).check(Schema.isInt().annotate({ expected: "an integer" })), + Schema.Null, + ]), + ), }).annotate({ title: "ThreadShellCommandParams" }); -export type V2ThreadShellCommandResponse = {}; -export const V2ThreadShellCommandResponse = Schema.Struct({}).annotate({ - title: "ThreadShellCommandResponse", -}); +export type V2ThreadShellCommandResponse = { readonly [x: string]: Schema.Json }; +export const V2ThreadShellCommandResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "ThreadShellCommandResponse" }); export type V2ThreadStartedNotification = { readonly thread: V2ThreadStartedNotification__Thread }; export const V2ThreadStartedNotification = Schema.Struct({ thread: V2ThreadStartedNotification__Thread, }).annotate({ title: "ThreadStartedNotification" }); -export type V2ThreadStartedNotification__ByteRange = { - readonly end: number; - readonly start: number; +export type V2ThreadStartedNotification__ThreadEnvironment = { + readonly cwd: V2ThreadStartedNotification__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; }; -export const V2ThreadStartedNotification__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 V2ThreadStartedNotification__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadStartedNotification__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadStartedNotification__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadStartedNotification__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadStartedNotification__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadStartedNotification__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ThreadStartedNotification__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadStartedNotification__SubAgentSource }; -export const V2ThreadStartedNotification__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadStartedNotification__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadStartedNotification__ThreadExtra = {}; -export const V2ThreadStartedNotification__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export const V2ThreadStartedNotification__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadStartedNotification__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadStartedNotification__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", }); -export type V2ThreadStartedNotification__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadStartedNotification__ThreadHistoryMode = Schema.Literals([ - "legacy", - "paginated", -]); - -export type V2ThreadStartedNotification__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadStartedNotification__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadStartedNotification__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadStartedNotification__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadStartedNotification__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export type V2ThreadStartedNotification__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadStartedNotification__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); export type V2ThreadStartParams = { readonly approvalPolicy?: V2ThreadStartParams__AskForApproval | null; readonly approvalsReviewer?: V2ThreadStartParams__ApprovalsReviewer | null; readonly baseInstructions?: string | null; - readonly config?: { readonly [x: string]: unknown } | null; + readonly config?: { readonly [x: string]: Schema.Json } | null; readonly cwd?: string | null; readonly developerInstructions?: string | null; readonly ephemeral?: boolean | null; @@ -42528,14 +56121,21 @@ export const V2ThreadStartParams = Schema.Struct({ ), baseInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), config: Schema.optionalKey( - Schema.Union([Schema.Record(Schema.String, Schema.Unknown), Schema.Null]), + Schema.Union([ + Schema.Record(Schema.String, Schema.Json.annotate({ expected: "JSON value" })), + Schema.Null, + ]), ), cwd: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), developerInstructions: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), ephemeral: Schema.optionalKey(Schema.Union([Schema.Boolean, Schema.Null])), model: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), modelProvider: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - personality: Schema.optionalKey(Schema.Union([V2ThreadStartParams__Personality, Schema.Null])), + personality: Schema.optionalKey( + Schema.Union([V2ThreadStartParams__Personality, Schema.Null]).annotate({ + description: "@deprecated `friendly` and `pragmatic` no longer select a style.", + }), + ), sandbox: Schema.optionalKey(Schema.Union([V2ThreadStartParams__SandboxMode, Schema.Null])), serviceName: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), @@ -42555,34 +56155,11 @@ export const V2ThreadStartParams__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 V2ThreadStartParams__CapabilityRootLocation = { - readonly environmentId: string; - readonly path: string; - readonly type: "environment"; -}; -export const V2ThreadStartParams__CapabilityRootLocation = Schema.Union( - [ - Schema.Struct({ - environmentId: Schema.String, - path: Schema.String.annotate({ - description: "Absolute path for the root in the selected environment.", - }), - type: Schema.Literal("environment").annotate({ - title: "EnvironmentCapabilityRootLocationType", - }), - }).annotate({ - title: "EnvironmentCapabilityRootLocation", - description: "A path owned by an execution environment.", - }), - ], - { mode: "oneOf" }, -).annotate({ description: "Location used to resolve a selected capability root." }); - export type V2ThreadStartParams__DynamicToolSpec = | { readonly deferLoading?: boolean; readonly description: string; - readonly inputSchema: unknown; + readonly inputSchema: Schema.Json; readonly name: string; readonly type: "function"; } @@ -42597,7 +56174,7 @@ export const V2ThreadStartParams__DynamicToolSpec = Schema.Union( Schema.Struct({ deferLoading: Schema.optionalKey(Schema.Boolean), description: Schema.String, - inputSchema: Schema.Unknown, + inputSchema: Schema.Json.annotate({ expected: "JSON value" }), name: Schema.String, type: Schema.Literal("function").annotate({ title: "FunctionDynamicToolSpecType" }), }).annotate({ title: "FunctionDynamicToolSpec" }), @@ -42628,33 +56205,16 @@ export const V2ThreadStartParams__MultiAgentMode = Schema.Union( export type V2ThreadStartParams__SelectedCapabilityRoot = { readonly id: string; - readonly location: { - readonly environmentId: string; - readonly path: string; - readonly type: "environment"; - }; + readonly location: V2ThreadStartParams__CapabilityRootLocation; }; export const V2ThreadStartParams__SelectedCapabilityRoot = Schema.Struct({ id: Schema.String.annotate({ description: "Stable identifier supplied by the capability selection platform.", }), - location: Schema.Union( - [ - Schema.Struct({ - environmentId: Schema.String, - path: Schema.String.annotate({ - description: "Absolute path for the root in the selected environment.", - }), - type: Schema.Literal("environment").annotate({ - title: "EnvironmentCapabilityRootLocationType", - }), - }).annotate({ - title: "EnvironmentCapabilityRootLocation", - description: "A path owned by an execution environment.", - }), - ], - { mode: "oneOf" }, - ).annotate({ description: "Location used to resolve a selected capability root." }), + location: Schema.suspend( + (): Schema.Codec => + V2ThreadStartParams__CapabilityRootLocation, + ).annotate({ description: "Where the selected root can be resolved." }), }).annotate({ description: "A user-selected root that can expose one or more runtime capabilities.", }); @@ -42682,33 +56242,30 @@ export const V2ThreadStartParams__TurnEnvironmentParams = Schema.Struct({ export type V2ThreadStartResponse = { readonly approvalPolicy: V2ThreadStartResponse__AskForApproval; - readonly approvalsReviewer: "user" | "auto_review" | "guardian_subagent"; + readonly approvalsReviewer: V2ThreadStartResponse__ApprovalsReviewer; readonly cwd: V2ThreadStartResponse__AbsolutePathBuf; + readonly disabledPluginIds?: ReadonlyArray; readonly instructionSources?: ReadonlyArray; readonly model: string; readonly modelProvider: string; readonly reasoningEffort?: V2ThreadStartResponse__ReasoningEffort | null; - readonly sandbox: - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; + readonly sandbox: V2ThreadStartResponse__SandboxPolicy; readonly serviceTier?: string | null; readonly thread: V2ThreadStartResponse__Thread; }; export const V2ThreadStartResponse = Schema.Struct({ approvalPolicy: V2ThreadStartResponse__AskForApproval, - approvalsReviewer: Schema.Literals(["user", "auto_review", "guardian_subagent"]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", - }), + approvalsReviewer: Schema.suspend( + (): Schema.Codec => + V2ThreadStartResponse__ApprovalsReviewer, + ).annotate({ description: "Reviewer currently used for approval requests on this thread." }), cwd: V2ThreadStartResponse__AbsolutePathBuf, + disabledPluginIds: Schema.optionalKey( + Schema.Array(Schema.String).annotate({ + description: "Saved list of disabled plugin IDs. Does not yet filter plugin capabilities.", + default: [], + }), + ), instructionSources: Schema.optionalKey( Schema.Array(V2ThreadStartResponse__LegacyAppPathString).annotate({ description: @@ -42721,131 +56278,35 @@ export const V2ThreadStartResponse = Schema.Struct({ reasoningEffort: Schema.optionalKey( Schema.Union([V2ThreadStartResponse__ReasoningEffort, Schema.Null]), ), - sandbox: Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ - title: "WorkspaceWriteSandboxPolicyType", - }), - writableRoots: Schema.optionalKey( - Schema.Array(V2ThreadStartResponse__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, + sandbox: Schema.suspend( + (): Schema.Codec => V2ThreadStartResponse__SandboxPolicy, ).annotate({ - description: - "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", - }), - serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), - thread: V2ThreadStartResponse__Thread, -}).annotate({ title: "ThreadStartResponse" }); - -export type V2ThreadStartResponse__ActivePermissionProfile = { - readonly extends?: string | null; - readonly id: string; -}; -export const V2ThreadStartResponse__ActivePermissionProfile = Schema.Struct({ - extends: Schema.optionalKey( - Schema.Union([ - Schema.String.annotate({ - description: - "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", - }), - Schema.Null, - ]), - ), - id: Schema.String.annotate({ - description: - "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", - }), -}); - -export type V2ThreadStartResponse__ApprovalsReviewer = "user" | "auto_review" | "guardian_subagent"; -export const V2ThreadStartResponse__ApprovalsReviewer = Schema.Literals([ - "user", - "auto_review", - "guardian_subagent", -]).annotate({ - 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`. `auto_review` uses a carefully prompted subagent to gather relevant context and apply a risk-based decision framework before approving or denying the request. The legacy value `guardian_subagent` is accepted for compatibility.", -}); - -export type V2ThreadStartResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ThreadStartResponse__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 V2ThreadStartResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadStartResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadStartResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadStartResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); + description: + "Legacy sandbox policy retained for compatibility. Experimental clients should prefer `activePermissionProfile` for profile provenance.", + }), + serviceTier: Schema.optionalKey(Schema.Union([Schema.String, Schema.Null])), + thread: V2ThreadStartResponse__Thread, +}).annotate({ title: "ThreadStartResponse" }); -export type V2ThreadStartResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadStartResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); +export type V2ThreadStartResponse__ActivePermissionProfile = { + readonly extends?: string | null; + readonly id: string; +}; +export const V2ThreadStartResponse__ActivePermissionProfile = Schema.Struct({ + extends: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Parent profile identifier from the selected permissions profile's `extends` setting, when present.", + }), + Schema.Null, + ]), + ), + id: Schema.String.annotate({ + description: + "Identifier from `default_permissions` or the implicit built-in default, such as `:workspace` or a user-defined `[permissions.]` profile.", + }), +}); export type V2ThreadStartResponse__MultiAgentMode = | "explicitRequestOnly" @@ -42862,112 +56323,24 @@ export const V2ThreadStartResponse__MultiAgentMode = Schema.Union( "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", }); -export type V2ThreadStartResponse__NetworkAccess = "restricted" | "enabled"; -export const V2ThreadStartResponse__NetworkAccess = Schema.Literals(["restricted", "enabled"]); - -export type V2ThreadStartResponse__SandboxPolicy = - | { readonly type: "dangerFullAccess" } - | { readonly networkAccess?: boolean; readonly type: "readOnly" } - | { readonly networkAccess?: "restricted" | "enabled"; readonly type: "externalSandbox" } - | { - readonly excludeSlashTmp?: boolean; - readonly excludeTmpdirEnvVar?: boolean; - readonly networkAccess?: boolean; - readonly type: "workspaceWrite"; - readonly writableRoots?: ReadonlyArray; - }; -export const V2ThreadStartResponse__SandboxPolicy = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("dangerFullAccess").annotate({ - title: "DangerFullAccessSandboxPolicyType", - }), - }).annotate({ title: "DangerFullAccessSandboxPolicy" }), - Schema.Struct({ - 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 })), - type: Schema.Literal("workspaceWrite").annotate({ title: "WorkspaceWriteSandboxPolicyType" }), - writableRoots: Schema.optionalKey( - Schema.Array(V2ThreadStartResponse__AbsolutePathBuf).annotate({ default: [] }), - ), - }).annotate({ title: "WorkspaceWriteSandboxPolicy" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadStartResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadStartResponse__SubAgentSource }; -export const V2ThreadStartResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadStartResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadStartResponse__ThreadExtra = {}; -export const V2ThreadStartResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export type V2ThreadStartResponse__ThreadEnvironment = { + readonly cwd: V2ThreadStartResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadStartResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadStartResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadStartResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", }); -export type V2ThreadStartResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadStartResponse__ThreadHistoryMode = Schema.Literals(["legacy", "paginated"]); - -export type V2ThreadStartResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadStartResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadStartResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadStartResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadStartResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export type V2ThreadStartResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadStartResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); export type V2ThreadStatusChangedNotification = { readonly status: V2ThreadStatusChangedNotification__ThreadStatus; @@ -42989,6 +56362,74 @@ export const V2ThreadTokenUsageUpdatedNotification = Schema.Struct({ turnId: Schema.String, }).annotate({ title: "ThreadTokenUsageUpdatedNotification" }); +export type V2ThreadTurnsListParams = { + readonly cursor?: string | null; + readonly itemsView?: V2ThreadTurnsListParams__TurnItemsView | 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, + ]), + ), + itemsView: Schema.optionalKey( + Schema.Union([V2ThreadTurnsListParams__TurnItemsView, Schema.Null]).annotate({ + description: "How much item detail to include for each returned turn; defaults to summary.", + }), + ), + limit: Schema.optionalKey( + Schema.Union([ + Schema.Number.annotate({ description: "Optional turn page size.", format: "uint32" }) + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ + expected: "a value greater than or equal to 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 V2ThreadUnarchivedNotification = { readonly threadId: string }; export const V2ThreadUnarchivedNotification = Schema.Struct({ threadId: Schema.String }).annotate({ title: "ThreadUnarchivedNotification", @@ -43004,125 +56445,24 @@ export const V2ThreadUnarchiveResponse = Schema.Struct({ thread: V2ThreadUnarchiveResponse__Thread, }).annotate({ title: "ThreadUnarchiveResponse" }); -export type V2ThreadUnarchiveResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2ThreadUnarchiveResponse__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 V2ThreadUnarchiveResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2ThreadUnarchiveResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2ThreadUnarchiveResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2ThreadUnarchiveResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2ThreadUnarchiveResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2ThreadUnarchiveResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2ThreadUnarchiveResponse__SessionSource = - | "cli" - | "vscode" - | "exec" - | "appServer" - | "unknown" - | { readonly custom: string } - | { readonly subAgent: V2ThreadUnarchiveResponse__SubAgentSource }; -export const V2ThreadUnarchiveResponse__SessionSource = Schema.Union( - [ - Schema.Literals(["cli", "vscode", "exec", "appServer", "unknown"]), - Schema.Struct({ custom: Schema.String }).annotate({ title: "CustomSessionSource" }), - Schema.Struct({ subAgent: V2ThreadUnarchiveResponse__SubAgentSource }).annotate({ - title: "SubAgentSessionSource", - }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadUnarchiveResponse__ThreadExtra = {}; -export const V2ThreadUnarchiveResponse__ThreadExtra = Schema.Struct({}).annotate({ - description: "Extra app-server data for a thread.", +export type V2ThreadUnarchiveResponse__ThreadEnvironment = { + readonly cwd: V2ThreadUnarchiveResponse__LegacyAppPathString; + readonly environmentId: string; + readonly runtimeWorkspaceRoots: ReadonlyArray; +}; +export const V2ThreadUnarchiveResponse__ThreadEnvironment = Schema.Struct({ + cwd: V2ThreadUnarchiveResponse__LegacyAppPathString, + environmentId: Schema.String, + runtimeWorkspaceRoots: Schema.Array(V2ThreadUnarchiveResponse__LegacyAppPathString), +}).annotate({ + description: "An environment selected by a loaded thread, independent of connection status.", }); -export type V2ThreadUnarchiveResponse__ThreadHistoryMode = "legacy" | "paginated"; -export const V2ThreadUnarchiveResponse__ThreadHistoryMode = Schema.Literals([ - "legacy", - "paginated", -]); - -export type V2ThreadUnarchiveResponse__ThreadStatus = - | { readonly type: "notLoaded" } - | { readonly type: "idle" } - | { readonly type: "systemError" } - | { - readonly activeFlags: ReadonlyArray; - readonly type: "active"; - }; -export const V2ThreadUnarchiveResponse__ThreadStatus = Schema.Union( - [ - Schema.Struct({ - type: Schema.Literal("notLoaded").annotate({ title: "NotLoadedThreadStatusType" }), - }).annotate({ title: "NotLoadedThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("idle").annotate({ title: "IdleThreadStatusType" }), - }).annotate({ title: "IdleThreadStatus" }), - Schema.Struct({ - type: Schema.Literal("systemError").annotate({ title: "SystemErrorThreadStatusType" }), - }).annotate({ title: "SystemErrorThreadStatus" }), - Schema.Struct({ - activeFlags: Schema.Array(V2ThreadUnarchiveResponse__ThreadActiveFlag), - type: Schema.Literal("active").annotate({ title: "ActiveThreadStatusType" }), - }).annotate({ title: "ActiveThreadStatus" }), - ], - { mode: "oneOf" }, -); - -export type V2ThreadUnarchiveResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2ThreadUnarchiveResponse__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); +export type V2ThreadUnarchiveResponse__ThreadExtra = { readonly [x: string]: Schema.Json }; +export const V2ThreadUnarchiveResponse__ThreadExtra = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ description: "Extra app-server data for a thread." }); export type V2ThreadUnsubscribeParams = { readonly threadId: string }; export const V2ThreadUnsubscribeParams = Schema.Struct({ threadId: Schema.String }).annotate({ @@ -43145,72 +56485,6 @@ export const V2TurnCompletedNotification = Schema.Struct({ turn: V2TurnCompletedNotification__Turn, }).annotate({ title: "TurnCompletedNotification" }); -export type V2TurnCompletedNotification__ByteRange = { - readonly end: number; - readonly start: number; -}; -export const V2TurnCompletedNotification__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 V2TurnCompletedNotification__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2TurnCompletedNotification__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2TurnCompletedNotification__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2TurnCompletedNotification__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2TurnCompletedNotification__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2TurnCompletedNotification__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2TurnCompletedNotification__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2TurnCompletedNotification__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); - export type V2TurnDiffUpdatedNotification = { readonly diff: string; readonly threadId: string; @@ -43232,18 +56506,19 @@ export const V2TurnInterruptParams = Schema.Struct({ turnId: Schema.String, }).annotate({ title: "TurnInterruptParams" }); -export type V2TurnInterruptResponse = {}; -export const V2TurnInterruptResponse = Schema.Struct({}).annotate({ - title: "TurnInterruptResponse", -}); +export type V2TurnInterruptResponse = { readonly [x: string]: Schema.Json }; +export const V2TurnInterruptResponse = Schema.Record( + Schema.String, + Schema.Json.annotate({ expected: "JSON value" }), +).annotate({ title: "TurnInterruptResponse" }); export type V2TurnModerationMetadataNotification = { - readonly metadata: unknown; + readonly metadata: Schema.Json; readonly threadId: string; readonly turnId: string; }; export const V2TurnModerationMetadataNotification = Schema.Struct({ - metadata: Schema.Unknown, + metadata: Schema.Json.annotate({ expected: "JSON value" }), threadId: Schema.String, turnId: Schema.String, }).annotate({ title: "TurnModerationMetadataNotification" }); @@ -43270,83 +56545,24 @@ export const V2TurnStartedNotification = Schema.Struct({ turn: V2TurnStartedNotification__Turn, }).annotate({ title: "TurnStartedNotification" }); -export type V2TurnStartedNotification__ByteRange = { readonly end: number; readonly start: number }; -export const V2TurnStartedNotification__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 V2TurnStartedNotification__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2TurnStartedNotification__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2TurnStartedNotification__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2TurnStartedNotification__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2TurnStartedNotification__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2TurnStartedNotification__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2TurnStartedNotification__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2TurnStartedNotification__TurnItemsView = Schema.Literals([ - "notLoaded", - "summary", - "full", -]); - export type V2TurnStartParams = { readonly approvalPolicy?: V2TurnStartParams__AskForApproval | null; readonly approvalsReviewer?: V2TurnStartParams__ApprovalsReviewer | null; readonly clientUserMessageId?: string | null; readonly cwd?: string | null; + readonly disabledPluginIds?: ReadonlyArray | null; readonly effort?: V2TurnStartParams__ReasoningEffort | null; readonly input: ReadonlyArray; readonly model?: string | null; - readonly outputSchema?: unknown; + readonly outputSchema?: Schema.Json; readonly personality?: V2TurnStartParams__Personality | null; readonly sandboxPolicy?: V2TurnStartParams__SandboxPolicy | null; readonly serviceTier?: string | null; + readonly serviceTierForTurn?: string | null; readonly summary?: V2TurnStartParams__ReasoningSummary | null; readonly threadId: string; + readonly toolOutput?: V2TurnStartParams__TurnToolOutput | null; + readonly turnTrigger?: string | null; }; export const V2TurnStartParams = Schema.Struct({ approvalPolicy: Schema.optionalKey( @@ -43369,6 +56585,15 @@ export const V2TurnStartParams = Schema.Struct({ Schema.Null, ]), ), + disabledPluginIds: Schema.optionalKey( + Schema.Union([ + Schema.Array(Schema.String).annotate({ + description: + "Replace this thread's disabled plugin IDs. Omitted/null preserves the list; [] clears it.", + }), + Schema.Null, + ]), + ), effort: Schema.optionalKey( Schema.Union([V2TurnStartParams__ReasoningEffort, Schema.Null]).annotate({ description: "Override the reasoning effort for this turn and subsequent turns.", @@ -43384,14 +56609,16 @@ export const V2TurnStartParams = Schema.Struct({ ]), ), outputSchema: Schema.optionalKey( - Schema.Unknown.annotate({ + Schema.Json.annotate({ + expected: "JSON value", description: "Optional JSON Schema used to constrain the final assistant message for this turn.", }), ), personality: Schema.optionalKey( Schema.Union([V2TurnStartParams__Personality, Schema.Null]).annotate({ - description: "Override the personality for this turn and subsequent turns.", + description: + "@deprecated `friendly` and `pragmatic` no longer select a style. Changing this does not rewrite the thread's existing instructions.", }), ), sandboxPolicy: Schema.optionalKey( @@ -43407,12 +56634,31 @@ export const V2TurnStartParams = Schema.Struct({ Schema.Null, ]), ), + serviceTierForTurn: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Override the service tier only when this request starts a new turn. Use \"default\" for standard speed. Omitted or null inherits the thread's tier. Does not change the thread's tier or a turn being steered.", + }), + Schema.Null, + ]), + ), summary: Schema.optionalKey( Schema.Union([V2TurnStartParams__ReasoningSummary, Schema.Null]).annotate({ description: "Override the reasoning summary for this turn and subsequent turns.", }), ), threadId: Schema.String, + toolOutput: Schema.optionalKey(Schema.Union([V2TurnStartParams__TurnToolOutput, Schema.Null])), + turnTrigger: Schema.optionalKey( + Schema.Union([ + Schema.String.annotate({ + description: + "Optional source classification for the caller that starts this turn. Ignored when this request steers an already-active turn.", + }), + Schema.Null, + ]), + ), }).annotate({ title: "TurnStartParams" }); export type V2TurnStartParams__AdditionalContextEntry = { @@ -43424,16 +56670,6 @@ export const V2TurnStartParams__AdditionalContextEntry = Schema.Struct({ value: Schema.String, }); -export type V2TurnStartParams__ByteRange = { readonly end: number; readonly start: number }; -export const V2TurnStartParams__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 V2TurnStartParams__CollaborationMode = { readonly mode: V2TurnStartParams__ModeKind; readonly settings: V2TurnStartParams__Settings; @@ -43443,6 +56679,16 @@ export const V2TurnStartParams__CollaborationMode = Schema.Struct({ settings: V2TurnStartParams__Settings, }).annotate({ description: "Collaboration mode for a Codex session." }); +export type V2TurnStartParams__CyberAccessProgram = "standard" | "daybreakBlue" | "daybreakRed"; +export const V2TurnStartParams__CyberAccessProgram = Schema.Literals([ + "standard", + "daybreakBlue", + "daybreakRed", +]).annotate({ + description: + "Requested cyber treatment for a ChatGPT-authenticated Codex turn. Authorization and model-tier restrictions remain server-owned.", +}); + export type V2TurnStartParams__MultiAgentMode = | "explicitRequestOnly" | "proactive" @@ -43458,9 +56704,6 @@ export const V2TurnStartParams__MultiAgentMode = Schema.Union( "Controls the effective multi-agent delegation instructions for a turn. `custom` means the configured mode hint defines the policy instead of a built-in policy.", }); -export type V2TurnStartParams__NetworkAccess = "restricted" | "enabled"; -export const V2TurnStartParams__NetworkAccess = Schema.Literals(["restricted", "enabled"]); - export type V2TurnStartParams__TurnEnvironmentParams = { readonly cwd: V2TurnStartParams__LegacyAppPathString; readonly environmentId: string; @@ -43484,65 +56727,6 @@ export const V2TurnStartResponse = Schema.Struct({ turn: V2TurnStartResponse__Tu title: "TurnStartResponse", }); -export type V2TurnStartResponse__ByteRange = { readonly end: number; readonly start: number }; -export const V2TurnStartResponse__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 V2TurnStartResponse__CollabAgentTool = - | "spawnAgent" - | "sendInput" - | "resumeAgent" - | "wait" - | "closeAgent" - | "sendMessage" - | "followupTask" - | "interruptAgent" - | "listAgents"; -export const V2TurnStartResponse__CollabAgentTool = Schema.Literals([ - "spawnAgent", - "sendInput", - "resumeAgent", - "wait", - "closeAgent", - "sendMessage", - "followupTask", - "interruptAgent", - "listAgents", -]); - -export type V2TurnStartResponse__CollabAgentToolCallStatus = - | "inProgress" - | "completed" - | "failed" - | "interrupted"; -export const V2TurnStartResponse__CollabAgentToolCallStatus = Schema.Literals([ - "inProgress", - "completed", - "failed", - "interrupted", -]); - -export type V2TurnStartResponse__CommandExecutionSource = - | "agent" - | "userShell" - | "unifiedExecStartup" - | "unifiedExecInteraction"; -export const V2TurnStartResponse__CommandExecutionSource = Schema.Literals([ - "agent", - "userShell", - "unifiedExecStartup", - "unifiedExecInteraction", -]); - -export type V2TurnStartResponse__TurnItemsView = "notLoaded" | "summary" | "full"; -export const V2TurnStartResponse__TurnItemsView = Schema.Literals(["notLoaded", "summary", "full"]); - export type V2TurnSteerParams = { readonly clientUserMessageId?: string | null; readonly expectedTurnId: string; @@ -43568,16 +56752,6 @@ export const V2TurnSteerParams__AdditionalContextEntry = Schema.Struct({ value: Schema.String, }); -export type V2TurnSteerParams__ByteRange = { readonly end: number; readonly start: number }; -export const V2TurnSteerParams__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 V2TurnSteerResponse = { readonly turnId: string }; export const V2TurnSteerResponse = Schema.Struct({ turnId: Schema.String }).annotate({ title: "TurnSteerResponse", @@ -43637,8 +56811,10 @@ export type V2WindowsWorldWritableWarningNotification = { }; export const V2WindowsWorldWritableWarningNotification = Schema.Struct({ extraCount: Schema.Number.annotate({ format: "uint" }) - .check(Schema.isInt()) - .check(Schema.isGreaterThanOrEqualTo(0)), + .check(Schema.isInt().annotate({ expected: "an integer" })) + .check( + Schema.isGreaterThanOrEqualTo(0).annotate({ expected: "a value greater than or equal to 0" }), + ), failedScan: Schema.Boolean, samplePaths: Schema.Array(Schema.String), }).annotate({ title: "WindowsWorldWritableWarningNotification" }); diff --git a/packages/effect-codex-app-server/src/_internal/shared.ts b/packages/effect-codex-app-server/src/_internal/shared.ts index 8bcb59467d3d..334dc4a43bd3 100644 --- a/packages/effect-codex-app-server/src/_internal/shared.ts +++ b/packages/effect-codex-app-server/src/_internal/shared.ts @@ -45,7 +45,7 @@ export const encodeOptionalPayload = ( ): Effect.Effect => { if (!schema) { if (payload === undefined) { - return Effect.sync(() => undefined); + return Effect.undefined; } return Effect.fail( CodexError.CodexAppServerRequestError.unexpectedPayload(method, "encode-payload", payload), diff --git a/packages/effect-codex-app-server/src/client.test.ts b/packages/effect-codex-app-server/src/client.test.ts index 3830c5fc5f6f..f7eca6207ca7 100644 --- a/packages/effect-codex-app-server/src/client.test.ts +++ b/packages/effect-codex-app-server/src/client.test.ts @@ -95,6 +95,7 @@ it.layer(NodeServices.layer)("effect-codex-app-server client", (it) => { assert.equal(result.skills.data[0]?.skills.length, 0); assert.deepEqual(yield* Ref.get(userInputRequests), [ { + isBlocking: true, itemId: "item-approval-1", threadId: "thread-1", turnId: "turn-1", diff --git a/packages/effect-codex-app-server/src/client.ts b/packages/effect-codex-app-server/src/client.ts index 78d719626139..d4ff21b8c59e 100644 --- a/packages/effect-codex-app-server/src/client.ts +++ b/packages/effect-codex-app-server/src/client.ts @@ -153,14 +153,12 @@ const make = Effect.fn("effect-codex-app-server/CodexAppServerClient.make")(func Effect.flatMap((decoded) => Effect.forEach(handlers, (handler) => handler(decoded), { discard: true }), ), - Effect.catch(() => Effect.void), + Effect.ignore, ); } return unknownNotificationHandler - ? unknownNotificationHandler(notification.method, notification.params).pipe( - Effect.catch(() => Effect.void), - ) + ? unknownNotificationHandler(notification.method, notification.params).pipe(Effect.ignore) : Effect.void; }; diff --git a/packages/effect-codex-app-server/src/schema.test.ts b/packages/effect-codex-app-server/src/schema.test.ts index 0c9fc3561002..b12b33e02854 100644 --- a/packages/effect-codex-app-server/src/schema.test.ts +++ b/packages/effect-codex-app-server/src/schema.test.ts @@ -6,7 +6,6 @@ import * as CodexSchema from "./schema.ts"; const isGetAccountResponse = Schema.is(CodexSchema.V2GetAccountResponse); const isThreadReadResponse = Schema.is(CodexSchema.V2ThreadReadResponse); const isThreadResumeResponse = Schema.is(CodexSchema.V2ThreadResumeResponse); -const isThreadRollbackResponse = Schema.is(CodexSchema.V2ThreadRollbackResponse); const isThreadForkResponse = Schema.is(CodexSchema.V2ThreadForkResponse); const isTurnCompletedNotification = Schema.is(CodexSchema.V2TurnCompletedNotification); const decodeThreadResumeResponse = Schema.decodeUnknownSync(CodexSchema.V2ThreadResumeResponse); @@ -30,7 +29,7 @@ it("keeps async questions in live notifications and thread history", () => { CodexSchema.V2ThreadReadResponse__ThreadItem, CodexSchema.V2ThreadResumeResponse__ThreadItem, ]) { - assert.deepEqual(Schema.decodeUnknownSync(schema)(item), item); + assert.deepEqual(Schema.decodeSync(schema)(item), item); } }); @@ -76,6 +75,7 @@ it("accepts Codex 0.150 multi-agent values", () => { id: "root-thread", modelProvider: "openai", preview: "", + projectId: null, sessionId: "session-1", source: "cli", status: { type: "idle" }, @@ -112,6 +112,7 @@ it("accepts Codex rate limit errors for thread responses", () => { id: "thread-1", modelProvider: "openai", preview: "", + projectId: null, sessionId: "session-1", source: "cli", status: { type: "idle" }, @@ -141,7 +142,6 @@ it("accepts Codex rate limit errors for thread responses", () => { }), true, ); - assert.equal(isThreadRollbackResponse({ thread: failedThread }), true); }); it("accepts Codex misalignment policy errors for thread responses", () => { @@ -153,6 +153,7 @@ it("accepts Codex misalignment policy errors for thread responses", () => { id: "thread-1", modelProvider: "openai", preview: "", + projectId: null, sessionId: "session-1", source: "cli", status: { type: "idle" }, @@ -180,7 +181,6 @@ it("accepts Codex misalignment policy errors for thread responses", () => { }; assert.equal(isThreadReadResponse({ thread: failedThread }), true); assert.equal(isThreadResumeResponse(resumeLikeResponse), true); - assert.equal(isThreadRollbackResponse({ thread: failedThread }), true); assert.equal(isThreadForkResponse(resumeLikeResponse), true); const decodedResume = decodeThreadResumeResponse(resumeLikeResponse); assert.equal(decodedResume.thread.turns[0]?.error?.codexErrorInfo, "misalignmentPolicyViolation"); diff --git a/packages/effect-codex-app-server/test/fixtures/codex-app-server-mock-peer.ts b/packages/effect-codex-app-server/test/fixtures/codex-app-server-mock-peer.ts index 3f2a213d38c7..59e5eb28faf9 100644 --- a/packages/effect-codex-app-server/test/fixtures/codex-app-server-mock-peer.ts +++ b/packages/effect-codex-app-server/test/fixtures/codex-app-server-mock-peer.ts @@ -84,6 +84,7 @@ const handleMethod = (message: Record) => { case "skills/list": { pendingSkillsListRequestId = message.id as number | string; pendingUserInputRequestId = sendRequest("item/tool/requestUserInput", { + isBlocking: true, itemId: "item-approval-1", threadId: "thread-1", turnId: "turn-1", diff --git a/packages/shared/src/httpReadiness.ts b/packages/shared/src/httpReadiness.ts index 5aad9d488aae..4aeda1c4757a 100644 --- a/packages/shared/src/httpReadiness.ts +++ b/packages/shared/src/httpReadiness.ts @@ -106,17 +106,13 @@ export const waitForHttpReady = Effect.fn("shared.httpReadiness.waitForHttpReady Effect.timeoutOption(Duration.millis(probeTimeoutMs)), Effect.mapError((cause) => fail(cause)), ); - return yield* Option.match(responseOption, { - onSome: Effect.succeed, - onNone: () => - Effect.fail( - fail({ - kind: "probe-timeout", - attempt, - probeTimeoutMs, - }), - ), - }); + return yield* Effect.fromOption(responseOption, () => + fail({ + kind: "probe-timeout", + attempt, + probeTimeoutMs, + }), + ); }).pipe( Effect.mapError((cause) => (isMadeError(cause) ? cause : fail(cause))), Effect.tapError((cause) => diff --git a/packages/shared/src/keybindings.ts b/packages/shared/src/keybindings.ts index 6f1071767ae0..28fa05be85b8 100644 --- a/packages/shared/src/keybindings.ts +++ b/packages/shared/src/keybindings.ts @@ -40,6 +40,7 @@ export const DEFAULT_KEYBINDINGS: ReadonlyArray = [ { key: "mod+k", command: "commandPalette.toggle", when: "!terminalFocus" }, { key: "mod+p", command: "filePicker.toggle", when: "!terminalFocus" }, { key: "mod+shift+f", command: "projectSearch.toggle", when: "!terminalFocus" }, + { key: "mod+u", command: "usage.open", when: "!terminalFocus" }, { key: "mod+alt+a", command: "theme.select", when: "!terminalFocus" }, { key: "mod+alt+shift+a", command: "appearance.cycle", when: "!terminalFocus" }, { key: "mod+alt+shift+t", command: "themeEditor.toggle" }, diff --git a/packages/shared/src/nodeSqliteClient.test.ts b/packages/shared/src/nodeSqliteClient.test.ts index 25b0ba2d38e8..bbdaa4624901 100644 --- a/packages/shared/src/nodeSqliteClient.test.ts +++ b/packages/shared/src/nodeSqliteClient.test.ts @@ -1,6 +1,10 @@ +import * as NodeSqlite from "node:sqlite"; +import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; import * as Layer from "effect/Layer"; +import * as Path from "effect/Path"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import * as SqliteClient from "./nodeSqliteClient.ts"; @@ -8,6 +12,21 @@ import * as SqliteClient from "./nodeSqliteClient.ts"; const layer = it.layer(SqliteClient.layer({ filename: ":memory:" })); layer("NodeSqliteClient", (it) => { + it.effect("retries preparing a query after the missing schema becomes available", () => + Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + const select = sql<{ name: string }>`SELECT name FROM created_after_prepare_failure`; + const error = yield* select.pipe(Effect.flip); + assert.equal(error._tag, "SqlError"); + assert.equal(error.reason.operation, "prepare"); + + yield* sql`CREATE TABLE created_after_prepare_failure(name TEXT NOT NULL)`; + yield* sql`INSERT INTO created_after_prepare_failure VALUES ('recovered')`; + assert.deepEqual(yield* select, [{ name: "recovered" }]); + assert.deepEqual(yield* select.values, [["recovered"]]); + }), + ); + it.effect("runs prepared queries and returns positional values", () => Effect.gen(function* () { const sql = yield* SqlClient.SqlClient; @@ -54,3 +73,32 @@ it.effect("returns a typed failure when the database cannot be opened", () => assert.equal(error.reason.operation, "open"); }), ); + +it.effect( + "recovers a prepared query immediately after an exclusive database lock is released", + () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const directory = yield* fs.makeTempDirectoryScoped({ prefix: "t3-sqlite-prepare-" }); + const filename = path.join(directory, "state.sqlite"); + const blocker = yield* Effect.acquireRelease( + Effect.sync(() => new NodeSqlite.DatabaseSync(filename)), + (database) => Effect.sync(() => database.close()), + ); + yield* Effect.sync(() => { + blocker.exec("CREATE TABLE entries(value TEXT); INSERT INTO entries VALUES ('retained')"); + }); + yield* Effect.gen(function* () { + const sql = yield* SqlClient.SqlClient; + yield* Effect.sync(() => blocker.exec("BEGIN EXCLUSIVE")); + const select = sql`SELECT value FROM entries`; + const error = yield* select.values.pipe(Effect.flip); + assert.equal(error._tag, "SqlError"); + assert.equal(error.reason.operation, "prepare"); + yield* Effect.sync(() => blocker.exec("ROLLBACK")); + assert.deepEqual(yield* select.values, [["retained"]]); + assert.deepEqual(yield* select, [{ value: "retained" }]); + }).pipe(Effect.provide(SqliteClient.layer({ filename }))); + }).pipe(Effect.provide(NodeServices.layer)), +); diff --git a/packages/shared/src/nodeSqliteClient.ts b/packages/shared/src/nodeSqliteClient.ts index 71ba11884626..047de403ed8c 100644 --- a/packages/shared/src/nodeSqliteClient.ts +++ b/packages/shared/src/nodeSqliteClient.ts @@ -9,6 +9,7 @@ import * as NodeSqlite from "node:sqlite"; import * as Cache from "effect/Cache"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; +import * as Exit from "effect/Exit"; import * as Fiber from "effect/Fiber"; import { identity } from "effect/Function"; import * as Layer from "effect/Layer"; @@ -144,10 +145,11 @@ const make = Effect.fn("makeWithDatabase")(function* ( }), }); - const prepareCache = yield* Cache.make({ + const prepareCache = yield* Cache.makeWith(prepare, { capacity: options.prepareCacheSize ?? 200, - timeToLive: options.prepareCacheTTL ?? Duration.minutes(10), - lookup: prepare, + // A transient prepare failure must not outlive the lock or missing schema. + timeToLive: (exit) => + Exit.isSuccess(exit) ? (options.prepareCacheTTL ?? Duration.minutes(10)) : Duration.zero, }); const runStatement = ( diff --git a/packages/shared/src/relayClient.ts b/packages/shared/src/relayClient.ts index b33078fc5548..4d1ce988086d 100644 --- a/packages/shared/src/relayClient.ts +++ b/packages/shared/src/relayClient.ts @@ -331,9 +331,7 @@ export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function for (let attempt = 0; attempt < INSTALL_LOCK_RETRY_COUNT; attempt += 1) { const acquired = yield* fileSystem.writeFileString(lockPath, "", { flag: "wx" }).pipe( Effect.as(true), - Effect.catch((error) => - isAlreadyExists(error) ? Effect.succeed(false) : Effect.fail(error), - ), + Effect.catchIf(isAlreadyExists, () => Effect.succeed(false)), ); if (acquired) return; @@ -445,16 +443,16 @@ export const makeCloudflaredRelayClient = Effect.fn("cloudflared.make")(function }).pipe( Effect.scoped, Effect.ensuring(fileSystem.remove(lockPath, { force: true }).pipe(Effect.ignore)), - Effect.catch((cause) => - cause instanceof RelayClientInstallError - ? Effect.fail(cause) - : Effect.fail( - new RelayClientInstallError({ - reason: "write_failed", - message: "Could not install the relay client.", - cause, - }), - ), + Effect.catchIf( + (cause) => !(cause instanceof RelayClientInstallError), + (cause) => + Effect.fail( + new RelayClientInstallError({ + reason: "write_failed", + message: "Could not install the relay client.", + cause, + }), + ), ), ); }); diff --git a/packages/shared/src/relayJwt.ts b/packages/shared/src/relayJwt.ts index f63f458a2253..cabe340d7009 100644 --- a/packages/shared/src/relayJwt.ts +++ b/packages/shared/src/relayJwt.ts @@ -10,6 +10,7 @@ export const RELAY_HEALTH_REQUEST_TYP = "t3-cloud-health+jwt"; export const RELAY_MINT_RESPONSE_TYP = "t3-env-mint+jwt"; export const RELAY_HEALTH_RESPONSE_TYP = "t3-env-health+jwt"; export const RELAY_ACTIVITY_PUBLISH_TYP = "t3-env-activity+jwt"; +export const RELAY_MANAGED_TUNNEL_RECOVERY_TYP = "t3-env-managed-tunnel-recovery+jwt"; export class RelayJwtError extends Schema.TaggedError()("RelayJwtError", { operation: Schema.Literals(["sign", "verify"]), diff --git a/packages/shared/src/relayTracing.test.ts b/packages/shared/src/relayTracing.test.ts index 3bb7f1ea1ac2..f96dfea8bb3b 100644 --- a/packages/shared/src/relayTracing.test.ts +++ b/packages/shared/src/relayTracing.test.ts @@ -97,6 +97,8 @@ describe("withRelayClientTracing", () => { const payload = new TextDecoder().decode(fetchFn.mock.calls[0]?.[1]?.body as Uint8Array); expect(payload).toContain("relay request failed"); expect(payload).toContain("relay socket closed"); + expect(payload).toContain('"key":"service.name","value":{"stringValue":"relay-test"}'); + expect(payload).toContain('"key":"service.namespace","value":{"stringValue":"t3code"}'); }), ), ); diff --git a/packages/shared/src/relayTracing.ts b/packages/shared/src/relayTracing.ts index 1259984ea3c9..76954558eb07 100644 --- a/packages/shared/src/relayTracing.ts +++ b/packages/shared/src/relayTracing.ts @@ -142,6 +142,7 @@ export function makeRelayClientTracingLayer( serviceName: resource.serviceName, serviceVersion: resource.serviceVersion, attributes: { + "service.namespace": "t3code", "service.runtime": resource.runtime, "service.component": resource.component ?? "relay-client", "t3.client.surface": resource.client, @@ -151,6 +152,6 @@ export function makeRelayClientTracingLayer( return Layer.effect( RelayClientTracer, - Tracer.Tracer.pipe(Effect.map(nonInterferingTracer), Effect.map(Option.some)), + Tracer.Tracer.pipe(Effect.map(nonInterferingTracer), Effect.asSome), ).pipe(Layer.provide(tracerLayer)); } diff --git a/packages/shared/src/schemaJson.ts b/packages/shared/src/schemaJson.ts index 076ff67b62df..c811c1da1081 100644 --- a/packages/shared/src/schemaJson.ts +++ b/packages/shared/src/schemaJson.ts @@ -1,7 +1,6 @@ import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import * as Exit from "effect/Exit"; -import * as Option from "effect/Option"; import * as Result from "effect/Result"; import * as Schema from "effect/Schema"; import * as SchemaGetter from "effect/SchemaGetter"; @@ -187,7 +186,7 @@ const parseLenientJsonGetter = SchemaGetter.onSome((input: string) => { ); return decodeJsonString(stripped).pipe( - Effect.map(Option.some), + Effect.asSome, Effect.mapError((error) => error.issue), ); }); diff --git a/packages/shared/src/usageFormat.test.ts b/packages/shared/src/usageFormat.test.ts index 3e92d0b3cb85..3a47ede3bcc8 100644 --- a/packages/shared/src/usageFormat.test.ts +++ b/packages/shared/src/usageFormat.test.ts @@ -5,10 +5,22 @@ import { enumerateHourStarts, formatDateTimeShort, formatHourShort, + formatPercent, formatRelativeHourShort, makeWindow, } from "./usageFormat.ts"; +describe("formatPercent", () => { + it("distinguishes a small positive share from zero", () => { + expect(formatPercent(0)).toBe("0.0%"); + expect(formatPercent(0.0004)).toBe("<0.1%"); + expect(formatPercent(0.0009)).toBe("<0.1%"); + expect(formatPercent(0.001)).toBe("0.1%"); + expect(formatPercent(0.023)).toBe("2.3%"); + expect(formatPercent(0.00004, 2)).toBe("<0.01%"); + }); +}); + describe("hourly usage formatting", () => { it("keeps requested zones separate when formatting repeated calls", () => { const instant = "2026-08-11T12:37:00.000Z"; diff --git a/packages/shared/src/usageFormat.ts b/packages/shared/src/usageFormat.ts index 442687308323..328e0cf814e7 100644 --- a/packages/shared/src/usageFormat.ts +++ b/packages/shared/src/usageFormat.ts @@ -43,7 +43,10 @@ function trim(value: number): string { } export function formatPercent(share: number, digits = 1): string { - return `${(share * 100).toFixed(digits)}%`; + const percent = share * 100; + const smallest = 10 ** -digits; + if (percent > 0 && percent < smallest) return `<${smallest.toFixed(digits)}%`; + return `${percent.toFixed(digits)}%`; } /** `2026-08-07` to `Aug 7`. */ diff --git a/packages/shared/src/usageLimits.test.ts b/packages/shared/src/usageLimits.test.ts index 0ef53b1e7f55..0646953a5f08 100644 --- a/packages/shared/src/usageLimits.test.ts +++ b/packages/shared/src/usageLimits.test.ts @@ -16,6 +16,7 @@ import { collectLimitAccounts, collectLimitNotices, collectLimitPools, + displayLimitWindows, elapsedShare, formatResetsIn, limitsNotice, @@ -687,6 +688,53 @@ describe("pooled account columns", () => { }); }); +describe("Cursor limit presentation", () => { + const cursorAccount: LimitAccount = { + key: "cursor", + driver: ProviderDriverKind.make("cursor"), + displayName: "Cursor", + email: undefined, + plan: undefined, + accentColor: undefined, + environments: [], + sourceLabel: "Cursor", + redeem: null, + limits: { + checkedAt: "2026-09-03T11:00:00.000Z", + windows: [ + { id: "apiPercentUsed", kind: "monthly", label: "Other Models", usedPercent: 49 }, + { id: "autoPercentUsed", kind: "monthly", label: "Cursor Models", usedPercent: 9 }, + { id: "totalPercentUsed", kind: "monthly", label: "Overall", usedPercent: 15 }, + ], + }, + }; + + it("hides the combined percentage and orders the two pools", () => { + const [pool] = collectLimitPools([cursorAccount], now); + const display = displayLimitWindows(pool!); + expect(display.map((window) => window.id)).toEqual(["autoPercentUsed", "apiPercentUsed"]); + }); + + it("keeps the combined percentage as a card if either allowance is missing", () => { + const [pool] = collectLimitPools( + [ + { + ...cursorAccount, + limits: { + ...cursorAccount.limits, + windows: cursorAccount.limits.windows.filter( + (window) => window.id !== "apiPercentUsed", + ), + }, + }, + ], + now, + ); + const display = displayLimitWindows(pool!); + expect(display.map((window) => window.id)).toEqual(["totalPercentUsed", "autoPercentUsed"]); + }); +}); + describe("collectLimitNotices", () => { const checkedAt = "2026-09-03T11:00:00.000Z"; const claude = ProviderDriverKind.make("claudeAgent"); diff --git a/packages/shared/src/usageLimits.ts b/packages/shared/src/usageLimits.ts index 17ba1feab5d6..f2b5cfe53b84 100644 --- a/packages/shared/src/usageLimits.ts +++ b/packages/shared/src/usageLimits.ts @@ -24,6 +24,33 @@ const MINUTE = 60_000; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; +export const CURSOR_USAGE_WINDOWS = [ + { + id: "totalPercentUsed", + label: "Overall", + description: "Combined usage across both allowances, not a third quota.", + }, + { + id: "autoPercentUsed", + label: "Cursor Models", + description: "Grok and Composer use this first. Auto can use either pool.", + }, + { + id: "apiPercentUsed", + label: "Other Models", + description: "Claude, GPT, and Gemini use this pool. Grok and Composer fall back here.", + }, +] as const; + +export function cursorUsageWindowDetails(id: string) { + return CURSOR_USAGE_WINDOWS.find((window) => window.id === id); +} + +function cursorUsageWindowRank(id: string): number { + const rank = CURSOR_USAGE_WINDOWS.findIndex((window) => window.id === id); + return rank < 0 ? CURSOR_USAGE_WINDOWS.length : rank; +} + /** * Providers that belong on the Limits view: enabled, installed, and one whose * driver reports subscription usage at all. A driver with no notion of usage @@ -282,6 +309,17 @@ export interface LimitPool { readonly windows: readonly LimitPoolWindow[]; } +/** Show Cursor's two usable pools instead of a combined percentage when both are available. */ +export function displayLimitWindows(pool: LimitPool) { + if (pool.driver !== "cursor") return pool.windows; + const hasAuto = pool.windows.some((window) => window.id === "autoPercentUsed"); + const hasApi = pool.windows.some((window) => window.id === "apiPercentUsed"); + const hasBothPools = hasAuto && hasApi; + return pool.windows + .filter((window) => !hasBothPools || window.id !== "totalPercentUsed") + .sort((left, right) => cursorUsageWindowRank(left.id) - cursorUsageWindowRank(right.id)); +} + const WINDOW_KIND_ORDER: Record = { session: 0, weekly: 1, diff --git a/packages/shared/src/usageMerge.test.ts b/packages/shared/src/usageMerge.test.ts index 668d8bcc2723..178282238482 100644 --- a/packages/shared/src/usageMerge.test.ts +++ b/packages/shared/src/usageMerge.test.ts @@ -1,5 +1,6 @@ import { USAGE_CONTRACT_VERSION, + USAGE_MERGE_COMPATIBLE_SINCE, type EnvironmentId, type UsageBucket, type UsageDay, @@ -74,6 +75,41 @@ function environment(id: string, usageSummary: UsageSummary): EnvironmentUsage { } describe("mergeUsage", () => { + it("counts a Cursor account once across servers while retaining each server's other providers", () => { + const account = { + provider: "cursor" as const, + hostId: "cursor.com", + homePath: "cursor-account:account-hash", + volumeId: "account-hash", + }; + const merged = mergeUsage( + [ + environment( + "mac", + summary([bucket({ provider: "cursor", sourcePath: account.homePath })], [account]), + ), + environment( + "linux", + summary( + [ + bucket({ provider: "cursor", sourcePath: account.homePath }), + bucket({ provider: "opencode", sourcePath: "/opencode" }), + ], + [account, { provider: "opencode", hostId: "linux", homePath: "/opencode" }], + ), + ), + ], + USAGE_CONTRACT_VERSION, + ); + expect( + merged.providers.map((provider) => [provider.provider, provider.costUsd]).sort(), + ).toEqual([ + ["cursor", 10], + ["opencode", 10], + ]); + expect(merged.duplicateSources).toHaveLength(1); + }); + it("sums environments that read different transcript directories", () => { const merged = mergeUsage( [ @@ -146,6 +182,31 @@ describe("mergeUsage", () => { ).toEqual({ claude: 1, codex: 1 }); }); + it("counts overlapping provider roots once while keeping each environment's unique root", () => { + const source = (homePath: string) => ({ + provider: "opencode" as const, + hostId: "host", + homePath, + }); + const usage = (sourcePath: string, costUsd: number) => + bucket({ provider: "opencode", sourcePath, costUsd }); + const merged = mergeUsage( + [ + environment( + "env-a", + summary([usage("/shared", 10), usage("/a", 2)], [source("/shared"), source("/a")]), + ), + environment( + "env-b", + summary([usage("/shared", 10), usage("/b", 3)], [source("/shared"), source("/b")]), + ), + ], + USAGE_CONTRACT_VERSION, + ); + expect(merged.costUsd).toBe(15); + expect(merged.sessions).toBe(3); + }); + it("uses the newest scan when environments share the same transcript directory", () => { const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" }; const environments = [ @@ -166,6 +227,104 @@ describe("mergeUsage", () => { } }); + it("prefers a complete scan over a newer partial scan of the same directory", () => { + const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" }; + const incomplete = summary([bucket({ costUsd: 4, records: 2 })], [source]); + const partial = environment("new", { + ...incomplete, + readAt: "2026-08-07T01:00:00.000Z", + sources: incomplete.sources.map((entry) => ({ ...entry, status: "partial" as const })), + }); + const complete = environment("old", summary([bucket()], [source])); + + for (const ordered of [ + [partial, complete], + [complete, partial], + ]) { + const merged = mergeUsage(ordered, USAGE_CONTRACT_VERSION); + expect(merged.costUsd).toBe(10); + expect(merged.contributingEnvironments).toEqual(["old"]); + expect(merged.duplicateSources).toEqual(["new: /home/theo/.claude"]); + } + expect(mergeUsage([partial], USAGE_CONTRACT_VERSION).costUsd).toBe(4); + }); + + it("keeps new cells from a later partial scan without recounting older cells", () => { + const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" }; + const complete = environment( + "old", + summary([bucket()], [source], USAGE_MERGE_COMPATIBLE_SINCE), + ); + const partialSummary = summary( + [ + bucket({ sourcePath: source.homePath, costUsd: 4, records: 2 }), + bucket({ + day: "2026-08-08" as UsageDay, + sourcePath: source.homePath, + costUsd: 3, + records: 1, + }), + ], + [{ ...source, distinctSessions: 2 }], + ); + const partial = environment("new", { + ...partialSummary, + readAt: "2026-08-08T01:00:00.000Z", + sources: partialSummary.sources.map((entry) => ({ ...entry, status: "partial" as const })), + }); + + for (const ordered of [ + [complete, partial], + [partial, complete], + ]) { + const merged = mergeUsage(ordered, USAGE_CONTRACT_VERSION); + expect(merged.costUsd).toBe(13); + expect(merged.records).toBe(6); + expect(merged.sessions).toBe(2); + expect(merged.daily.map(({ day, costUsd }) => [day, costUsd])).toEqual([ + ["2026-08-07", 10], + ["2026-08-08", 3], + ]); + expect(merged.contributingEnvironments).toEqual( + ordered.map(({ environmentId }) => environmentId), + ); + expect(merged.duplicateSources).toEqual(["new: /home/theo/.claude"]); + } + }); + + it("retains a complete cell when a larger partial cell may have skipped old records", () => { + const source = { provider: "claude" as const, hostId: "mac", homePath: "/home/theo/.claude" }; + const complete = environment("old", summary([bucket()], [source])); + const partialSummary = summary( + [ + bucket({ + costUsd: 4, + records: 6, + totals: { + uncachedInputTokens: 80, + cachedInputTokens: 500, + cacheCreationTokens: 10, + outputTokens: 30, + reasoningTokens: 0, + }, + }), + ], + [{ ...source, distinctSessions: 2 }], + ); + const partial = environment("new", { + ...partialSummary, + readAt: "2026-08-07T01:00:00.000Z", + sources: partialSummary.sources.map((entry) => ({ ...entry, status: "partial" as const })), + }); + + const merged = mergeUsage([complete, partial], USAGE_CONTRACT_VERSION); + expect(merged.costUsd).toBe(10); + expect(merged.totalTokens).toBe(1160); + expect(merged.records).toBe(5); + expect(merged.sessions).toBe(1); + expect(merged.contributingEnvironments).toEqual(["old"]); + }); + it("excludes an environment reporting an older contract version", () => { const merged = mergeUsage( [ @@ -178,7 +337,7 @@ describe("mergeUsage", () => { summary( [bucket()], [{ provider: "claude", hostId: "linux", homePath: "/b" }], - USAGE_CONTRACT_VERSION - 2, + USAGE_MERGE_COMPATIBLE_SINCE - 1, ), ), ], diff --git a/packages/shared/src/usageMerge.ts b/packages/shared/src/usageMerge.ts index fee8e9c95666..c4a56829a420 100644 --- a/packages/shared/src/usageMerge.ts +++ b/packages/shared/src/usageMerge.ts @@ -11,6 +11,7 @@ import { type EnvironmentId, type UsageBucket, type UsageProviderKind, + type UsageSource, type UsageSourceFingerprint, type UsageSummary, } from "@t3tools/contracts"; @@ -113,20 +114,46 @@ function fingerprintKey(fingerprint: UsageSourceFingerprint): string { ].join(" "); } +function bucketsForSource(summary: UsageSummary, source: UsageSource): readonly UsageBucket[] { + const providerSources = summary.sources.filter( + (entry) => entry.fingerprint.provider === source.fingerprint.provider, + ); + return summary.buckets.filter( + (bucket) => + bucket.provider === source.fingerprint.provider && + (bucket.sourcePath === source.fingerprint.resolvedHomePath || + (bucket.sourcePath === undefined && providerSources.length === 1)), + ); +} + +function bucketKey(bucket: UsageBucket): string { + return JSON.stringify([bucket.day, bucket.hourStart ?? null, bucket.provider, bucket.model]); +} + /** * Decides which environment owns each physical transcript directory. * * Several environments on one machine (worktree servers, for instance) resolve * the same provider home and would otherwise double count every token. The - * most recently read summary claims a fingerprint; the rest have that provider's - * buckets dropped. Environment ids break ties so the winner is stable when - * summaries have the same read time. + * Complete scans claim a fingerprint ahead of partial scans, then the most + * recently read scan wins within each status. A newer partial scan can still + * contribute cells absent from an older complete scan. Environment ids break + * ties so the result is stable when summaries have the same read time. */ function claimSources(environments: readonly EnvironmentUsage[]): { readonly ownerByFingerprint: ReadonlyMap; + readonly supplementalBucketsByEnvironment: ReadonlyMap>; + readonly sessionsByFingerprint: ReadonlyMap; readonly duplicates: readonly string[]; } { const ownerByFingerprint = new Map(); + const ownerScanByFingerprint = new Map< + string, + { environment: EnvironmentUsage; source: UsageSource } + >(); + const seenBucketKeysByFingerprint = new Map>(); + const supplementalBucketsByEnvironment = new Map>(); + const sessionsByFingerprint = new Map(); const duplicates: string[] = []; const ordered = [...environments].sort( @@ -135,30 +162,82 @@ function claimSources(environments: readonly EnvironmentUsage[]): { a.environmentId.localeCompare(b.environmentId), ); + // A complete scan takes precedence over a newer partial scan of the same + // directory. Partial history still contributes when no complete copy exists. + for (const status of ["ok", "partial", "failed"] as const) { + for (const environment of ordered) { + for (const source of environment.summary.sources) { + if (source.status !== status) continue; + const key = fingerprintKey(source.fingerprint); + if (ownerByFingerprint.has(key)) { + duplicates.push(`${environment.label}: ${source.fingerprint.resolvedHomePath}`); + continue; + } + ownerByFingerprint.set(key, environment.environmentId); + ownerScanByFingerprint.set(key, { environment, source }); + sessionsByFingerprint.set(key, source.distinctSessions); + } + } + } + + // A newer partial scan may contain usage recorded after an older complete + // scan. Keep cells absent from the complete scan. Aggregated cells do not + // reveal enough to reconcile overlapping records without double counting. for (const environment of ordered) { for (const source of environment.summary.sources) { - if (source.status === "missing") continue; + if (source.status !== "partial") continue; const key = fingerprintKey(source.fingerprint); - if (ownerByFingerprint.has(key)) { - duplicates.push(`${environment.label}: ${source.fingerprint.resolvedHomePath}`); + const owner = ownerScanByFingerprint.get(key); + if ( + owner?.source.status !== "ok" || + Date.parse(environment.summary.readAt) <= Date.parse(owner.environment.summary.readAt) + ) { continue; } - ownerByFingerprint.set(key, environment.environmentId); + let seen = seenBucketKeysByFingerprint.get(key); + if (seen === undefined) { + seen = new Set(bucketsForSource(owner.environment.summary, owner.source).map(bucketKey)); + seenBucketKeysByFingerprint.set(key, seen); + } + const supplemental = + supplementalBucketsByEnvironment.get(environment.environmentId) ?? new Set(); + let added = false; + for (const bucket of bucketsForSource(environment.summary, source)) { + const cell = bucketKey(bucket); + if (seen.has(cell)) continue; + seen.add(cell); + supplemental.add(bucket); + added = true; + } + if (!added) continue; + supplementalBucketsByEnvironment.set(environment.environmentId, supplemental); + sessionsByFingerprint.set( + key, + Math.max(sessionsByFingerprint.get(key) ?? 0, source.distinctSessions), + ); } } - return { ownerByFingerprint, duplicates }; + return { + ownerByFingerprint, + supplementalBucketsByEnvironment, + sessionsByFingerprint, + duplicates, + }; } /** Sources this environment owns after fingerprint claims, plus their buckets. */ function ownedContribution( environment: EnvironmentUsage, ownerByFingerprint: ReadonlyMap, + supplementalBuckets: ReadonlySet, + sessionsByFingerprint: ReadonlyMap, ): { readonly buckets: readonly UsageBucket[]; readonly sessionsByProvider: ReadonlyMap; } { const ownedProviders = new Set(); + const ownedSources = new Set(); const sessionsByProvider = new Map(); for (const source of environment.summary.sources) { if (source.status === "missing") continue; @@ -166,16 +245,24 @@ function ownedContribution( if (ownerByFingerprint.get(key) === environment.environmentId) { const provider = source.fingerprint.provider; ownedProviders.add(provider); + ownedSources.add(`${provider}\u0000${source.fingerprint.resolvedHomePath}`); // Distinct within a directory. Summing per-bucket session counts instead // would count a session once per day and model it spans. sessionsByProvider.set( provider, - (sessionsByProvider.get(provider) ?? 0) + source.distinctSessions, + (sessionsByProvider.get(provider) ?? 0) + + (sessionsByFingerprint.get(key) ?? source.distinctSessions), ); } } return { - buckets: environment.summary.buckets.filter((bucket) => ownedProviders.has(bucket.provider)), + buckets: environment.summary.buckets.filter( + (bucket) => + supplementalBuckets.has(bucket) || + (bucket.sourcePath === undefined + ? ownedProviders.has(bucket.provider) + : ownedSources.has(`${bucket.provider}\u0000${bucket.sourcePath}`)), + ), sessionsByProvider, }; } @@ -246,7 +333,12 @@ export function mergeUsage( } } - const { ownerByFingerprint, duplicates } = claimSources(current); + const { + ownerByFingerprint, + supplementalBucketsByEnvironment, + sessionsByFingerprint, + duplicates, + } = claimSources(current); let costUsd = 0; let uncachedInputTokens = 0; @@ -295,7 +387,12 @@ export function mergeUsage( const contributingEnvironments: EnvironmentId[] = []; for (const environment of current) { - const { buckets, sessionsByProvider } = ownedContribution(environment, ownerByFingerprint); + const { buckets, sessionsByProvider } = ownedContribution( + environment, + ownerByFingerprint, + supplementalBucketsByEnvironment.get(environment.environmentId) ?? new Set(), + sessionsByFingerprint, + ); if (buckets.length > 0) contributingEnvironments.push(environment.environmentId); for (const [providerKind, providerSessions] of sessionsByProvider) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1b636cc5c9f..14e163d36ac9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -516,6 +516,9 @@ importers: '@ff-labs/fff-node': specifier: 0.9.4 version: 0.9.4(patch_hash=c4e3cc2420ceb9dc650f9d189e9c24baf7e83f342998bacda5f459a4ce7927a8) + '@napi-rs/keyring': + specifier: ^1.3.0 + version: 1.3.0 '@opencode-ai/sdk': specifier: ^1.3.15 version: 1.15.13 diff --git a/scripts/build-desktop-artifact.test.ts b/scripts/build-desktop-artifact.test.ts index 05fc0baa45a0..4e97301ddba8 100644 --- a/scripts/build-desktop-artifact.test.ts +++ b/scripts/build-desktop-artifact.test.ts @@ -668,6 +668,8 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => { iconSize: 120, iconTextSize: 12, }); + // A Linux AppImage build also emits the .deb from the same run. + assert.deepStrictEqual((linux.linux as Record).target, ["AppImage", "deb"]); // Linux must register the renderer schemes so the generated .desktop // entry advertises MimeType=x-scheme-handler/t3code; for OAuth deep links. assert.deepStrictEqual((linux.linux as Record).protocols, [ diff --git a/scripts/build-desktop-artifact.ts b/scripts/build-desktop-artifact.ts index 02e514d83fbd..d2d9df767946 100644 --- a/scripts/build-desktop-artifact.ts +++ b/scripts/build-desktop-artifact.ts @@ -929,6 +929,7 @@ interface StagePackageJson { readonly private: true; readonly packageManager: string; readonly description: string; + readonly homepage: string; readonly author: string; readonly main: string; readonly build: Record; @@ -2733,10 +2734,17 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( if (platform === "linux") { buildConfig.linux = { - target: [target], + // The .deb is built from the same unpacked app after the AppImage. + // electron-builder lists both in latest-linux.yml and writes + // resources/package-type into the .deb only, so electron-updater updates + // each install in its own format. + target: target === "AppImage" ? [target, "deb"] : [target], executableName: "t3code", icon: "icons", category: "Development", + synopsis: "Desktop GUI for coding agents", + // Required by the .deb control file. + maintainer: "T3 Tools ", // electron-builder turns these into MimeType=x-scheme-handler/; // in the .desktop entry (Exec already gets %U), so browsers can hand // t3code:// OAuth callbacks to the app. @@ -2752,6 +2760,23 @@ export const createBuildConfig = Effect.fn("createBuildConfig")(function* ( }, }, }; + buildConfig.deb = { + // Electron's runtime libraries. Debian 13 and Ubuntu 24.04 renamed some + // for 64-bit time; the old name is the fallback for older releases. + depends: [ + "libasound2t64 | libasound2", + "libatspi2.0-0t64 | libatspi2.0-0", + "libgbm1", + "libgtk-3-0t64 | libgtk-3-0", + "libnotify4", + "libnss3", + "libsecret-1-0", + "libuuid1", + "libxss1", + "libxtst6", + "xdg-utils", + ], + }; } if (platform === "win") { @@ -3642,8 +3667,10 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( private: true, packageManager: rootPackageJson.packageManager, description: "T3 Code desktop build", + // Required by the .deb control file. + homepage: "https://t3.codes", author: "T3 Tools", - main: "apps/desktop/dist-electron/main.cjs", + main: "apps/desktop/dist-electron/boot.cjs", build: yield* createBuildConfig( options.platform, options.target, @@ -3738,6 +3765,11 @@ const buildDesktopArtifact = Effect.fn("buildDesktopArtifact")(function* ( delete buildEnv[key]; } } + if (options.platform === "linux") { + // fpm compresses the .deb with the system xz through tar. Threaded mode + // takes seconds on a many-core runner instead of about two minutes. + buildEnv.XZ_DEFAULTS = "-T0"; + } if (!options.signed) { buildEnv.CSC_IDENTITY_AUTO_DISCOVERY = "false"; delete buildEnv.CSC_LINK; diff --git a/scripts/lib/cli-external-packages.test.ts b/scripts/lib/cli-external-packages.test.ts index ebcc81020bfe..5168e770a114 100644 --- a/scripts/lib/cli-external-packages.test.ts +++ b/scripts/lib/cli-external-packages.test.ts @@ -49,6 +49,7 @@ describe("shouldBundleCliDependency", () => { "ffi-rs", "@yuuang/ffi-rs-win32-x64-msvc", "@ff-labs/fff-node", + "@napi-rs/keyring", "@clerk/electron-passkeys", "node-addon-api", ]) { @@ -82,7 +83,7 @@ describe("selectCliRuntimeExternalDependencies", () => { it("selects every external root declared by the server", () => { assert.deepStrictEqual( Object.keys(selectCliRuntimeExternalDependencies(serverPackageJson.dependencies)).sort(), - ["@ff-labs/fff-node", "node-pty"], + ["@ff-labs/fff-node", "@napi-rs/keyring", "node-pty"], ); }); }); diff --git a/scripts/lib/cli-external-packages.ts b/scripts/lib/cli-external-packages.ts index 5cafee960675..c158ccaff7f4 100644 --- a/scripts/lib/cli-external-packages.ts +++ b/scripts/lib/cli-external-packages.ts @@ -30,6 +30,7 @@ export const CLI_RUNTIME_EXTERNAL_PREFIXES = [ "ffi-rs", "@yuuang/", "@ff-labs/", + "@napi-rs/keyring", "@clerk/electron-passkeys", "node-gyp-build", "node-addon-api", diff --git a/scripts/mobile-showcase.config.ts b/scripts/mobile-showcase.config.ts index c52d1f1dd116..544d8056981e 100644 --- a/scripts/mobile-showcase.config.ts +++ b/scripts/mobile-showcase.config.ts @@ -104,7 +104,9 @@ const config: ShowcaseConfig = { { id: "iphone-6.9", platform: "ios", - simulator: "iPhone 17 Pro Max", + // A disposable device lands on the newest runtime, whose default lock + // screen wallpaper suits both appearances; a stock one may be older. + simulator: "T3 Showcase iPhone 17 Pro Max", simulatorDeviceType: "com.apple.CoreSimulator.SimDeviceType.iPhone-17-Pro-Max", appearance: "dark", theme: DEFAULT_SHOWCASE_THEME, diff --git a/scripts/mobile-showcase.ts b/scripts/mobile-showcase.ts index 1fa8e3009f4e..2b141e12164f 100644 --- a/scripts/mobile-showcase.ts +++ b/scripts/mobile-showcase.ts @@ -838,6 +838,20 @@ async function suppressIosSystemFollowUps(udid: string): Promise { async function normalizeIosSimulator(appearance: ShowcaseAppearance, udid: string): Promise { await runCommand("xcrun", ["simctl", "ui", udid, "appearance", appearance]); + // Always-on displays (Pro Max) dim a locked screen instead of turning it + // off, which the lock-screen wake cannot tell from a lit one. Without it the + // locked display goes dark and the wake lights it fully. + await runCommand("xcrun", [ + "simctl", + "spawn", + udid, + "defaults", + "write", + "com.apple.springboard", + "SBEnableAlwaysOn", + "-bool", + "false", + ]); await runCommand("xcrun", [ "simctl", "status_bar", diff --git a/scripts/setup-worktree.ts b/scripts/setup-worktree.ts new file mode 100644 index 000000000000..ea07a8b65ad8 --- /dev/null +++ b/scripts/setup-worktree.ts @@ -0,0 +1,45 @@ +// @effect-diagnostics nodeBuiltinImport:off - runs before `vp i`, so only Node built-ins exist. +/** + * Worktree setup, run by the t3.json "Setup Worktree" action as + * `node scripts/setup-worktree.ts`. Plain Node keeps one command working in + * every shell T3 Code spawns (zsh, bash, fish, PowerShell): it installs + * dependencies, links the main checkout's gitignored env files into this + * worktree, then warms the web dependency cache. + */ +import * as NodeChildProcess from "node:child_process"; +import * as NodeFS from "node:fs"; +import * as NodePath from "node:path"; + +const ENV_FILES = [".env", NodePath.join("infra", "relay", ".env")]; + +const projectRoot = process.env.T3CODE_PROJECT_ROOT; +if (!projectRoot) { + throw new Error("T3CODE_PROJECT_ROOT is not set. Run this through the t3.json setup action."); +} +const worktree = NodePath.dirname(import.meta.dirname); + +// `shell` resolves `vp` through PATH, including Windows command shims. +const install = NodeChildProcess.spawnSync("vp i", { + cwd: worktree, + shell: true, + stdio: "inherit", +}); +if (install.status !== 0) process.exit(install.status ?? 1); + +// In the main checkout itself, relinking would replace the real env files. +if (NodeFS.realpathSync(projectRoot) !== NodeFS.realpathSync(worktree)) { + for (const file of ENV_FILES) { + const source = NodePath.join(projectRoot, file); + if (!NodeFS.existsSync(source)) continue; + const target = NodePath.join(worktree, file); + NodeFS.rmSync(target, { force: true }); + NodeFS.symlinkSync(source, target); + } +} + +const warm = NodeChildProcess.spawnSync( + process.execPath, + [NodePath.join(worktree, "apps", "web", "scripts", "warm-dep-cache.ts")], + { cwd: worktree, stdio: "inherit" }, +); +process.exit(warm.status ?? 1); diff --git a/t3.json b/t3.json index 284d416a6820..d2c55e050776 100644 --- a/t3.json +++ b/t3.json @@ -4,13 +4,7 @@ "scripts": [ { "name": "Setup Worktree", - "command": "vp i && ln -sf $T3CODE_PROJECT_ROOT/.env .env && ln -sf $T3CODE_PROJECT_ROOT/infra/relay/.env infra/relay/.env && node apps/web/scripts/warm-dep-cache.ts", - "icon": "configure", - "runOnWorktreeCreate": true - }, - { - "name": "Setup Worktree (Windows)", - "command": "vp i && New-Item -ItemType SymbolicLink -Path .env -Target \"$env:T3CODE_PROJECT_ROOT\\.env\" -Force && New-Item -ItemType SymbolicLink -Path \"infra\\relay\\.env\" -Target \"$env:T3CODE_PROJECT_ROOT\\infra\\relay\\.env\" -Force && node apps\\web\\scripts\\warm-dep-cache.ts", + "command": "node scripts/setup-worktree.ts", "icon": "configure", "runOnWorktreeCreate": true } diff --git a/tsconfig.base.json b/tsconfig.base.json index a84f620ba861..7897a92f557f 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -50,7 +50,8 @@ "globalTimers": "error", "globalTimersInEffect": "error", "globalFetch": "error", - "globalFetchInEffect": "error" + "globalFetchInEffect": "error", + "schemaNumber": "off" } } ]