From 586f4463d224620298da77ce36b6bd0e30a658e7 Mon Sep 17 00:00:00 2001 From: shuv Date: Tue, 14 Jul 2026 16:18:39 -0700 Subject: [PATCH 1/7] feat: add mobile server pairing --- bun.lock | 1 + packages/cli/src/commands/commands.ts | 15 +- .../cli/src/commands/handlers/device/list.ts | 30 + .../src/commands/handlers/device/revoke.ts | 21 + packages/cli/src/commands/handlers/pair.ts | 17 +- packages/cli/src/index.ts | 4 + packages/cli/src/server-process.ts | 1 + packages/cli/src/services/service-config.ts | 23 +- packages/cli/test/service.test.ts | 6 +- packages/client/src/effect/api/api.ts | 1252 +-- .../client/src/effect/generated/client.ts | 1193 +-- .../client/src/promise/generated/client.ts | 256 +- .../client/src/promise/generated/types.ts | 91 +- packages/client/test/promise.test.ts | 5 +- packages/core/schema.json | 139 +- packages/core/src/database/migration.gen.ts | 1 + .../20260714225613_mobile_pairing.ts | 29 + packages/core/src/database/schema.gen.ts | 19 + packages/core/src/pairing.ts | 195 + packages/core/src/pairing/sql.ts | 21 + packages/core/test/pairing.test.ts | 239 + packages/docs/openapi.json | 8063 +++++++---------- packages/protocol/src/api.ts | 7 +- packages/protocol/src/capabilities.ts | 90 + packages/protocol/src/client.ts | 3 +- packages/protocol/src/errors.ts | 18 + packages/protocol/src/groups/pairing.ts | 43 + .../protocol/src/middleware/authorization.ts | 18 +- packages/protocol/test/capabilities.test.ts | 22 + packages/schema/src/index.ts | 1 + packages/schema/src/pairing.ts | 75 + packages/server/package.json | 1 + packages/server/src/handlers.ts | 2 + packages/server/src/handlers/pairing.ts | 42 + .../server/src/middleware/authorization.ts | 39 +- packages/server/src/process.ts | 3 + packages/server/src/routes.ts | 11 +- packages/server/src/server-info.ts | 5 + packages/server/test/pairing.test.ts | 100 + packages/server/test/server-info.test.ts | 23 + packages/tui/src/app.tsx | 57 +- packages/tui/src/component/dialog-pair.tsx | 98 +- 42 files changed, 5928 insertions(+), 6351 deletions(-) create mode 100644 packages/cli/src/commands/handlers/device/list.ts create mode 100644 packages/cli/src/commands/handlers/device/revoke.ts create mode 100644 packages/core/src/database/migration/20260714225613_mobile_pairing.ts create mode 100644 packages/core/src/pairing.ts create mode 100644 packages/core/src/pairing/sql.ts create mode 100644 packages/core/test/pairing.test.ts create mode 100644 packages/protocol/src/capabilities.ts create mode 100644 packages/protocol/src/groups/pairing.ts create mode 100644 packages/protocol/test/capabilities.test.ts create mode 100644 packages/schema/src/pairing.ts create mode 100644 packages/server/src/handlers/pairing.ts create mode 100644 packages/server/test/pairing.test.ts create mode 100644 packages/server/test/server-info.test.ts diff --git a/bun.lock b/bun.lock index ce513e77fbed..56b237612819 100644 --- a/bun.lock +++ b/bun.lock @@ -822,6 +822,7 @@ "@effect/platform-node": "catalog:", "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/simulation": "workspace:*", "drizzle-orm": "catalog:", "effect": "catalog:", diff --git a/packages/cli/src/commands/commands.ts b/packages/cli/src/commands/commands.ts index c8092343ab29..e2aa7e147b08 100644 --- a/packages/cli/src/commands/commands.ts +++ b/packages/cli/src/commands/commands.ts @@ -178,10 +178,7 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO Flag.atMost(100), ), title: Flag.string("title").pipe(Flag.withDescription("Session title"), Flag.optional), - thinking: Flag.boolean("thinking").pipe( - Flag.withDescription("Show thinking blocks"), - Flag.withDefault(false), - ), + thinking: Flag.boolean("thinking").pipe(Flag.withDescription("Show thinking blocks"), Flag.withDefault(false)), auto: Flag.boolean("auto").pipe( Flag.withDescription("Auto-approve permissions that are not explicitly denied"), Flag.withDefault(false), @@ -211,6 +208,16 @@ export const Commands = Spec.make(typeof OPENCODE_CLI_NAME === "string" ? OPENCO ], }), Spec.make("pair", { description: "Show server pairing information" }), + Spec.make("device", { + description: "Manage paired mobile devices", + commands: [ + Spec.make("list", { description: "List paired devices" }), + Spec.make("revoke", { + description: "Revoke a paired device", + params: { deviceID: Argument.string("deviceID").pipe(Argument.withDescription("Paired device ID")) }, + }), + ], + }), Spec.make("serve", { description: "Start the v2 API server", params: { diff --git a/packages/cli/src/commands/handlers/device/list.ts b/packages/cli/src/commands/handlers/device/list.ts new file mode 100644 index 000000000000..b3c7471e12c0 --- /dev/null +++ b/packages/cli/src/commands/handlers/device/list.ts @@ -0,0 +1,30 @@ +import { EOL } from "os" +import { OpenCode } from "@opencode-ai/client/promise" +import { Service } from "@opencode-ai/client/effect" +import { Effect } from "effect" +import { Commands } from "../../commands" +import { Runtime } from "../../../framework/runtime" +import { ServiceConfig } from "../../../services/service-config" + +export default Runtime.handler( + Commands.commands.device.commands.list, + Effect.fn("cli.device.list")(function* () { + const endpoint = yield* Service.start(yield* ServiceConfig.options()) + const devices = yield* Effect.tryPromise(() => + OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) }).pairing.device.list(), + ) + if (devices.length === 0) { + process.stdout.write("No paired devices" + EOL) + return + } + const width = Math.max(...devices.map((device) => device.deviceID.length)) + process.stdout.write( + devices + .map( + (device) => + `${device.deviceID.padEnd(width)} ${device.name} ${device.revokedAt ? `revoked ${device.revokedAt}` : "active"}`, + ) + .join(EOL) + EOL, + ) + }), +) diff --git a/packages/cli/src/commands/handlers/device/revoke.ts b/packages/cli/src/commands/handlers/device/revoke.ts new file mode 100644 index 000000000000..5f463284387d --- /dev/null +++ b/packages/cli/src/commands/handlers/device/revoke.ts @@ -0,0 +1,21 @@ +import { EOL } from "os" +import { OpenCode } from "@opencode-ai/client/promise" +import { Service } from "@opencode-ai/client/effect" +import { Effect } from "effect" +import { Pairing } from "@opencode-ai/schema/pairing" +import { Commands } from "../../commands" +import { Runtime } from "../../../framework/runtime" +import { ServiceConfig } from "../../../services/service-config" + +export default Runtime.handler( + Commands.commands.device.commands.revoke, + Effect.fn("cli.device.revoke")(function* (input) { + const endpoint = yield* Service.start(yield* ServiceConfig.options()) + yield* Effect.tryPromise(() => + OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) }).pairing.device.revoke({ + deviceID: Pairing.DeviceID.make(input.deviceID), + }), + ) + process.stdout.write(`Revoked ${input.deviceID}${EOL}`) + }), +) diff --git a/packages/cli/src/commands/handlers/pair.ts b/packages/cli/src/commands/handlers/pair.ts index 5c40a2044431..96ef11b9ca3c 100644 --- a/packages/cli/src/commands/handlers/pair.ts +++ b/packages/cli/src/commands/handlers/pair.ts @@ -11,22 +11,19 @@ export default Runtime.handler( Commands.commands.pair, Effect.fn("cli.pair")(function* () { const endpoint = yield* Service.start(yield* ServiceConfig.options()) - const password = yield* ServiceConfig.password() - const server = yield* Effect.tryPromise(() => - OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) }).server.get(), + const invitation = yield* Effect.tryPromise(() => + OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) }).pairing.invitation.create(), ) - const info = { urls: server.urls, username: "opencode", password } process.stdout.write( [ "", - ` URLs ${info.urls[0] ?? "(none)"}`, - ...info.urls.slice(1).map((url) => ` ${url}`), - ` Username ${info.username}`, - ` Password ${info.password}`, + ` URLs ${invitation.urls[0] ?? "(none)"}`, + ...invitation.urls.slice(1).map((url) => ` ${url}`), + ` Expires ${invitation.expiresAt}`, "", " Scan to pair", "", - renderUnicodeCompact(JSON.stringify(info), { border: 2 }) + renderUnicodeCompact(JSON.stringify(invitation), { border: 2 }) .split(EOL) .map((line) => " " + line) .join(EOL), @@ -37,7 +34,7 @@ export default Runtime.handler( const hostname = new URL(endpoint.url).hostname if (!["localhost", "127.0.0.1", "[::1]"].includes(hostname)) return process.stderr.write( - ` Run \`opencode service set hostname 0.0.0.0\` to access the service remotely.${EOL}${EOL}`, + ` The service is bound to loopback. Configure \`shuvcode service set advertised-urls https://host\` when using Tailscale Serve or a reverse proxy.${EOL}${EOL}`, ) }), ) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 2128495526ea..646e60246438 100755 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -32,6 +32,10 @@ const Handlers = Runtime.handlers(Commands, { mini: () => import("./commands/handlers/mini"), run: () => import("./commands/handlers/run"), pair: () => import("./commands/handlers/pair"), + device: { + list: () => import("./commands/handlers/device/list"), + revoke: () => import("./commands/handlers/device/revoke"), + }, service: { start: () => import("./commands/handlers/service/start"), restart: () => import("./commands/handlers/service/restart"), diff --git a/packages/cli/src/server-process.ts b/packages/cli/src/server-process.ts index 9f688fb16efd..306b7aa7eed9 100644 --- a/packages/cli/src/server-process.ts +++ b/packages/cli/src/server-process.ts @@ -66,6 +66,7 @@ const processEffect = Effect.fnUntraced(function* (options: Options) { port: Option.fromNullishOr(options.port ?? config.port), password, restartContinuity: options.mode === "service", + advertisedURLs: config.advertisedUrls, }).pipe(Effect.provide(Logger.layer([], { mergeWithExisting: false }))) if (lockScope !== undefined) { yield* register(address, password) diff --git a/packages/cli/src/services/service-config.ts b/packages/cli/src/services/service-config.ts index ecdd5bf19a0c..3efc38dcaf65 100644 --- a/packages/cli/src/services/service-config.ts +++ b/packages/cli/src/services/service-config.ts @@ -3,6 +3,7 @@ import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/inst import { Service } from "@opencode-ai/client/effect" import { Effect, FileSystem, Schema } from "effect" import { randomBytes } from "crypto" +import { Pairing } from "@opencode-ai/schema/pairing" import path from "path" // The CLI's service configuration file, plus the Service.Options binding that @@ -13,10 +14,11 @@ export const Info = Schema.Struct({ hostname: Schema.optional(Schema.String), port: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(1), Schema.isLessThanOrEqualTo(65_535))), password: Schema.optional(Schema.String), + advertisedUrls: Schema.optional(Schema.Array(Schema.String)), }) export type Info = typeof Info.Type -const keys = ["hostname", "port", "password"] as const +const keys = ["hostname", "port", "password", "advertised-urls"] as const type Key = (typeof keys)[number] const decodeInfo = Schema.decodeUnknownEffect(Schema.fromJsonString(Info)) @@ -92,6 +94,9 @@ export const get = Effect.fn("cli.service-config.get")(function* (key?: string) case "password": { return yield* password() } + case "advertised-urls": { + return ((yield* read()).advertisedUrls ?? []).join(",") + } } }) @@ -114,6 +119,16 @@ export const set = Effect.fn("cli.service-config.set")(function* (key: string, v yield* password(value) return } + case "advertised-urls": { + const advertisedUrls = value + .split(",") + .map((item) => item.trim()) + .filter((item) => item.length > 0) + Pairing.advertisedURLs(advertisedUrls) + yield* Service.stop(yield* options()) + yield* write({ ...(yield* read()), advertisedUrls }) + return + } } }) @@ -137,6 +152,12 @@ export const unset = Effect.fn("cli.service-config.unset")(function* (key: strin yield* write(next) return } + case "advertised-urls": { + yield* Service.stop(yield* options()) + const { advertisedUrls: _advertisedUrls, ...next } = yield* read() + yield* write(next) + return + } } }) diff --git a/packages/cli/test/service.test.ts b/packages/cli/test/service.test.ts index a793ce025744..943e90620409 100644 --- a/packages/cli/test/service.test.ts +++ b/packages/cli/test/service.test.ts @@ -21,13 +21,17 @@ test("local channel stores service config with the local service filename", asyn const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-service-")) try { await Effect.runPromise( - ServiceConfig.set("hostname", "127.0.0.2").pipe( + Effect.gen(function* () { + yield* ServiceConfig.set("hostname", "127.0.0.2") + yield* ServiceConfig.set("advertised-urls", "https://shuvdev.example:10001,http://127.0.0.1:4096") + }).pipe( Effect.provide(Global.layerWith({ config: path.join(root, "config"), state: path.join(root, "state") })), Effect.provide(NodeFileSystem.layer), ), ) expect(await Bun.file(path.join(root, "config", "service-local.json")).json()).toEqual({ hostname: "127.0.0.2", + advertisedUrls: ["https://shuvdev.example:10001", "http://127.0.0.1:4096"], }) expect(await Bun.file(path.join(root, "config", "service.json")).exists()).toBe(false) } finally { diff --git a/packages/client/src/effect/api/api.ts b/packages/client/src/effect/api/api.ts index 28c06cbf04ff..f3665ae504c6 100644 --- a/packages/client/src/effect/api/api.ts +++ b/packages/client/src/effect/api/api.ts @@ -21,274 +21,301 @@ export interface ServerApi { readonly get: ServerGetOperation } -type Endpoint2_0Request = Parameters[0] -export type Endpoint2_0Input = { readonly location?: Endpoint2_0Request["query"]["location"] } -export type Endpoint2_0Output = EffectValue> -export type LocationGetOperation = (input?: Endpoint2_0Input) => Effect.Effect +export type Endpoint2_0Output = EffectValue> +export type PairingInvitationCreateOperation = () => Effect.Effect + +type Endpoint2_1Request = Parameters[0] +export type Endpoint2_1Input = { + readonly token: Endpoint2_1Request["payload"]["token"] + readonly requestID: Endpoint2_1Request["payload"]["requestID"] + readonly deviceName: Endpoint2_1Request["payload"]["deviceName"] + readonly credential: Endpoint2_1Request["payload"]["credential"] +} +export type Endpoint2_1Output = EffectValue> +export type PairingRedeemOperation = (input: Endpoint2_1Input) => Effect.Effect + +export type Endpoint2_2Output = EffectValue> +export type PairingDeviceListOperation = () => Effect.Effect + +type Endpoint2_3Request = Parameters[0] +export type Endpoint2_3Input = { readonly deviceID: Endpoint2_3Request["params"]["deviceID"] } +export type Endpoint2_3Output = EffectValue> +export type PairingDeviceRevokeOperation = (input: Endpoint2_3Input) => Effect.Effect + +export interface PairingApi { + readonly invitation: { readonly create: PairingInvitationCreateOperation } + readonly redeem: PairingRedeemOperation + readonly device: { readonly list: PairingDeviceListOperation; readonly revoke: PairingDeviceRevokeOperation } +} + +type Endpoint3_0Request = Parameters[0] +export type Endpoint3_0Input = { readonly location?: Endpoint3_0Request["query"]["location"] } +export type Endpoint3_0Output = EffectValue> +export type LocationGetOperation = (input?: Endpoint3_0Input) => Effect.Effect export interface LocationApi { readonly get: LocationGetOperation } -type Endpoint3_0Request = Parameters[0] -export type Endpoint3_0Input = { readonly location?: Endpoint3_0Request["query"]["location"] } -export type Endpoint3_0Output = EffectValue> -export type AgentListOperation = (input?: Endpoint3_0Input) => Effect.Effect +type Endpoint4_0Request = Parameters[0] +export type Endpoint4_0Input = { readonly location?: Endpoint4_0Request["query"]["location"] } +export type Endpoint4_0Output = EffectValue> +export type AgentListOperation = (input?: Endpoint4_0Input) => Effect.Effect export interface AgentApi { readonly list: AgentListOperation } -type Endpoint4_0Request = Parameters[0] -export type Endpoint4_0Input = { readonly location?: Endpoint4_0Request["query"]["location"] } -export type Endpoint4_0Output = EffectValue> -export type PluginListOperation = (input?: Endpoint4_0Input) => Effect.Effect +type Endpoint5_0Request = Parameters[0] +export type Endpoint5_0Input = { readonly location?: Endpoint5_0Request["query"]["location"] } +export type Endpoint5_0Output = EffectValue> +export type PluginListOperation = (input?: Endpoint5_0Input) => Effect.Effect export interface PluginApi { readonly list: PluginListOperation } -type Endpoint5_0Request = Parameters[0] -export type Endpoint5_0Input = { - readonly workspace?: Endpoint5_0Request["query"]["workspace"] - readonly limit?: Endpoint5_0Request["query"]["limit"] - readonly order?: Endpoint5_0Request["query"]["order"] - readonly search?: Endpoint5_0Request["query"]["search"] - readonly parentID?: Endpoint5_0Request["query"]["parentID"] - readonly directory?: Endpoint5_0Request["query"]["directory"] - readonly project?: Endpoint5_0Request["query"]["project"] - readonly subpath?: Endpoint5_0Request["query"]["subpath"] - readonly cursor?: Endpoint5_0Request["query"]["cursor"] -} -export type Endpoint5_0Output = EffectValue> -export type SessionListOperation = (input?: Endpoint5_0Input) => Effect.Effect - -type Endpoint5_1Request = Parameters[0] -export type Endpoint5_1Input = { - readonly id?: Endpoint5_1Request["payload"]["id"] - readonly agent?: Endpoint5_1Request["payload"]["agent"] - readonly model?: Endpoint5_1Request["payload"]["model"] - readonly location?: Endpoint5_1Request["payload"]["location"] -} -export type Endpoint5_1Output = EffectValue>["data"] -export type SessionCreateOperation = (input?: Endpoint5_1Input) => Effect.Effect - -export type Endpoint5_2Output = EffectValue>["data"] -export type SessionActiveOperation = () => Effect.Effect - -type Endpoint5_3Request = Parameters[0] -export type Endpoint5_3Input = { readonly sessionID: Endpoint5_3Request["params"]["sessionID"] } -export type Endpoint5_3Output = EffectValue>["data"] -export type SessionGetOperation = (input: Endpoint5_3Input) => Effect.Effect - -type Endpoint5_4Request = Parameters[0] -export type Endpoint5_4Input = { readonly sessionID: Endpoint5_4Request["params"]["sessionID"] } -export type Endpoint5_4Output = EffectValue> -export type SessionRemoveOperation = (input: Endpoint5_4Input) => Effect.Effect - -type Endpoint5_5Request = Parameters[0] -export type Endpoint5_5Input = { - readonly sessionID: Endpoint5_5Request["params"]["sessionID"] - readonly messageID?: Endpoint5_5Request["payload"]["messageID"] -} -export type Endpoint5_5Output = EffectValue>["data"] -export type SessionForkOperation = (input: Endpoint5_5Input) => Effect.Effect - -type Endpoint5_6Request = Parameters[0] -export type Endpoint5_6Input = { - readonly sessionID: Endpoint5_6Request["params"]["sessionID"] - readonly agent: Endpoint5_6Request["payload"]["agent"] -} -export type Endpoint5_6Output = EffectValue> -export type SessionSwitchAgentOperation = (input: Endpoint5_6Input) => Effect.Effect - -type Endpoint5_7Request = Parameters[0] -export type Endpoint5_7Input = { - readonly sessionID: Endpoint5_7Request["params"]["sessionID"] - readonly model: Endpoint5_7Request["payload"]["model"] -} -export type Endpoint5_7Output = EffectValue> -export type SessionSwitchModelOperation = (input: Endpoint5_7Input) => Effect.Effect - -type Endpoint5_8Request = Parameters[0] -export type Endpoint5_8Input = { - readonly sessionID: Endpoint5_8Request["params"]["sessionID"] - readonly title: Endpoint5_8Request["payload"]["title"] -} -export type Endpoint5_8Output = EffectValue> -export type SessionRenameOperation = (input: Endpoint5_8Input) => Effect.Effect - -type Endpoint5_9Request = Parameters[0] -export type Endpoint5_9Input = { - readonly sessionID: Endpoint5_9Request["params"]["sessionID"] - readonly destination: Endpoint5_9Request["payload"]["destination"] - readonly moveChanges?: Endpoint5_9Request["payload"]["moveChanges"] -} -export type Endpoint5_9Output = EffectValue> -export type SessionMoveOperation = (input: Endpoint5_9Input) => Effect.Effect - -type Endpoint5_10Request = Parameters[0] -export type Endpoint5_10Input = { - readonly sessionID: Endpoint5_10Request["params"]["sessionID"] - readonly id?: Endpoint5_10Request["payload"]["id"] - readonly text: Endpoint5_10Request["payload"]["text"] - readonly files?: Endpoint5_10Request["payload"]["files"] - readonly agents?: Endpoint5_10Request["payload"]["agents"] - readonly metadata?: Endpoint5_10Request["payload"]["metadata"] - readonly delivery?: Endpoint5_10Request["payload"]["delivery"] - readonly resume?: Endpoint5_10Request["payload"]["resume"] -} -export type Endpoint5_10Output = EffectValue>["data"] -export type SessionPromptOperation = (input: Endpoint5_10Input) => Effect.Effect - -type Endpoint5_11Request = Parameters[0] -export type Endpoint5_11Input = { - readonly sessionID: Endpoint5_11Request["params"]["sessionID"] - readonly id?: Endpoint5_11Request["payload"]["id"] - readonly command: Endpoint5_11Request["payload"]["command"] - readonly arguments?: Endpoint5_11Request["payload"]["arguments"] - readonly agent?: Endpoint5_11Request["payload"]["agent"] - readonly model?: Endpoint5_11Request["payload"]["model"] - readonly files?: Endpoint5_11Request["payload"]["files"] - readonly agents?: Endpoint5_11Request["payload"]["agents"] - readonly delivery?: Endpoint5_11Request["payload"]["delivery"] - readonly resume?: Endpoint5_11Request["payload"]["resume"] -} -export type Endpoint5_11Output = EffectValue>["data"] -export type SessionCommandOperation = (input: Endpoint5_11Input) => Effect.Effect - -type Endpoint5_12Request = Parameters[0] -export type Endpoint5_12Input = { - readonly sessionID: Endpoint5_12Request["params"]["sessionID"] - readonly id?: Endpoint5_12Request["payload"]["id"] - readonly skill: Endpoint5_12Request["payload"]["skill"] - readonly resume?: Endpoint5_12Request["payload"]["resume"] -} -export type Endpoint5_12Output = EffectValue> -export type SessionSkillOperation = (input: Endpoint5_12Input) => Effect.Effect - -type Endpoint5_13Request = Parameters[0] -export type Endpoint5_13Input = { - readonly sessionID: Endpoint5_13Request["params"]["sessionID"] - readonly id?: Endpoint5_13Request["payload"]["id"] - readonly text: Endpoint5_13Request["payload"]["text"] - readonly description?: Endpoint5_13Request["payload"]["description"] - readonly metadata?: Endpoint5_13Request["payload"]["metadata"] - readonly delivery?: Endpoint5_13Request["payload"]["delivery"] - readonly resume?: Endpoint5_13Request["payload"]["resume"] -} -export type Endpoint5_13Output = EffectValue>["data"] -export type SessionSyntheticOperation = (input: Endpoint5_13Input) => Effect.Effect - -type Endpoint5_14Request = Parameters[0] -export type Endpoint5_14Input = { - readonly sessionID: Endpoint5_14Request["params"]["sessionID"] - readonly id?: Endpoint5_14Request["payload"]["id"] - readonly command: Endpoint5_14Request["payload"]["command"] -} -export type Endpoint5_14Output = EffectValue> -export type SessionShellOperation = (input: Endpoint5_14Input) => Effect.Effect - -type Endpoint5_15Request = Parameters[0] -export type Endpoint5_15Input = { - readonly sessionID: Endpoint5_15Request["params"]["sessionID"] - readonly id?: Endpoint5_15Request["payload"]["id"] -} -export type Endpoint5_15Output = EffectValue>["data"] -export type SessionCompactOperation = (input: Endpoint5_15Input) => Effect.Effect - -type Endpoint5_16Request = Parameters[0] -export type Endpoint5_16Input = { readonly sessionID: Endpoint5_16Request["params"]["sessionID"] } -export type Endpoint5_16Output = EffectValue> -export type SessionWaitOperation = (input: Endpoint5_16Input) => Effect.Effect - -type Endpoint5_17Request = Parameters[0] -export type Endpoint5_17Input = { - readonly sessionID: Endpoint5_17Request["params"]["sessionID"] - readonly messageID: Endpoint5_17Request["payload"]["messageID"] - readonly files?: Endpoint5_17Request["payload"]["files"] -} -export type Endpoint5_17Output = EffectValue>["data"] -export type SessionRevertStageOperation = (input: Endpoint5_17Input) => Effect.Effect - -type Endpoint5_18Request = Parameters[0] -export type Endpoint5_18Input = { readonly sessionID: Endpoint5_18Request["params"]["sessionID"] } -export type Endpoint5_18Output = EffectValue> -export type SessionRevertClearOperation = (input: Endpoint5_18Input) => Effect.Effect - -type Endpoint5_19Request = Parameters[0] -export type Endpoint5_19Input = { readonly sessionID: Endpoint5_19Request["params"]["sessionID"] } -export type Endpoint5_19Output = EffectValue> -export type SessionRevertCommitOperation = (input: Endpoint5_19Input) => Effect.Effect - -type Endpoint5_20Request = Parameters[0] -export type Endpoint5_20Input = { readonly sessionID: Endpoint5_20Request["params"]["sessionID"] } -export type Endpoint5_20Output = EffectValue>["data"] -export type SessionContextOperation = (input: Endpoint5_20Input) => Effect.Effect - -type Endpoint5_21Request = Parameters[0] -export type Endpoint5_21Input = { readonly sessionID: Endpoint5_21Request["params"]["sessionID"] } -export type Endpoint5_21Output = EffectValue>["data"] -export type SessionPendingListOperation = (input: Endpoint5_21Input) => Effect.Effect - -type Endpoint5_22Request = Parameters[0] -export type Endpoint5_22Input = { readonly sessionID: Endpoint5_22Request["params"]["sessionID"] } -export type Endpoint5_22Output = EffectValue< +type Endpoint6_0Request = Parameters[0] +export type Endpoint6_0Input = { + readonly workspace?: Endpoint6_0Request["query"]["workspace"] + readonly limit?: Endpoint6_0Request["query"]["limit"] + readonly order?: Endpoint6_0Request["query"]["order"] + readonly search?: Endpoint6_0Request["query"]["search"] + readonly parentID?: Endpoint6_0Request["query"]["parentID"] + readonly directory?: Endpoint6_0Request["query"]["directory"] + readonly project?: Endpoint6_0Request["query"]["project"] + readonly subpath?: Endpoint6_0Request["query"]["subpath"] + readonly cursor?: Endpoint6_0Request["query"]["cursor"] +} +export type Endpoint6_0Output = EffectValue> +export type SessionListOperation = (input?: Endpoint6_0Input) => Effect.Effect + +type Endpoint6_1Request = Parameters[0] +export type Endpoint6_1Input = { + readonly id?: Endpoint6_1Request["payload"]["id"] + readonly agent?: Endpoint6_1Request["payload"]["agent"] + readonly model?: Endpoint6_1Request["payload"]["model"] + readonly location?: Endpoint6_1Request["payload"]["location"] +} +export type Endpoint6_1Output = EffectValue>["data"] +export type SessionCreateOperation = (input?: Endpoint6_1Input) => Effect.Effect + +export type Endpoint6_2Output = EffectValue>["data"] +export type SessionActiveOperation = () => Effect.Effect + +type Endpoint6_3Request = Parameters[0] +export type Endpoint6_3Input = { readonly sessionID: Endpoint6_3Request["params"]["sessionID"] } +export type Endpoint6_3Output = EffectValue>["data"] +export type SessionGetOperation = (input: Endpoint6_3Input) => Effect.Effect + +type Endpoint6_4Request = Parameters[0] +export type Endpoint6_4Input = { readonly sessionID: Endpoint6_4Request["params"]["sessionID"] } +export type Endpoint6_4Output = EffectValue> +export type SessionRemoveOperation = (input: Endpoint6_4Input) => Effect.Effect + +type Endpoint6_5Request = Parameters[0] +export type Endpoint6_5Input = { + readonly sessionID: Endpoint6_5Request["params"]["sessionID"] + readonly messageID?: Endpoint6_5Request["payload"]["messageID"] +} +export type Endpoint6_5Output = EffectValue>["data"] +export type SessionForkOperation = (input: Endpoint6_5Input) => Effect.Effect + +type Endpoint6_6Request = Parameters[0] +export type Endpoint6_6Input = { + readonly sessionID: Endpoint6_6Request["params"]["sessionID"] + readonly agent: Endpoint6_6Request["payload"]["agent"] +} +export type Endpoint6_6Output = EffectValue> +export type SessionSwitchAgentOperation = (input: Endpoint6_6Input) => Effect.Effect + +type Endpoint6_7Request = Parameters[0] +export type Endpoint6_7Input = { + readonly sessionID: Endpoint6_7Request["params"]["sessionID"] + readonly model: Endpoint6_7Request["payload"]["model"] +} +export type Endpoint6_7Output = EffectValue> +export type SessionSwitchModelOperation = (input: Endpoint6_7Input) => Effect.Effect + +type Endpoint6_8Request = Parameters[0] +export type Endpoint6_8Input = { + readonly sessionID: Endpoint6_8Request["params"]["sessionID"] + readonly title: Endpoint6_8Request["payload"]["title"] +} +export type Endpoint6_8Output = EffectValue> +export type SessionRenameOperation = (input: Endpoint6_8Input) => Effect.Effect + +type Endpoint6_9Request = Parameters[0] +export type Endpoint6_9Input = { + readonly sessionID: Endpoint6_9Request["params"]["sessionID"] + readonly destination: Endpoint6_9Request["payload"]["destination"] + readonly moveChanges?: Endpoint6_9Request["payload"]["moveChanges"] +} +export type Endpoint6_9Output = EffectValue> +export type SessionMoveOperation = (input: Endpoint6_9Input) => Effect.Effect + +type Endpoint6_10Request = Parameters[0] +export type Endpoint6_10Input = { + readonly sessionID: Endpoint6_10Request["params"]["sessionID"] + readonly id?: Endpoint6_10Request["payload"]["id"] + readonly text: Endpoint6_10Request["payload"]["text"] + readonly files?: Endpoint6_10Request["payload"]["files"] + readonly agents?: Endpoint6_10Request["payload"]["agents"] + readonly metadata?: Endpoint6_10Request["payload"]["metadata"] + readonly delivery?: Endpoint6_10Request["payload"]["delivery"] + readonly resume?: Endpoint6_10Request["payload"]["resume"] +} +export type Endpoint6_10Output = EffectValue>["data"] +export type SessionPromptOperation = (input: Endpoint6_10Input) => Effect.Effect + +type Endpoint6_11Request = Parameters[0] +export type Endpoint6_11Input = { + readonly sessionID: Endpoint6_11Request["params"]["sessionID"] + readonly id?: Endpoint6_11Request["payload"]["id"] + readonly command: Endpoint6_11Request["payload"]["command"] + readonly arguments?: Endpoint6_11Request["payload"]["arguments"] + readonly agent?: Endpoint6_11Request["payload"]["agent"] + readonly model?: Endpoint6_11Request["payload"]["model"] + readonly files?: Endpoint6_11Request["payload"]["files"] + readonly agents?: Endpoint6_11Request["payload"]["agents"] + readonly delivery?: Endpoint6_11Request["payload"]["delivery"] + readonly resume?: Endpoint6_11Request["payload"]["resume"] +} +export type Endpoint6_11Output = EffectValue>["data"] +export type SessionCommandOperation = (input: Endpoint6_11Input) => Effect.Effect + +type Endpoint6_12Request = Parameters[0] +export type Endpoint6_12Input = { + readonly sessionID: Endpoint6_12Request["params"]["sessionID"] + readonly id?: Endpoint6_12Request["payload"]["id"] + readonly skill: Endpoint6_12Request["payload"]["skill"] + readonly resume?: Endpoint6_12Request["payload"]["resume"] +} +export type Endpoint6_12Output = EffectValue> +export type SessionSkillOperation = (input: Endpoint6_12Input) => Effect.Effect + +type Endpoint6_13Request = Parameters[0] +export type Endpoint6_13Input = { + readonly sessionID: Endpoint6_13Request["params"]["sessionID"] + readonly id?: Endpoint6_13Request["payload"]["id"] + readonly text: Endpoint6_13Request["payload"]["text"] + readonly description?: Endpoint6_13Request["payload"]["description"] + readonly metadata?: Endpoint6_13Request["payload"]["metadata"] + readonly delivery?: Endpoint6_13Request["payload"]["delivery"] + readonly resume?: Endpoint6_13Request["payload"]["resume"] +} +export type Endpoint6_13Output = EffectValue>["data"] +export type SessionSyntheticOperation = (input: Endpoint6_13Input) => Effect.Effect + +type Endpoint6_14Request = Parameters[0] +export type Endpoint6_14Input = { + readonly sessionID: Endpoint6_14Request["params"]["sessionID"] + readonly id?: Endpoint6_14Request["payload"]["id"] + readonly command: Endpoint6_14Request["payload"]["command"] +} +export type Endpoint6_14Output = EffectValue> +export type SessionShellOperation = (input: Endpoint6_14Input) => Effect.Effect + +type Endpoint6_15Request = Parameters[0] +export type Endpoint6_15Input = { + readonly sessionID: Endpoint6_15Request["params"]["sessionID"] + readonly id?: Endpoint6_15Request["payload"]["id"] +} +export type Endpoint6_15Output = EffectValue>["data"] +export type SessionCompactOperation = (input: Endpoint6_15Input) => Effect.Effect + +type Endpoint6_16Request = Parameters[0] +export type Endpoint6_16Input = { readonly sessionID: Endpoint6_16Request["params"]["sessionID"] } +export type Endpoint6_16Output = EffectValue> +export type SessionWaitOperation = (input: Endpoint6_16Input) => Effect.Effect + +type Endpoint6_17Request = Parameters[0] +export type Endpoint6_17Input = { + readonly sessionID: Endpoint6_17Request["params"]["sessionID"] + readonly messageID: Endpoint6_17Request["payload"]["messageID"] + readonly files?: Endpoint6_17Request["payload"]["files"] +} +export type Endpoint6_17Output = EffectValue>["data"] +export type SessionRevertStageOperation = (input: Endpoint6_17Input) => Effect.Effect + +type Endpoint6_18Request = Parameters[0] +export type Endpoint6_18Input = { readonly sessionID: Endpoint6_18Request["params"]["sessionID"] } +export type Endpoint6_18Output = EffectValue> +export type SessionRevertClearOperation = (input: Endpoint6_18Input) => Effect.Effect + +type Endpoint6_19Request = Parameters[0] +export type Endpoint6_19Input = { readonly sessionID: Endpoint6_19Request["params"]["sessionID"] } +export type Endpoint6_19Output = EffectValue> +export type SessionRevertCommitOperation = (input: Endpoint6_19Input) => Effect.Effect + +type Endpoint6_20Request = Parameters[0] +export type Endpoint6_20Input = { readonly sessionID: Endpoint6_20Request["params"]["sessionID"] } +export type Endpoint6_20Output = EffectValue>["data"] +export type SessionContextOperation = (input: Endpoint6_20Input) => Effect.Effect + +type Endpoint6_21Request = Parameters[0] +export type Endpoint6_21Input = { readonly sessionID: Endpoint6_21Request["params"]["sessionID"] } +export type Endpoint6_21Output = EffectValue>["data"] +export type SessionPendingListOperation = (input: Endpoint6_21Input) => Effect.Effect + +type Endpoint6_22Request = Parameters[0] +export type Endpoint6_22Input = { readonly sessionID: Endpoint6_22Request["params"]["sessionID"] } +export type Endpoint6_22Output = EffectValue< ReturnType >["data"] export type SessionInstructionsEntryListOperation = ( - input: Endpoint5_22Input, -) => Effect.Effect + input: Endpoint6_22Input, +) => Effect.Effect -type Endpoint5_23Request = Parameters[0] -export type Endpoint5_23Input = { - readonly sessionID: Endpoint5_23Request["params"]["sessionID"] - readonly key: Endpoint5_23Request["params"]["key"] - readonly value: Endpoint5_23Request["payload"]["value"] +type Endpoint6_23Request = Parameters[0] +export type Endpoint6_23Input = { + readonly sessionID: Endpoint6_23Request["params"]["sessionID"] + readonly key: Endpoint6_23Request["params"]["key"] + readonly value: Endpoint6_23Request["payload"]["value"] } -export type Endpoint5_23Output = EffectValue> +export type Endpoint6_23Output = EffectValue> export type SessionInstructionsEntryPutOperation = ( - input: Endpoint5_23Input, -) => Effect.Effect + input: Endpoint6_23Input, +) => Effect.Effect -type Endpoint5_24Request = Parameters[0] -export type Endpoint5_24Input = { - readonly sessionID: Endpoint5_24Request["params"]["sessionID"] - readonly key: Endpoint5_24Request["params"]["key"] +type Endpoint6_24Request = Parameters[0] +export type Endpoint6_24Input = { + readonly sessionID: Endpoint6_24Request["params"]["sessionID"] + readonly key: Endpoint6_24Request["params"]["key"] } -export type Endpoint5_24Output = EffectValue< +export type Endpoint6_24Output = EffectValue< ReturnType > export type SessionInstructionsEntryRemoveOperation = ( - input: Endpoint5_24Input, -) => Effect.Effect + input: Endpoint6_24Input, +) => Effect.Effect -type Endpoint5_25Request = Parameters[0] -export type Endpoint5_25Input = { - readonly sessionID: Endpoint5_25Request["params"]["sessionID"] - readonly after?: Endpoint5_25Request["query"]["after"] - readonly follow?: Endpoint5_25Request["query"]["follow"] +type Endpoint6_25Request = Parameters[0] +export type Endpoint6_25Input = { + readonly sessionID: Endpoint6_25Request["params"]["sessionID"] + readonly after?: Endpoint6_25Request["query"]["after"] + readonly follow?: Endpoint6_25Request["query"]["follow"] } -export type Endpoint5_25Output = StreamValue>> -export type SessionLogOperation = (input: Endpoint5_25Input) => Stream.Stream +export type Endpoint6_25Output = StreamValue>> +export type SessionLogOperation = (input: Endpoint6_25Input) => Stream.Stream -type Endpoint5_26Request = Parameters[0] -export type Endpoint5_26Input = { readonly sessionID: Endpoint5_26Request["params"]["sessionID"] } -export type Endpoint5_26Output = EffectValue> -export type SessionInterruptOperation = (input: Endpoint5_26Input) => Effect.Effect +type Endpoint6_26Request = Parameters[0] +export type Endpoint6_26Input = { readonly sessionID: Endpoint6_26Request["params"]["sessionID"] } +export type Endpoint6_26Output = EffectValue> +export type SessionInterruptOperation = (input: Endpoint6_26Input) => Effect.Effect -type Endpoint5_27Request = Parameters[0] -export type Endpoint5_27Input = { readonly sessionID: Endpoint5_27Request["params"]["sessionID"] } -export type Endpoint5_27Output = EffectValue> -export type SessionBackgroundOperation = (input: Endpoint5_27Input) => Effect.Effect +type Endpoint6_27Request = Parameters[0] +export type Endpoint6_27Input = { readonly sessionID: Endpoint6_27Request["params"]["sessionID"] } +export type Endpoint6_27Output = EffectValue> +export type SessionBackgroundOperation = (input: Endpoint6_27Input) => Effect.Effect -type Endpoint5_28Request = Parameters[0] -export type Endpoint5_28Input = { - readonly sessionID: Endpoint5_28Request["params"]["sessionID"] - readonly messageID: Endpoint5_28Request["params"]["messageID"] +type Endpoint6_28Request = Parameters[0] +export type Endpoint6_28Input = { + readonly sessionID: Endpoint6_28Request["params"]["sessionID"] + readonly messageID: Endpoint6_28Request["params"]["messageID"] } -export type Endpoint5_28Output = EffectValue>["data"] -export type SessionMessageOperation = (input: Endpoint5_28Input) => Effect.Effect +export type Endpoint6_28Output = EffectValue>["data"] +export type SessionMessageOperation = (input: Endpoint6_28Input) => Effect.Effect export interface SessionApi { readonly list: SessionListOperation @@ -328,136 +355,136 @@ export interface SessionApi { readonly message: SessionMessageOperation } -type Endpoint6_0Request = Parameters[0] -export type Endpoint6_0Input = { - readonly sessionID: Endpoint6_0Request["params"]["sessionID"] - readonly limit?: Endpoint6_0Request["query"]["limit"] - readonly order?: Endpoint6_0Request["query"]["order"] - readonly cursor?: Endpoint6_0Request["query"]["cursor"] +type Endpoint7_0Request = Parameters[0] +export type Endpoint7_0Input = { + readonly sessionID: Endpoint7_0Request["params"]["sessionID"] + readonly limit?: Endpoint7_0Request["query"]["limit"] + readonly order?: Endpoint7_0Request["query"]["order"] + readonly cursor?: Endpoint7_0Request["query"]["cursor"] } -export type Endpoint6_0Output = EffectValue> -export type MessageListOperation = (input: Endpoint6_0Input) => Effect.Effect +export type Endpoint7_0Output = EffectValue> +export type MessageListOperation = (input: Endpoint7_0Input) => Effect.Effect export interface MessageApi { readonly list: MessageListOperation } -type Endpoint7_0Request = Parameters[0] -export type Endpoint7_0Input = { readonly location?: Endpoint7_0Request["query"]["location"] } -export type Endpoint7_0Output = EffectValue> -export type ModelListOperation = (input?: Endpoint7_0Input) => Effect.Effect +type Endpoint8_0Request = Parameters[0] +export type Endpoint8_0Input = { readonly location?: Endpoint8_0Request["query"]["location"] } +export type Endpoint8_0Output = EffectValue> +export type ModelListOperation = (input?: Endpoint8_0Input) => Effect.Effect -type Endpoint7_1Request = Parameters[0] -export type Endpoint7_1Input = { readonly location?: Endpoint7_1Request["query"]["location"] } -export type Endpoint7_1Output = EffectValue> -export type ModelDefaultOperation = (input?: Endpoint7_1Input) => Effect.Effect +type Endpoint8_1Request = Parameters[0] +export type Endpoint8_1Input = { readonly location?: Endpoint8_1Request["query"]["location"] } +export type Endpoint8_1Output = EffectValue> +export type ModelDefaultOperation = (input?: Endpoint8_1Input) => Effect.Effect export interface ModelApi { readonly list: ModelListOperation readonly default: ModelDefaultOperation } -type Endpoint8_0Request = Parameters[0] -export type Endpoint8_0Input = { - readonly location?: Endpoint8_0Request["query"]["location"] - readonly prompt: Endpoint8_0Request["payload"]["prompt"] - readonly model?: Endpoint8_0Request["payload"]["model"] +type Endpoint9_0Request = Parameters[0] +export type Endpoint9_0Input = { + readonly location?: Endpoint9_0Request["query"]["location"] + readonly prompt: Endpoint9_0Request["payload"]["prompt"] + readonly model?: Endpoint9_0Request["payload"]["model"] } -export type Endpoint8_0Output = EffectValue>["data"] -export type GenerateTextOperation = (input: Endpoint8_0Input) => Effect.Effect +export type Endpoint9_0Output = EffectValue>["data"] +export type GenerateTextOperation = (input: Endpoint9_0Input) => Effect.Effect export interface GenerateApi { readonly text: GenerateTextOperation } -type Endpoint9_0Request = Parameters[0] -export type Endpoint9_0Input = { readonly location?: Endpoint9_0Request["query"]["location"] } -export type Endpoint9_0Output = EffectValue> -export type ProviderListOperation = (input?: Endpoint9_0Input) => Effect.Effect +type Endpoint10_0Request = Parameters[0] +export type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["location"] } +export type Endpoint10_0Output = EffectValue> +export type ProviderListOperation = (input?: Endpoint10_0Input) => Effect.Effect -type Endpoint9_1Request = Parameters[0] -export type Endpoint9_1Input = { - readonly providerID: Endpoint9_1Request["params"]["providerID"] - readonly location?: Endpoint9_1Request["query"]["location"] +type Endpoint10_1Request = Parameters[0] +export type Endpoint10_1Input = { + readonly providerID: Endpoint10_1Request["params"]["providerID"] + readonly location?: Endpoint10_1Request["query"]["location"] } -export type Endpoint9_1Output = EffectValue> -export type ProviderGetOperation = (input: Endpoint9_1Input) => Effect.Effect +export type Endpoint10_1Output = EffectValue> +export type ProviderGetOperation = (input: Endpoint10_1Input) => Effect.Effect export interface ProviderApi { readonly list: ProviderListOperation readonly get: ProviderGetOperation } -type Endpoint10_0Request = Parameters[0] -export type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["location"] } -export type Endpoint10_0Output = EffectValue> -export type IntegrationListOperation = (input?: Endpoint10_0Input) => Effect.Effect +type Endpoint11_0Request = Parameters[0] +export type Endpoint11_0Input = { readonly location?: Endpoint11_0Request["query"]["location"] } +export type Endpoint11_0Output = EffectValue> +export type IntegrationListOperation = (input?: Endpoint11_0Input) => Effect.Effect -type Endpoint10_1Request = Parameters[0] -export type Endpoint10_1Input = { - readonly integrationID: Endpoint10_1Request["params"]["integrationID"] - readonly location?: Endpoint10_1Request["query"]["location"] +type Endpoint11_1Request = Parameters[0] +export type Endpoint11_1Input = { + readonly integrationID: Endpoint11_1Request["params"]["integrationID"] + readonly location?: Endpoint11_1Request["query"]["location"] } -export type Endpoint10_1Output = EffectValue> -export type IntegrationGetOperation = (input: Endpoint10_1Input) => Effect.Effect +export type Endpoint11_1Output = EffectValue> +export type IntegrationGetOperation = (input: Endpoint11_1Input) => Effect.Effect -type Endpoint10_2Request = Parameters[0] -export type Endpoint10_2Input = { - readonly integrationID: Endpoint10_2Request["params"]["integrationID"] - readonly location?: Endpoint10_2Request["query"]["location"] - readonly key: Endpoint10_2Request["payload"]["key"] - readonly label?: Endpoint10_2Request["payload"]["label"] +type Endpoint11_2Request = Parameters[0] +export type Endpoint11_2Input = { + readonly integrationID: Endpoint11_2Request["params"]["integrationID"] + readonly location?: Endpoint11_2Request["query"]["location"] + readonly key: Endpoint11_2Request["payload"]["key"] + readonly label?: Endpoint11_2Request["payload"]["label"] } -export type Endpoint10_2Output = EffectValue> +export type Endpoint11_2Output = EffectValue> export type IntegrationConnectKeyOperation = ( - input: Endpoint10_2Input, -) => Effect.Effect - -type Endpoint10_3Request = Parameters[0] -export type Endpoint10_3Input = { - readonly integrationID: Endpoint10_3Request["params"]["integrationID"] - readonly location?: Endpoint10_3Request["query"]["location"] - readonly methodID: Endpoint10_3Request["payload"]["methodID"] - readonly inputs: Endpoint10_3Request["payload"]["inputs"] - readonly label?: Endpoint10_3Request["payload"]["label"] -} -export type Endpoint10_3Output = EffectValue> + input: Endpoint11_2Input, +) => Effect.Effect + +type Endpoint11_3Request = Parameters[0] +export type Endpoint11_3Input = { + readonly integrationID: Endpoint11_3Request["params"]["integrationID"] + readonly location?: Endpoint11_3Request["query"]["location"] + readonly methodID: Endpoint11_3Request["payload"]["methodID"] + readonly inputs: Endpoint11_3Request["payload"]["inputs"] + readonly label?: Endpoint11_3Request["payload"]["label"] +} +export type Endpoint11_3Output = EffectValue> export type IntegrationConnectOauthOperation = ( - input: Endpoint10_3Input, -) => Effect.Effect + input: Endpoint11_3Input, +) => Effect.Effect -type Endpoint10_4Request = Parameters[0] -export type Endpoint10_4Input = { - readonly attemptID: Endpoint10_4Request["params"]["attemptID"] - readonly location?: Endpoint10_4Request["query"]["location"] +type Endpoint11_4Request = Parameters[0] +export type Endpoint11_4Input = { + readonly attemptID: Endpoint11_4Request["params"]["attemptID"] + readonly location?: Endpoint11_4Request["query"]["location"] } -export type Endpoint10_4Output = EffectValue> +export type Endpoint11_4Output = EffectValue> export type IntegrationAttemptStatusOperation = ( - input: Endpoint10_4Input, -) => Effect.Effect + input: Endpoint11_4Input, +) => Effect.Effect -type Endpoint10_5Request = Parameters[0] -export type Endpoint10_5Input = { - readonly attemptID: Endpoint10_5Request["params"]["attemptID"] - readonly location?: Endpoint10_5Request["query"]["location"] - readonly code?: Endpoint10_5Request["payload"]["code"] +type Endpoint11_5Request = Parameters[0] +export type Endpoint11_5Input = { + readonly attemptID: Endpoint11_5Request["params"]["attemptID"] + readonly location?: Endpoint11_5Request["query"]["location"] + readonly code?: Endpoint11_5Request["payload"]["code"] } -export type Endpoint10_5Output = EffectValue< +export type Endpoint11_5Output = EffectValue< ReturnType > export type IntegrationAttemptCompleteOperation = ( - input: Endpoint10_5Input, -) => Effect.Effect + input: Endpoint11_5Input, +) => Effect.Effect -type Endpoint10_6Request = Parameters[0] -export type Endpoint10_6Input = { - readonly attemptID: Endpoint10_6Request["params"]["attemptID"] - readonly location?: Endpoint10_6Request["query"]["location"] +type Endpoint11_6Request = Parameters[0] +export type Endpoint11_6Input = { + readonly attemptID: Endpoint11_6Request["params"]["attemptID"] + readonly location?: Endpoint11_6Request["query"]["location"] } -export type Endpoint10_6Output = EffectValue> +export type Endpoint11_6Output = EffectValue> export type IntegrationAttemptCancelOperation = ( - input: Endpoint10_6Input, -) => Effect.Effect + input: Endpoint11_6Input, +) => Effect.Effect export interface IntegrationApi { readonly list: IntegrationListOperation @@ -473,58 +500,58 @@ export interface IntegrationApi { } } -type Endpoint11_0Request = Parameters[0] -export type Endpoint11_0Input = { readonly location?: Endpoint11_0Request["query"]["location"] } -export type Endpoint11_0Output = EffectValue> -export type McpListOperation = (input?: Endpoint11_0Input) => Effect.Effect +type Endpoint12_0Request = Parameters[0] +export type Endpoint12_0Input = { readonly location?: Endpoint12_0Request["query"]["location"] } +export type Endpoint12_0Output = EffectValue> +export type McpListOperation = (input?: Endpoint12_0Input) => Effect.Effect -type Endpoint11_1Request = Parameters[0] -export type Endpoint11_1Input = { readonly location?: Endpoint11_1Request["query"]["location"] } -export type Endpoint11_1Output = EffectValue> -export type McpResourceCatalogOperation = (input?: Endpoint11_1Input) => Effect.Effect +type Endpoint12_1Request = Parameters[0] +export type Endpoint12_1Input = { readonly location?: Endpoint12_1Request["query"]["location"] } +export type Endpoint12_1Output = EffectValue> +export type McpResourceCatalogOperation = (input?: Endpoint12_1Input) => Effect.Effect export interface McpApi { readonly list: McpListOperation readonly resource: { readonly catalog: McpResourceCatalogOperation } } -type Endpoint12_0Request = Parameters[0] -export type Endpoint12_0Input = { - readonly credentialID: Endpoint12_0Request["params"]["credentialID"] - readonly location?: Endpoint12_0Request["query"]["location"] - readonly label: Endpoint12_0Request["payload"]["label"] +type Endpoint13_0Request = Parameters[0] +export type Endpoint13_0Input = { + readonly credentialID: Endpoint13_0Request["params"]["credentialID"] + readonly location?: Endpoint13_0Request["query"]["location"] + readonly label: Endpoint13_0Request["payload"]["label"] } -export type Endpoint12_0Output = EffectValue> -export type CredentialUpdateOperation = (input: Endpoint12_0Input) => Effect.Effect +export type Endpoint13_0Output = EffectValue> +export type CredentialUpdateOperation = (input: Endpoint13_0Input) => Effect.Effect -type Endpoint12_1Request = Parameters[0] -export type Endpoint12_1Input = { - readonly credentialID: Endpoint12_1Request["params"]["credentialID"] - readonly location?: Endpoint12_1Request["query"]["location"] +type Endpoint13_1Request = Parameters[0] +export type Endpoint13_1Input = { + readonly credentialID: Endpoint13_1Request["params"]["credentialID"] + readonly location?: Endpoint13_1Request["query"]["location"] } -export type Endpoint12_1Output = EffectValue> -export type CredentialRemoveOperation = (input: Endpoint12_1Input) => Effect.Effect +export type Endpoint13_1Output = EffectValue> +export type CredentialRemoveOperation = (input: Endpoint13_1Input) => Effect.Effect export interface CredentialApi { readonly update: CredentialUpdateOperation readonly remove: CredentialRemoveOperation } -export type Endpoint13_0Output = EffectValue> -export type ProjectListOperation = () => Effect.Effect +export type Endpoint14_0Output = EffectValue> +export type ProjectListOperation = () => Effect.Effect -type Endpoint13_1Request = Parameters[0] -export type Endpoint13_1Input = { readonly location?: Endpoint13_1Request["query"]["location"] } -export type Endpoint13_1Output = EffectValue> -export type ProjectCurrentOperation = (input?: Endpoint13_1Input) => Effect.Effect +type Endpoint14_1Request = Parameters[0] +export type Endpoint14_1Input = { readonly location?: Endpoint14_1Request["query"]["location"] } +export type Endpoint14_1Output = EffectValue> +export type ProjectCurrentOperation = (input?: Endpoint14_1Input) => Effect.Effect -type Endpoint13_2Request = Parameters[0] -export type Endpoint13_2Input = { - readonly projectID: Endpoint13_2Request["params"]["projectID"] - readonly location?: Endpoint13_2Request["query"]["location"] +type Endpoint14_2Request = Parameters[0] +export type Endpoint14_2Input = { + readonly projectID: Endpoint14_2Request["params"]["projectID"] + readonly location?: Endpoint14_2Request["query"]["location"] } -export type Endpoint13_2Output = EffectValue> -export type ProjectDirectoriesOperation = (input: Endpoint13_2Input) => Effect.Effect +export type Endpoint14_2Output = EffectValue> +export type ProjectDirectoriesOperation = (input: Endpoint14_2Input) => Effect.Effect export interface ProjectApi { readonly list: ProjectListOperation @@ -532,59 +559,59 @@ export interface ProjectApi { readonly directories: ProjectDirectoriesOperation } -type Endpoint14_0Request = Parameters[0] -export type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] } -export type Endpoint14_0Output = EffectValue> -export type FormRequestListOperation = (input?: Endpoint14_0Input) => Effect.Effect - -type Endpoint14_1Request = Parameters[0] -export type Endpoint14_1Input = { readonly sessionID: Endpoint14_1Request["params"]["sessionID"] } -export type Endpoint14_1Output = EffectValue>["data"] -export type FormListOperation = (input: Endpoint14_1Input) => Effect.Effect - -type Endpoint14_2Request = Parameters[0] -export type Endpoint14_2Input = { - readonly sessionID: Endpoint14_2Request["params"]["sessionID"] - readonly id?: Endpoint14_2Request["payload"]["id"] - readonly title: Endpoint14_2Request["payload"]["title"] - readonly metadata?: Endpoint14_2Request["payload"]["metadata"] - readonly fields: Endpoint14_2Request["payload"]["fields"] -} -export type Endpoint14_2Output = EffectValue>["data"] -export type FormCreateOperation = (input: Endpoint14_2Input) => Effect.Effect - -type Endpoint14_3Request = Parameters[0] -export type Endpoint14_3Input = { - readonly sessionID: Endpoint14_3Request["params"]["sessionID"] - readonly formID: Endpoint14_3Request["params"]["formID"] +type Endpoint15_0Request = Parameters[0] +export type Endpoint15_0Input = { readonly location?: Endpoint15_0Request["query"]["location"] } +export type Endpoint15_0Output = EffectValue> +export type FormRequestListOperation = (input?: Endpoint15_0Input) => Effect.Effect + +type Endpoint15_1Request = Parameters[0] +export type Endpoint15_1Input = { readonly sessionID: Endpoint15_1Request["params"]["sessionID"] } +export type Endpoint15_1Output = EffectValue>["data"] +export type FormListOperation = (input: Endpoint15_1Input) => Effect.Effect + +type Endpoint15_2Request = Parameters[0] +export type Endpoint15_2Input = { + readonly sessionID: Endpoint15_2Request["params"]["sessionID"] + readonly id?: Endpoint15_2Request["payload"]["id"] + readonly title: Endpoint15_2Request["payload"]["title"] + readonly metadata?: Endpoint15_2Request["payload"]["metadata"] + readonly fields: Endpoint15_2Request["payload"]["fields"] +} +export type Endpoint15_2Output = EffectValue>["data"] +export type FormCreateOperation = (input: Endpoint15_2Input) => Effect.Effect + +type Endpoint15_3Request = Parameters[0] +export type Endpoint15_3Input = { + readonly sessionID: Endpoint15_3Request["params"]["sessionID"] + readonly formID: Endpoint15_3Request["params"]["formID"] } -export type Endpoint14_3Output = EffectValue>["data"] -export type FormGetOperation = (input: Endpoint14_3Input) => Effect.Effect +export type Endpoint15_3Output = EffectValue>["data"] +export type FormGetOperation = (input: Endpoint15_3Input) => Effect.Effect -type Endpoint14_4Request = Parameters[0] -export type Endpoint14_4Input = { - readonly sessionID: Endpoint14_4Request["params"]["sessionID"] - readonly formID: Endpoint14_4Request["params"]["formID"] +type Endpoint15_4Request = Parameters[0] +export type Endpoint15_4Input = { + readonly sessionID: Endpoint15_4Request["params"]["sessionID"] + readonly formID: Endpoint15_4Request["params"]["formID"] } -export type Endpoint14_4Output = EffectValue>["data"] -export type FormStateOperation = (input: Endpoint14_4Input) => Effect.Effect +export type Endpoint15_4Output = EffectValue>["data"] +export type FormStateOperation = (input: Endpoint15_4Input) => Effect.Effect -type Endpoint14_5Request = Parameters[0] -export type Endpoint14_5Input = { - readonly sessionID: Endpoint14_5Request["params"]["sessionID"] - readonly formID: Endpoint14_5Request["params"]["formID"] - readonly answer: Endpoint14_5Request["payload"]["answer"] +type Endpoint15_5Request = Parameters[0] +export type Endpoint15_5Input = { + readonly sessionID: Endpoint15_5Request["params"]["sessionID"] + readonly formID: Endpoint15_5Request["params"]["formID"] + readonly answer: Endpoint15_5Request["payload"]["answer"] } -export type Endpoint14_5Output = EffectValue> -export type FormReplyOperation = (input: Endpoint14_5Input) => Effect.Effect +export type Endpoint15_5Output = EffectValue> +export type FormReplyOperation = (input: Endpoint15_5Input) => Effect.Effect -type Endpoint14_6Request = Parameters[0] -export type Endpoint14_6Input = { - readonly sessionID: Endpoint14_6Request["params"]["sessionID"] - readonly formID: Endpoint14_6Request["params"]["formID"] +type Endpoint15_6Request = Parameters[0] +export type Endpoint15_6Input = { + readonly sessionID: Endpoint15_6Request["params"]["sessionID"] + readonly formID: Endpoint15_6Request["params"]["formID"] } -export type Endpoint14_6Output = EffectValue> -export type FormCancelOperation = (input: Endpoint14_6Input) => Effect.Effect +export type Endpoint15_6Output = EffectValue> +export type FormCancelOperation = (input: Endpoint15_6Input) => Effect.Effect export interface FormApi { readonly request: { readonly list: FormRequestListOperation } @@ -596,71 +623,71 @@ export interface FormApi { readonly cancel: FormCancelOperation } -type Endpoint15_0Request = Parameters[0] -export type Endpoint15_0Input = { readonly location?: Endpoint15_0Request["query"]["location"] } -export type Endpoint15_0Output = EffectValue> +type Endpoint16_0Request = Parameters[0] +export type Endpoint16_0Input = { readonly location?: Endpoint16_0Request["query"]["location"] } +export type Endpoint16_0Output = EffectValue> export type PermissionRequestListOperation = ( - input?: Endpoint15_0Input, -) => Effect.Effect + input?: Endpoint16_0Input, +) => Effect.Effect -type Endpoint15_1Request = Parameters[0] -export type Endpoint15_1Input = { readonly projectID?: Endpoint15_1Request["query"]["projectID"] } -export type Endpoint15_1Output = EffectValue< +type Endpoint16_1Request = Parameters[0] +export type Endpoint16_1Input = { readonly projectID?: Endpoint16_1Request["query"]["projectID"] } +export type Endpoint16_1Output = EffectValue< ReturnType >["data"] export type PermissionSavedListOperation = ( - input?: Endpoint15_1Input, -) => Effect.Effect + input?: Endpoint16_1Input, +) => Effect.Effect -type Endpoint15_2Request = Parameters[0] -export type Endpoint15_2Input = { readonly id: Endpoint15_2Request["params"]["id"] } -export type Endpoint15_2Output = EffectValue> +type Endpoint16_2Request = Parameters[0] +export type Endpoint16_2Input = { readonly id: Endpoint16_2Request["params"]["id"] } +export type Endpoint16_2Output = EffectValue> export type PermissionSavedRemoveOperation = ( - input: Endpoint15_2Input, -) => Effect.Effect - -type Endpoint15_3Request = Parameters[0] -export type Endpoint15_3Input = { - readonly sessionID: Endpoint15_3Request["params"]["sessionID"] - readonly id?: Endpoint15_3Request["payload"]["id"] - readonly action: Endpoint15_3Request["payload"]["action"] - readonly resources: Endpoint15_3Request["payload"]["resources"] - readonly save?: Endpoint15_3Request["payload"]["save"] - readonly metadata?: Endpoint15_3Request["payload"]["metadata"] - readonly source?: Endpoint15_3Request["payload"]["source"] - readonly agent?: Endpoint15_3Request["payload"]["agent"] -} -export type Endpoint15_3Output = EffectValue< + input: Endpoint16_2Input, +) => Effect.Effect + +type Endpoint16_3Request = Parameters[0] +export type Endpoint16_3Input = { + readonly sessionID: Endpoint16_3Request["params"]["sessionID"] + readonly id?: Endpoint16_3Request["payload"]["id"] + readonly action: Endpoint16_3Request["payload"]["action"] + readonly resources: Endpoint16_3Request["payload"]["resources"] + readonly save?: Endpoint16_3Request["payload"]["save"] + readonly metadata?: Endpoint16_3Request["payload"]["metadata"] + readonly source?: Endpoint16_3Request["payload"]["source"] + readonly agent?: Endpoint16_3Request["payload"]["agent"] +} +export type Endpoint16_3Output = EffectValue< ReturnType >["data"] -export type PermissionCreateOperation = (input: Endpoint15_3Input) => Effect.Effect +export type PermissionCreateOperation = (input: Endpoint16_3Input) => Effect.Effect -type Endpoint15_4Request = Parameters[0] -export type Endpoint15_4Input = { readonly sessionID: Endpoint15_4Request["params"]["sessionID"] } -export type Endpoint15_4Output = EffectValue< +type Endpoint16_4Request = Parameters[0] +export type Endpoint16_4Input = { readonly sessionID: Endpoint16_4Request["params"]["sessionID"] } +export type Endpoint16_4Output = EffectValue< ReturnType >["data"] -export type PermissionListOperation = (input: Endpoint15_4Input) => Effect.Effect +export type PermissionListOperation = (input: Endpoint16_4Input) => Effect.Effect -type Endpoint15_5Request = Parameters[0] -export type Endpoint15_5Input = { - readonly sessionID: Endpoint15_5Request["params"]["sessionID"] - readonly requestID: Endpoint15_5Request["params"]["requestID"] +type Endpoint16_5Request = Parameters[0] +export type Endpoint16_5Input = { + readonly sessionID: Endpoint16_5Request["params"]["sessionID"] + readonly requestID: Endpoint16_5Request["params"]["requestID"] } -export type Endpoint15_5Output = EffectValue< +export type Endpoint16_5Output = EffectValue< ReturnType >["data"] -export type PermissionGetOperation = (input: Endpoint15_5Input) => Effect.Effect +export type PermissionGetOperation = (input: Endpoint16_5Input) => Effect.Effect -type Endpoint15_6Request = Parameters[0] -export type Endpoint15_6Input = { - readonly sessionID: Endpoint15_6Request["params"]["sessionID"] - readonly requestID: Endpoint15_6Request["params"]["requestID"] - readonly reply: Endpoint15_6Request["payload"]["reply"] - readonly message?: Endpoint15_6Request["payload"]["message"] +type Endpoint16_6Request = Parameters[0] +export type Endpoint16_6Input = { + readonly sessionID: Endpoint16_6Request["params"]["sessionID"] + readonly requestID: Endpoint16_6Request["params"]["requestID"] + readonly reply: Endpoint16_6Request["payload"]["reply"] + readonly message?: Endpoint16_6Request["payload"]["message"] } -export type Endpoint15_6Output = EffectValue> -export type PermissionReplyOperation = (input: Endpoint15_6Input) => Effect.Effect +export type Endpoint16_6Output = EffectValue> +export type PermissionReplyOperation = (input: Endpoint16_6Input) => Effect.Effect export interface PermissionApi { readonly request: { readonly list: PermissionRequestListOperation } @@ -671,96 +698,96 @@ export interface PermissionApi { readonly reply: PermissionReplyOperation } -type Endpoint16_0Request = Parameters[0] -export type Endpoint16_0Input = { - readonly location?: Endpoint16_0Request["query"]["location"] - readonly path?: Endpoint16_0Request["query"]["path"] +type Endpoint17_0Request = Parameters[0] +export type Endpoint17_0Input = { + readonly location?: Endpoint17_0Request["query"]["location"] + readonly path?: Endpoint17_0Request["query"]["path"] } -export type Endpoint16_0Output = EffectValue> -export type FileListOperation = (input?: Endpoint16_0Input) => Effect.Effect +export type Endpoint17_0Output = EffectValue> +export type FileListOperation = (input?: Endpoint17_0Input) => Effect.Effect -type Endpoint16_1Request = Parameters[0] -export type Endpoint16_1Input = { - readonly location?: Endpoint16_1Request["query"]["location"] - readonly query: Endpoint16_1Request["query"]["query"] - readonly type?: Endpoint16_1Request["query"]["type"] - readonly limit?: Endpoint16_1Request["query"]["limit"] +type Endpoint17_1Request = Parameters[0] +export type Endpoint17_1Input = { + readonly location?: Endpoint17_1Request["query"]["location"] + readonly query: Endpoint17_1Request["query"]["query"] + readonly type?: Endpoint17_1Request["query"]["type"] + readonly limit?: Endpoint17_1Request["query"]["limit"] } -export type Endpoint16_1Output = EffectValue> -export type FileFindOperation = (input: Endpoint16_1Input) => Effect.Effect +export type Endpoint17_1Output = EffectValue> +export type FileFindOperation = (input: Endpoint17_1Input) => Effect.Effect export interface FileApi { readonly list: FileListOperation readonly find: FileFindOperation } -type Endpoint17_0Request = Parameters[0] -export type Endpoint17_0Input = { readonly location?: Endpoint17_0Request["query"]["location"] } -export type Endpoint17_0Output = EffectValue> -export type CommandListOperation = (input?: Endpoint17_0Input) => Effect.Effect +type Endpoint18_0Request = Parameters[0] +export type Endpoint18_0Input = { readonly location?: Endpoint18_0Request["query"]["location"] } +export type Endpoint18_0Output = EffectValue> +export type CommandListOperation = (input?: Endpoint18_0Input) => Effect.Effect export interface CommandApi { readonly list: CommandListOperation } -type Endpoint18_0Request = Parameters[0] -export type Endpoint18_0Input = { readonly location?: Endpoint18_0Request["query"]["location"] } -export type Endpoint18_0Output = EffectValue> -export type SkillListOperation = (input?: Endpoint18_0Input) => Effect.Effect +type Endpoint19_0Request = Parameters[0] +export type Endpoint19_0Input = { readonly location?: Endpoint19_0Request["query"]["location"] } +export type Endpoint19_0Output = EffectValue> +export type SkillListOperation = (input?: Endpoint19_0Input) => Effect.Effect export interface SkillApi { readonly list: SkillListOperation } -export type Endpoint19_0Output = StreamValue>> -export type EventSubscribeOperation = () => Stream.Stream +export type Endpoint20_0Output = StreamValue>> +export type EventSubscribeOperation = () => Stream.Stream export interface EventApi { readonly subscribe: EventSubscribeOperation } -type Endpoint20_0Request = Parameters[0] -export type Endpoint20_0Input = { readonly location?: Endpoint20_0Request["query"]["location"] } -export type Endpoint20_0Output = EffectValue> -export type PtyListOperation = (input?: Endpoint20_0Input) => Effect.Effect +type Endpoint21_0Request = Parameters[0] +export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] } +export type Endpoint21_0Output = EffectValue> +export type PtyListOperation = (input?: Endpoint21_0Input) => Effect.Effect -type Endpoint20_1Request = Parameters[0] -export type Endpoint20_1Input = { - readonly location?: Endpoint20_1Request["query"]["location"] - readonly command?: Endpoint20_1Request["payload"]["command"] - readonly args?: Endpoint20_1Request["payload"]["args"] - readonly cwd?: Endpoint20_1Request["payload"]["cwd"] - readonly title?: Endpoint20_1Request["payload"]["title"] - readonly env?: Endpoint20_1Request["payload"]["env"] +type Endpoint21_1Request = Parameters[0] +export type Endpoint21_1Input = { + readonly location?: Endpoint21_1Request["query"]["location"] + readonly command?: Endpoint21_1Request["payload"]["command"] + readonly args?: Endpoint21_1Request["payload"]["args"] + readonly cwd?: Endpoint21_1Request["payload"]["cwd"] + readonly title?: Endpoint21_1Request["payload"]["title"] + readonly env?: Endpoint21_1Request["payload"]["env"] } -export type Endpoint20_1Output = EffectValue> -export type PtyCreateOperation = (input?: Endpoint20_1Input) => Effect.Effect +export type Endpoint21_1Output = EffectValue> +export type PtyCreateOperation = (input?: Endpoint21_1Input) => Effect.Effect -type Endpoint20_2Request = Parameters[0] -export type Endpoint20_2Input = { - readonly ptyID: Endpoint20_2Request["params"]["ptyID"] - readonly location?: Endpoint20_2Request["query"]["location"] +type Endpoint21_2Request = Parameters[0] +export type Endpoint21_2Input = { + readonly ptyID: Endpoint21_2Request["params"]["ptyID"] + readonly location?: Endpoint21_2Request["query"]["location"] } -export type Endpoint20_2Output = EffectValue> -export type PtyGetOperation = (input: Endpoint20_2Input) => Effect.Effect +export type Endpoint21_2Output = EffectValue> +export type PtyGetOperation = (input: Endpoint21_2Input) => Effect.Effect -type Endpoint20_3Request = Parameters[0] -export type Endpoint20_3Input = { - readonly ptyID: Endpoint20_3Request["params"]["ptyID"] - readonly location?: Endpoint20_3Request["query"]["location"] - readonly title?: Endpoint20_3Request["payload"]["title"] - readonly size?: Endpoint20_3Request["payload"]["size"] +type Endpoint21_3Request = Parameters[0] +export type Endpoint21_3Input = { + readonly ptyID: Endpoint21_3Request["params"]["ptyID"] + readonly location?: Endpoint21_3Request["query"]["location"] + readonly title?: Endpoint21_3Request["payload"]["title"] + readonly size?: Endpoint21_3Request["payload"]["size"] } -export type Endpoint20_3Output = EffectValue> -export type PtyUpdateOperation = (input: Endpoint20_3Input) => Effect.Effect +export type Endpoint21_3Output = EffectValue> +export type PtyUpdateOperation = (input: Endpoint21_3Input) => Effect.Effect -type Endpoint20_4Request = Parameters[0] -export type Endpoint20_4Input = { - readonly ptyID: Endpoint20_4Request["params"]["ptyID"] - readonly location?: Endpoint20_4Request["query"]["location"] +type Endpoint21_4Request = Parameters[0] +export type Endpoint21_4Input = { + readonly ptyID: Endpoint21_4Request["params"]["ptyID"] + readonly location?: Endpoint21_4Request["query"]["location"] } -export type Endpoint20_4Output = EffectValue> -export type PtyRemoveOperation = (input: Endpoint20_4Input) => Effect.Effect +export type Endpoint21_4Output = EffectValue> +export type PtyRemoveOperation = (input: Endpoint21_4Input) => Effect.Effect export interface PtyApi { readonly list: PtyListOperation @@ -770,56 +797,56 @@ export interface PtyApi { readonly remove: PtyRemoveOperation } -type Endpoint21_0Request = Parameters[0] -export type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] } -export type Endpoint21_0Output = EffectValue> -export type ShellListOperation = (input?: Endpoint21_0Input) => Effect.Effect +type Endpoint22_0Request = Parameters[0] +export type Endpoint22_0Input = { readonly location?: Endpoint22_0Request["query"]["location"] } +export type Endpoint22_0Output = EffectValue> +export type ShellListOperation = (input?: Endpoint22_0Input) => Effect.Effect -type Endpoint21_1Request = Parameters[0] -export type Endpoint21_1Input = { - readonly location?: Endpoint21_1Request["query"]["location"] - readonly command: Endpoint21_1Request["payload"]["command"] - readonly cwd?: Endpoint21_1Request["payload"]["cwd"] - readonly timeout: Endpoint21_1Request["payload"]["timeout"] - readonly metadata?: Endpoint21_1Request["payload"]["metadata"] +type Endpoint22_1Request = Parameters[0] +export type Endpoint22_1Input = { + readonly location?: Endpoint22_1Request["query"]["location"] + readonly command: Endpoint22_1Request["payload"]["command"] + readonly cwd?: Endpoint22_1Request["payload"]["cwd"] + readonly timeout: Endpoint22_1Request["payload"]["timeout"] + readonly metadata?: Endpoint22_1Request["payload"]["metadata"] } -export type Endpoint21_1Output = EffectValue> -export type ShellCreateOperation = (input: Endpoint21_1Input) => Effect.Effect +export type Endpoint22_1Output = EffectValue> +export type ShellCreateOperation = (input: Endpoint22_1Input) => Effect.Effect -type Endpoint21_2Request = Parameters[0] -export type Endpoint21_2Input = { - readonly id: Endpoint21_2Request["params"]["id"] - readonly location?: Endpoint21_2Request["query"]["location"] +type Endpoint22_2Request = Parameters[0] +export type Endpoint22_2Input = { + readonly id: Endpoint22_2Request["params"]["id"] + readonly location?: Endpoint22_2Request["query"]["location"] } -export type Endpoint21_2Output = EffectValue> -export type ShellGetOperation = (input: Endpoint21_2Input) => Effect.Effect +export type Endpoint22_2Output = EffectValue> +export type ShellGetOperation = (input: Endpoint22_2Input) => Effect.Effect -type Endpoint21_3Request = Parameters[0] -export type Endpoint21_3Input = { - readonly id: Endpoint21_3Request["params"]["id"] - readonly location?: Endpoint21_3Request["query"]["location"] - readonly timeout: Endpoint21_3Request["payload"]["timeout"] +type Endpoint22_3Request = Parameters[0] +export type Endpoint22_3Input = { + readonly id: Endpoint22_3Request["params"]["id"] + readonly location?: Endpoint22_3Request["query"]["location"] + readonly timeout: Endpoint22_3Request["payload"]["timeout"] } -export type Endpoint21_3Output = EffectValue> -export type ShellTimeoutOperation = (input: Endpoint21_3Input) => Effect.Effect +export type Endpoint22_3Output = EffectValue> +export type ShellTimeoutOperation = (input: Endpoint22_3Input) => Effect.Effect -type Endpoint21_4Request = Parameters[0] -export type Endpoint21_4Input = { - readonly id: Endpoint21_4Request["params"]["id"] - readonly location?: Endpoint21_4Request["query"]["location"] - readonly cursor?: Endpoint21_4Request["query"]["cursor"] - readonly limit?: Endpoint21_4Request["query"]["limit"] +type Endpoint22_4Request = Parameters[0] +export type Endpoint22_4Input = { + readonly id: Endpoint22_4Request["params"]["id"] + readonly location?: Endpoint22_4Request["query"]["location"] + readonly cursor?: Endpoint22_4Request["query"]["cursor"] + readonly limit?: Endpoint22_4Request["query"]["limit"] } -export type Endpoint21_4Output = EffectValue> -export type ShellOutputOperation = (input: Endpoint21_4Input) => Effect.Effect +export type Endpoint22_4Output = EffectValue> +export type ShellOutputOperation = (input: Endpoint22_4Input) => Effect.Effect -type Endpoint21_5Request = Parameters[0] -export type Endpoint21_5Input = { - readonly id: Endpoint21_5Request["params"]["id"] - readonly location?: Endpoint21_5Request["query"]["location"] +type Endpoint22_5Request = Parameters[0] +export type Endpoint22_5Input = { + readonly id: Endpoint22_5Request["params"]["id"] + readonly location?: Endpoint22_5Request["query"]["location"] } -export type Endpoint21_5Output = EffectValue> -export type ShellRemoveOperation = (input: Endpoint21_5Input) => Effect.Effect +export type Endpoint22_5Output = EffectValue> +export type ShellRemoveOperation = (input: Endpoint22_5Input) => Effect.Effect export interface ShellApi { readonly list: ShellListOperation @@ -830,34 +857,34 @@ export interface ShellApi { readonly remove: ShellRemoveOperation } -type Endpoint22_0Request = Parameters[0] -export type Endpoint22_0Input = { readonly location?: Endpoint22_0Request["query"]["location"] } -export type Endpoint22_0Output = EffectValue> +type Endpoint23_0Request = Parameters[0] +export type Endpoint23_0Input = { readonly location?: Endpoint23_0Request["query"]["location"] } +export type Endpoint23_0Output = EffectValue> export type QuestionRequestListOperation = ( - input?: Endpoint22_0Input, -) => Effect.Effect + input?: Endpoint23_0Input, +) => Effect.Effect -type Endpoint22_1Request = Parameters[0] -export type Endpoint22_1Input = { readonly sessionID: Endpoint22_1Request["params"]["sessionID"] } -export type Endpoint22_1Output = EffectValue>["data"] -export type QuestionListOperation = (input: Endpoint22_1Input) => Effect.Effect +type Endpoint23_1Request = Parameters[0] +export type Endpoint23_1Input = { readonly sessionID: Endpoint23_1Request["params"]["sessionID"] } +export type Endpoint23_1Output = EffectValue>["data"] +export type QuestionListOperation = (input: Endpoint23_1Input) => Effect.Effect -type Endpoint22_2Request = Parameters[0] -export type Endpoint22_2Input = { - readonly sessionID: Endpoint22_2Request["params"]["sessionID"] - readonly requestID: Endpoint22_2Request["params"]["requestID"] - readonly answers: Endpoint22_2Request["payload"]["answers"] +type Endpoint23_2Request = Parameters[0] +export type Endpoint23_2Input = { + readonly sessionID: Endpoint23_2Request["params"]["sessionID"] + readonly requestID: Endpoint23_2Request["params"]["requestID"] + readonly answers: Endpoint23_2Request["payload"]["answers"] } -export type Endpoint22_2Output = EffectValue> -export type QuestionReplyOperation = (input: Endpoint22_2Input) => Effect.Effect +export type Endpoint23_2Output = EffectValue> +export type QuestionReplyOperation = (input: Endpoint23_2Input) => Effect.Effect -type Endpoint22_3Request = Parameters[0] -export type Endpoint22_3Input = { - readonly sessionID: Endpoint22_3Request["params"]["sessionID"] - readonly requestID: Endpoint22_3Request["params"]["requestID"] +type Endpoint23_3Request = Parameters[0] +export type Endpoint23_3Input = { + readonly sessionID: Endpoint23_3Request["params"]["sessionID"] + readonly requestID: Endpoint23_3Request["params"]["requestID"] } -export type Endpoint22_3Output = EffectValue> -export type QuestionRejectOperation = (input: Endpoint22_3Input) => Effect.Effect +export type Endpoint23_3Output = EffectValue> +export type QuestionRejectOperation = (input: Endpoint23_3Input) => Effect.Effect export interface QuestionApi { readonly request: { readonly list: QuestionRequestListOperation } @@ -866,43 +893,43 @@ export interface QuestionApi { readonly reject: QuestionRejectOperation } -type Endpoint23_0Request = Parameters[0] -export type Endpoint23_0Input = { readonly location?: Endpoint23_0Request["query"]["location"] } -export type Endpoint23_0Output = EffectValue> -export type ReferenceListOperation = (input?: Endpoint23_0Input) => Effect.Effect +type Endpoint24_0Request = Parameters[0] +export type Endpoint24_0Input = { readonly location?: Endpoint24_0Request["query"]["location"] } +export type Endpoint24_0Output = EffectValue> +export type ReferenceListOperation = (input?: Endpoint24_0Input) => Effect.Effect export interface ReferenceApi { readonly list: ReferenceListOperation } -type Endpoint24_0Request = Parameters[0] -export type Endpoint24_0Input = { - readonly projectID: Endpoint24_0Request["params"]["projectID"] - readonly location?: Endpoint24_0Request["query"]["location"] - readonly strategy: Endpoint24_0Request["payload"]["strategy"] - readonly directory: Endpoint24_0Request["payload"]["directory"] - readonly name?: Endpoint24_0Request["payload"]["name"] +type Endpoint25_0Request = Parameters[0] +export type Endpoint25_0Input = { + readonly projectID: Endpoint25_0Request["params"]["projectID"] + readonly location?: Endpoint25_0Request["query"]["location"] + readonly strategy: Endpoint25_0Request["payload"]["strategy"] + readonly directory: Endpoint25_0Request["payload"]["directory"] + readonly name?: Endpoint25_0Request["payload"]["name"] } -export type Endpoint24_0Output = EffectValue> -export type ProjectCopyCreateOperation = (input: Endpoint24_0Input) => Effect.Effect +export type Endpoint25_0Output = EffectValue> +export type ProjectCopyCreateOperation = (input: Endpoint25_0Input) => Effect.Effect -type Endpoint24_1Request = Parameters[0] -export type Endpoint24_1Input = { - readonly projectID: Endpoint24_1Request["params"]["projectID"] - readonly location?: Endpoint24_1Request["query"]["location"] - readonly directory: Endpoint24_1Request["payload"]["directory"] - readonly force: Endpoint24_1Request["payload"]["force"] +type Endpoint25_1Request = Parameters[0] +export type Endpoint25_1Input = { + readonly projectID: Endpoint25_1Request["params"]["projectID"] + readonly location?: Endpoint25_1Request["query"]["location"] + readonly directory: Endpoint25_1Request["payload"]["directory"] + readonly force: Endpoint25_1Request["payload"]["force"] } -export type Endpoint24_1Output = EffectValue> -export type ProjectCopyRemoveOperation = (input: Endpoint24_1Input) => Effect.Effect +export type Endpoint25_1Output = EffectValue> +export type ProjectCopyRemoveOperation = (input: Endpoint25_1Input) => Effect.Effect -type Endpoint24_2Request = Parameters[0] -export type Endpoint24_2Input = { - readonly projectID: Endpoint24_2Request["params"]["projectID"] - readonly location?: Endpoint24_2Request["query"]["location"] +type Endpoint25_2Request = Parameters[0] +export type Endpoint25_2Input = { + readonly projectID: Endpoint25_2Request["params"]["projectID"] + readonly location?: Endpoint25_2Request["query"]["location"] } -export type Endpoint24_2Output = EffectValue> -export type ProjectCopyRefreshOperation = (input: Endpoint24_2Input) => Effect.Effect +export type Endpoint25_2Output = EffectValue> +export type ProjectCopyRefreshOperation = (input: Endpoint25_2Input) => Effect.Effect export interface ProjectCopyApi { readonly create: ProjectCopyCreateOperation @@ -910,32 +937,32 @@ export interface ProjectCopyApi { readonly refresh: ProjectCopyRefreshOperation } -type Endpoint25_0Request = Parameters[0] -export type Endpoint25_0Input = { readonly location?: Endpoint25_0Request["query"]["location"] } -export type Endpoint25_0Output = EffectValue> -export type VcsStatusOperation = (input?: Endpoint25_0Input) => Effect.Effect +type Endpoint26_0Request = Parameters[0] +export type Endpoint26_0Input = { readonly location?: Endpoint26_0Request["query"]["location"] } +export type Endpoint26_0Output = EffectValue> +export type VcsStatusOperation = (input?: Endpoint26_0Input) => Effect.Effect -type Endpoint25_1Request = Parameters[0] -export type Endpoint25_1Input = { - readonly location?: Endpoint25_1Request["query"]["location"] - readonly mode: Endpoint25_1Request["query"]["mode"] - readonly context?: Endpoint25_1Request["query"]["context"] +type Endpoint26_1Request = Parameters[0] +export type Endpoint26_1Input = { + readonly location?: Endpoint26_1Request["query"]["location"] + readonly mode: Endpoint26_1Request["query"]["mode"] + readonly context?: Endpoint26_1Request["query"]["context"] } -export type Endpoint25_1Output = EffectValue> -export type VcsDiffOperation = (input: Endpoint25_1Input) => Effect.Effect +export type Endpoint26_1Output = EffectValue> +export type VcsDiffOperation = (input: Endpoint26_1Input) => Effect.Effect export interface VcsApi { readonly status: VcsStatusOperation readonly diff: VcsDiffOperation } -export type Endpoint26_0Output = EffectValue> -export type DebugLocationListOperation = () => Effect.Effect +export type Endpoint27_0Output = EffectValue> +export type DebugLocationListOperation = () => Effect.Effect -type Endpoint26_1Request = Parameters[0] -export type Endpoint26_1Input = { readonly location?: Endpoint26_1Request["query"]["location"] } -export type Endpoint26_1Output = EffectValue> -export type DebugLocationEvictOperation = (input?: Endpoint26_1Input) => Effect.Effect +type Endpoint27_1Request = Parameters[0] +export type Endpoint27_1Input = { readonly location?: Endpoint27_1Request["query"]["location"] } +export type Endpoint27_1Output = EffectValue> +export type DebugLocationEvictOperation = (input?: Endpoint27_1Input) => Effect.Effect export interface DebugApi { readonly location: { readonly list: DebugLocationListOperation; readonly evict: DebugLocationEvictOperation } @@ -944,6 +971,7 @@ export interface DebugApi { export interface AppApi { readonly health: HealthApi readonly server: ServerApi + readonly pairing: PairingApi readonly location: LocationApi readonly agent: AgentApi readonly plugin: PluginApi diff --git a/packages/client/src/effect/generated/client.ts b/packages/client/src/effect/generated/client.ts index 1f5871711356..417da4aeafc8 100644 --- a/packages/client/src/effect/generated/client.ts +++ b/packages/client/src/effect/generated/client.ts @@ -23,40 +23,74 @@ const Endpoint1_0 = (raw: RawClient["server.server"]) => () => const adaptGroup1 = (raw: RawClient["server.server"]) => ({ get: Endpoint1_0(raw) }) -type Endpoint2_0Request = Parameters[0] -type Endpoint2_0Input = { readonly location?: Endpoint2_0Request["query"]["location"] } -const Endpoint2_0 = (raw: RawClient["server.location"]) => (input?: Endpoint2_0Input) => - raw["location.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) +const Endpoint2_0 = (raw: RawClient["server.pairing"]) => () => + raw["pairing.invitation.create"]({}).pipe(Effect.mapError(mapClientError)) + +type Endpoint2_1Request = Parameters[0] +type Endpoint2_1Input = { + readonly token: Endpoint2_1Request["payload"]["token"] + readonly requestID: Endpoint2_1Request["payload"]["requestID"] + readonly deviceName: Endpoint2_1Request["payload"]["deviceName"] + readonly credential: Endpoint2_1Request["payload"]["credential"] +} +const Endpoint2_1 = (raw: RawClient["server.pairing"]) => (input: Endpoint2_1Input) => + raw["pairing.redeem"]({ + payload: { + token: input["token"], + requestID: input["requestID"], + deviceName: input["deviceName"], + credential: input["credential"], + }, + }).pipe(Effect.mapError(mapClientError)) + +const Endpoint2_2 = (raw: RawClient["server.pairing"]) => () => + raw["pairing.device.list"]({}).pipe(Effect.mapError(mapClientError)) -const adaptGroup2 = (raw: RawClient["server.location"]) => ({ get: Endpoint2_0(raw) }) +type Endpoint2_3Request = Parameters[0] +type Endpoint2_3Input = { readonly deviceID: Endpoint2_3Request["params"]["deviceID"] } +const Endpoint2_3 = (raw: RawClient["server.pairing"]) => (input: Endpoint2_3Input) => + raw["pairing.device.revoke"]({ params: { deviceID: input["deviceID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint3_0Request = Parameters[0] +const adaptGroup2 = (raw: RawClient["server.pairing"]) => ({ + invitation: { create: Endpoint2_0(raw) }, + redeem: Endpoint2_1(raw), + device: { list: Endpoint2_2(raw), revoke: Endpoint2_3(raw) }, +}) + +type Endpoint3_0Request = Parameters[0] type Endpoint3_0Input = { readonly location?: Endpoint3_0Request["query"]["location"] } -const Endpoint3_0 = (raw: RawClient["server.agent"]) => (input?: Endpoint3_0Input) => - raw["agent.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) +const Endpoint3_0 = (raw: RawClient["server.location"]) => (input?: Endpoint3_0Input) => + raw["location.get"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup3 = (raw: RawClient["server.agent"]) => ({ list: Endpoint3_0(raw) }) +const adaptGroup3 = (raw: RawClient["server.location"]) => ({ get: Endpoint3_0(raw) }) -type Endpoint4_0Request = Parameters[0] +type Endpoint4_0Request = Parameters[0] type Endpoint4_0Input = { readonly location?: Endpoint4_0Request["query"]["location"] } -const Endpoint4_0 = (raw: RawClient["server.plugin"]) => (input?: Endpoint4_0Input) => +const Endpoint4_0 = (raw: RawClient["server.agent"]) => (input?: Endpoint4_0Input) => + raw["agent.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) + +const adaptGroup4 = (raw: RawClient["server.agent"]) => ({ list: Endpoint4_0(raw) }) + +type Endpoint5_0Request = Parameters[0] +type Endpoint5_0Input = { readonly location?: Endpoint5_0Request["query"]["location"] } +const Endpoint5_0 = (raw: RawClient["server.plugin"]) => (input?: Endpoint5_0Input) => raw["plugin.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup4 = (raw: RawClient["server.plugin"]) => ({ list: Endpoint4_0(raw) }) - -type Endpoint5_0Request = Parameters[0] -type Endpoint5_0Input = { - readonly workspace?: Endpoint5_0Request["query"]["workspace"] - readonly limit?: Endpoint5_0Request["query"]["limit"] - readonly order?: Endpoint5_0Request["query"]["order"] - readonly search?: Endpoint5_0Request["query"]["search"] - readonly parentID?: Endpoint5_0Request["query"]["parentID"] - readonly directory?: Endpoint5_0Request["query"]["directory"] - readonly project?: Endpoint5_0Request["query"]["project"] - readonly subpath?: Endpoint5_0Request["query"]["subpath"] - readonly cursor?: Endpoint5_0Request["query"]["cursor"] +const adaptGroup5 = (raw: RawClient["server.plugin"]) => ({ list: Endpoint5_0(raw) }) + +type Endpoint6_0Request = Parameters[0] +type Endpoint6_0Input = { + readonly workspace?: Endpoint6_0Request["query"]["workspace"] + readonly limit?: Endpoint6_0Request["query"]["limit"] + readonly order?: Endpoint6_0Request["query"]["order"] + readonly search?: Endpoint6_0Request["query"]["search"] + readonly parentID?: Endpoint6_0Request["query"]["parentID"] + readonly directory?: Endpoint6_0Request["query"]["directory"] + readonly project?: Endpoint6_0Request["query"]["project"] + readonly subpath?: Endpoint6_0Request["query"]["subpath"] + readonly cursor?: Endpoint6_0Request["query"]["cursor"] } -const Endpoint5_0 = (raw: RawClient["server.session"]) => (input?: Endpoint5_0Input) => +const Endpoint6_0 = (raw: RawClient["server.session"]) => (input?: Endpoint6_0Input) => raw["session.list"]({ query: { workspace: input?.["workspace"], @@ -71,14 +105,14 @@ const Endpoint5_0 = (raw: RawClient["server.session"]) => (input?: Endpoint5_0In }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_1Request = Parameters[0] -type Endpoint5_1Input = { - readonly id?: Endpoint5_1Request["payload"]["id"] - readonly agent?: Endpoint5_1Request["payload"]["agent"] - readonly model?: Endpoint5_1Request["payload"]["model"] - readonly location?: Endpoint5_1Request["payload"]["location"] +type Endpoint6_1Request = Parameters[0] +type Endpoint6_1Input = { + readonly id?: Endpoint6_1Request["payload"]["id"] + readonly agent?: Endpoint6_1Request["payload"]["agent"] + readonly model?: Endpoint6_1Request["payload"]["model"] + readonly location?: Endpoint6_1Request["payload"]["location"] } -const Endpoint5_1 = (raw: RawClient["server.session"]) => (input?: Endpoint5_1Input) => +const Endpoint6_1 = (raw: RawClient["server.session"]) => (input?: Endpoint6_1Input) => raw["session.create"]({ payload: { id: input?.["id"], agent: input?.["agent"], model: input?.["model"], location: input?.["location"] }, }).pipe( @@ -86,90 +120,90 @@ const Endpoint5_1 = (raw: RawClient["server.session"]) => (input?: Endpoint5_1In Effect.map((value) => value.data), ) -const Endpoint5_2 = (raw: RawClient["server.session"]) => () => +const Endpoint6_2 = (raw: RawClient["server.session"]) => () => raw["session.active"]({}).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint5_3Request = Parameters[0] -type Endpoint5_3Input = { readonly sessionID: Endpoint5_3Request["params"]["sessionID"] } -const Endpoint5_3 = (raw: RawClient["server.session"]) => (input: Endpoint5_3Input) => +type Endpoint6_3Request = Parameters[0] +type Endpoint6_3Input = { readonly sessionID: Endpoint6_3Request["params"]["sessionID"] } +const Endpoint6_3 = (raw: RawClient["server.session"]) => (input: Endpoint6_3Input) => raw["session.get"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint5_4Request = Parameters[0] -type Endpoint5_4Input = { readonly sessionID: Endpoint5_4Request["params"]["sessionID"] } -const Endpoint5_4 = (raw: RawClient["server.session"]) => (input: Endpoint5_4Input) => +type Endpoint6_4Request = Parameters[0] +type Endpoint6_4Input = { readonly sessionID: Endpoint6_4Request["params"]["sessionID"] } +const Endpoint6_4 = (raw: RawClient["server.session"]) => (input: Endpoint6_4Input) => raw["session.remove"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_5Request = Parameters[0] -type Endpoint5_5Input = { - readonly sessionID: Endpoint5_5Request["params"]["sessionID"] - readonly messageID?: Endpoint5_5Request["payload"]["messageID"] +type Endpoint6_5Request = Parameters[0] +type Endpoint6_5Input = { + readonly sessionID: Endpoint6_5Request["params"]["sessionID"] + readonly messageID?: Endpoint6_5Request["payload"]["messageID"] } -const Endpoint5_5 = (raw: RawClient["server.session"]) => (input: Endpoint5_5Input) => +const Endpoint6_5 = (raw: RawClient["server.session"]) => (input: Endpoint6_5Input) => raw["session.fork"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint5_6Request = Parameters[0] -type Endpoint5_6Input = { - readonly sessionID: Endpoint5_6Request["params"]["sessionID"] - readonly agent: Endpoint5_6Request["payload"]["agent"] +type Endpoint6_6Request = Parameters[0] +type Endpoint6_6Input = { + readonly sessionID: Endpoint6_6Request["params"]["sessionID"] + readonly agent: Endpoint6_6Request["payload"]["agent"] } -const Endpoint5_6 = (raw: RawClient["server.session"]) => (input: Endpoint5_6Input) => +const Endpoint6_6 = (raw: RawClient["server.session"]) => (input: Endpoint6_6Input) => raw["session.switchAgent"]({ params: { sessionID: input["sessionID"] }, payload: { agent: input["agent"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint5_7Request = Parameters[0] -type Endpoint5_7Input = { - readonly sessionID: Endpoint5_7Request["params"]["sessionID"] - readonly model: Endpoint5_7Request["payload"]["model"] +type Endpoint6_7Request = Parameters[0] +type Endpoint6_7Input = { + readonly sessionID: Endpoint6_7Request["params"]["sessionID"] + readonly model: Endpoint6_7Request["payload"]["model"] } -const Endpoint5_7 = (raw: RawClient["server.session"]) => (input: Endpoint5_7Input) => +const Endpoint6_7 = (raw: RawClient["server.session"]) => (input: Endpoint6_7Input) => raw["session.switchModel"]({ params: { sessionID: input["sessionID"] }, payload: { model: input["model"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint5_8Request = Parameters[0] -type Endpoint5_8Input = { - readonly sessionID: Endpoint5_8Request["params"]["sessionID"] - readonly title: Endpoint5_8Request["payload"]["title"] +type Endpoint6_8Request = Parameters[0] +type Endpoint6_8Input = { + readonly sessionID: Endpoint6_8Request["params"]["sessionID"] + readonly title: Endpoint6_8Request["payload"]["title"] } -const Endpoint5_8 = (raw: RawClient["server.session"]) => (input: Endpoint5_8Input) => +const Endpoint6_8 = (raw: RawClient["server.session"]) => (input: Endpoint6_8Input) => raw["session.rename"]({ params: { sessionID: input["sessionID"] }, payload: { title: input["title"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint5_9Request = Parameters[0] -type Endpoint5_9Input = { - readonly sessionID: Endpoint5_9Request["params"]["sessionID"] - readonly destination: Endpoint5_9Request["payload"]["destination"] - readonly moveChanges?: Endpoint5_9Request["payload"]["moveChanges"] +type Endpoint6_9Request = Parameters[0] +type Endpoint6_9Input = { + readonly sessionID: Endpoint6_9Request["params"]["sessionID"] + readonly destination: Endpoint6_9Request["payload"]["destination"] + readonly moveChanges?: Endpoint6_9Request["payload"]["moveChanges"] } -const Endpoint5_9 = (raw: RawClient["server.session"]) => (input: Endpoint5_9Input) => +const Endpoint6_9 = (raw: RawClient["server.session"]) => (input: Endpoint6_9Input) => raw["session.move"]({ params: { sessionID: input["sessionID"] }, payload: { destination: input["destination"], moveChanges: input["moveChanges"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_10Request = Parameters[0] -type Endpoint5_10Input = { - readonly sessionID: Endpoint5_10Request["params"]["sessionID"] - readonly id?: Endpoint5_10Request["payload"]["id"] - readonly text: Endpoint5_10Request["payload"]["text"] - readonly files?: Endpoint5_10Request["payload"]["files"] - readonly agents?: Endpoint5_10Request["payload"]["agents"] - readonly metadata?: Endpoint5_10Request["payload"]["metadata"] - readonly delivery?: Endpoint5_10Request["payload"]["delivery"] - readonly resume?: Endpoint5_10Request["payload"]["resume"] +type Endpoint6_10Request = Parameters[0] +type Endpoint6_10Input = { + readonly sessionID: Endpoint6_10Request["params"]["sessionID"] + readonly id?: Endpoint6_10Request["payload"]["id"] + readonly text: Endpoint6_10Request["payload"]["text"] + readonly files?: Endpoint6_10Request["payload"]["files"] + readonly agents?: Endpoint6_10Request["payload"]["agents"] + readonly metadata?: Endpoint6_10Request["payload"]["metadata"] + readonly delivery?: Endpoint6_10Request["payload"]["delivery"] + readonly resume?: Endpoint6_10Request["payload"]["resume"] } -const Endpoint5_10 = (raw: RawClient["server.session"]) => (input: Endpoint5_10Input) => +const Endpoint6_10 = (raw: RawClient["server.session"]) => (input: Endpoint6_10Input) => raw["session.prompt"]({ params: { sessionID: input["sessionID"] }, payload: { @@ -186,20 +220,20 @@ const Endpoint5_10 = (raw: RawClient["server.session"]) => (input: Endpoint5_10I Effect.map((value) => value.data), ) -type Endpoint5_11Request = Parameters[0] -type Endpoint5_11Input = { - readonly sessionID: Endpoint5_11Request["params"]["sessionID"] - readonly id?: Endpoint5_11Request["payload"]["id"] - readonly command: Endpoint5_11Request["payload"]["command"] - readonly arguments?: Endpoint5_11Request["payload"]["arguments"] - readonly agent?: Endpoint5_11Request["payload"]["agent"] - readonly model?: Endpoint5_11Request["payload"]["model"] - readonly files?: Endpoint5_11Request["payload"]["files"] - readonly agents?: Endpoint5_11Request["payload"]["agents"] - readonly delivery?: Endpoint5_11Request["payload"]["delivery"] - readonly resume?: Endpoint5_11Request["payload"]["resume"] +type Endpoint6_11Request = Parameters[0] +type Endpoint6_11Input = { + readonly sessionID: Endpoint6_11Request["params"]["sessionID"] + readonly id?: Endpoint6_11Request["payload"]["id"] + readonly command: Endpoint6_11Request["payload"]["command"] + readonly arguments?: Endpoint6_11Request["payload"]["arguments"] + readonly agent?: Endpoint6_11Request["payload"]["agent"] + readonly model?: Endpoint6_11Request["payload"]["model"] + readonly files?: Endpoint6_11Request["payload"]["files"] + readonly agents?: Endpoint6_11Request["payload"]["agents"] + readonly delivery?: Endpoint6_11Request["payload"]["delivery"] + readonly resume?: Endpoint6_11Request["payload"]["resume"] } -const Endpoint5_11 = (raw: RawClient["server.session"]) => (input: Endpoint5_11Input) => +const Endpoint6_11 = (raw: RawClient["server.session"]) => (input: Endpoint6_11Input) => raw["session.command"]({ params: { sessionID: input["sessionID"] }, payload: { @@ -218,30 +252,30 @@ const Endpoint5_11 = (raw: RawClient["server.session"]) => (input: Endpoint5_11I Effect.map((value) => value.data), ) -type Endpoint5_12Request = Parameters[0] -type Endpoint5_12Input = { - readonly sessionID: Endpoint5_12Request["params"]["sessionID"] - readonly id?: Endpoint5_12Request["payload"]["id"] - readonly skill: Endpoint5_12Request["payload"]["skill"] - readonly resume?: Endpoint5_12Request["payload"]["resume"] +type Endpoint6_12Request = Parameters[0] +type Endpoint6_12Input = { + readonly sessionID: Endpoint6_12Request["params"]["sessionID"] + readonly id?: Endpoint6_12Request["payload"]["id"] + readonly skill: Endpoint6_12Request["payload"]["skill"] + readonly resume?: Endpoint6_12Request["payload"]["resume"] } -const Endpoint5_12 = (raw: RawClient["server.session"]) => (input: Endpoint5_12Input) => +const Endpoint6_12 = (raw: RawClient["server.session"]) => (input: Endpoint6_12Input) => raw["session.skill"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], skill: input["skill"], resume: input["resume"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_13Request = Parameters[0] -type Endpoint5_13Input = { - readonly sessionID: Endpoint5_13Request["params"]["sessionID"] - readonly id?: Endpoint5_13Request["payload"]["id"] - readonly text: Endpoint5_13Request["payload"]["text"] - readonly description?: Endpoint5_13Request["payload"]["description"] - readonly metadata?: Endpoint5_13Request["payload"]["metadata"] - readonly delivery?: Endpoint5_13Request["payload"]["delivery"] - readonly resume?: Endpoint5_13Request["payload"]["resume"] +type Endpoint6_13Request = Parameters[0] +type Endpoint6_13Input = { + readonly sessionID: Endpoint6_13Request["params"]["sessionID"] + readonly id?: Endpoint6_13Request["payload"]["id"] + readonly text: Endpoint6_13Request["payload"]["text"] + readonly description?: Endpoint6_13Request["payload"]["description"] + readonly metadata?: Endpoint6_13Request["payload"]["metadata"] + readonly delivery?: Endpoint6_13Request["payload"]["delivery"] + readonly resume?: Endpoint6_13Request["payload"]["resume"] } -const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13Input) => +const Endpoint6_13 = (raw: RawClient["server.session"]) => (input: Endpoint6_13Input) => raw["session.synthetic"]({ params: { sessionID: input["sessionID"] }, payload: { @@ -257,41 +291,41 @@ const Endpoint5_13 = (raw: RawClient["server.session"]) => (input: Endpoint5_13I Effect.map((value) => value.data), ) -type Endpoint5_14Request = Parameters[0] -type Endpoint5_14Input = { - readonly sessionID: Endpoint5_14Request["params"]["sessionID"] - readonly id?: Endpoint5_14Request["payload"]["id"] - readonly command: Endpoint5_14Request["payload"]["command"] +type Endpoint6_14Request = Parameters[0] +type Endpoint6_14Input = { + readonly sessionID: Endpoint6_14Request["params"]["sessionID"] + readonly id?: Endpoint6_14Request["payload"]["id"] + readonly command: Endpoint6_14Request["payload"]["command"] } -const Endpoint5_14 = (raw: RawClient["server.session"]) => (input: Endpoint5_14Input) => +const Endpoint6_14 = (raw: RawClient["server.session"]) => (input: Endpoint6_14Input) => raw["session.shell"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], command: input["command"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_15Request = Parameters[0] -type Endpoint5_15Input = { - readonly sessionID: Endpoint5_15Request["params"]["sessionID"] - readonly id?: Endpoint5_15Request["payload"]["id"] +type Endpoint6_15Request = Parameters[0] +type Endpoint6_15Input = { + readonly sessionID: Endpoint6_15Request["params"]["sessionID"] + readonly id?: Endpoint6_15Request["payload"]["id"] } -const Endpoint5_15 = (raw: RawClient["server.session"]) => (input: Endpoint5_15Input) => +const Endpoint6_15 = (raw: RawClient["server.session"]) => (input: Endpoint6_15Input) => raw["session.compact"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint5_16Request = Parameters[0] -type Endpoint5_16Input = { readonly sessionID: Endpoint5_16Request["params"]["sessionID"] } -const Endpoint5_16 = (raw: RawClient["server.session"]) => (input: Endpoint5_16Input) => +type Endpoint6_16Request = Parameters[0] +type Endpoint6_16Input = { readonly sessionID: Endpoint6_16Request["params"]["sessionID"] } +const Endpoint6_16 = (raw: RawClient["server.session"]) => (input: Endpoint6_16Input) => raw["session.wait"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_17Request = Parameters[0] -type Endpoint5_17Input = { - readonly sessionID: Endpoint5_17Request["params"]["sessionID"] - readonly messageID: Endpoint5_17Request["payload"]["messageID"] - readonly files?: Endpoint5_17Request["payload"]["files"] +type Endpoint6_17Request = Parameters[0] +type Endpoint6_17Input = { + readonly sessionID: Endpoint6_17Request["params"]["sessionID"] + readonly messageID: Endpoint6_17Request["payload"]["messageID"] + readonly files?: Endpoint6_17Request["payload"]["files"] } -const Endpoint5_17 = (raw: RawClient["server.session"]) => (input: Endpoint5_17Input) => +const Endpoint6_17 = (raw: RawClient["server.session"]) => (input: Endpoint6_17Input) => raw["session.revert.stage"]({ params: { sessionID: input["sessionID"] }, payload: { messageID: input["messageID"], files: input["files"] }, @@ -300,69 +334,69 @@ const Endpoint5_17 = (raw: RawClient["server.session"]) => (input: Endpoint5_17I Effect.map((value) => value.data), ) -type Endpoint5_18Request = Parameters[0] -type Endpoint5_18Input = { readonly sessionID: Endpoint5_18Request["params"]["sessionID"] } -const Endpoint5_18 = (raw: RawClient["server.session"]) => (input: Endpoint5_18Input) => +type Endpoint6_18Request = Parameters[0] +type Endpoint6_18Input = { readonly sessionID: Endpoint6_18Request["params"]["sessionID"] } +const Endpoint6_18 = (raw: RawClient["server.session"]) => (input: Endpoint6_18Input) => raw["session.revert.clear"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_19Request = Parameters[0] -type Endpoint5_19Input = { readonly sessionID: Endpoint5_19Request["params"]["sessionID"] } -const Endpoint5_19 = (raw: RawClient["server.session"]) => (input: Endpoint5_19Input) => +type Endpoint6_19Request = Parameters[0] +type Endpoint6_19Input = { readonly sessionID: Endpoint6_19Request["params"]["sessionID"] } +const Endpoint6_19 = (raw: RawClient["server.session"]) => (input: Endpoint6_19Input) => raw["session.revert.commit"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_20Request = Parameters[0] -type Endpoint5_20Input = { readonly sessionID: Endpoint5_20Request["params"]["sessionID"] } -const Endpoint5_20 = (raw: RawClient["server.session"]) => (input: Endpoint5_20Input) => +type Endpoint6_20Request = Parameters[0] +type Endpoint6_20Input = { readonly sessionID: Endpoint6_20Request["params"]["sessionID"] } +const Endpoint6_20 = (raw: RawClient["server.session"]) => (input: Endpoint6_20Input) => raw["session.context"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint5_21Request = Parameters[0] -type Endpoint5_21Input = { readonly sessionID: Endpoint5_21Request["params"]["sessionID"] } -const Endpoint5_21 = (raw: RawClient["server.session"]) => (input: Endpoint5_21Input) => +type Endpoint6_21Request = Parameters[0] +type Endpoint6_21Input = { readonly sessionID: Endpoint6_21Request["params"]["sessionID"] } +const Endpoint6_21 = (raw: RawClient["server.session"]) => (input: Endpoint6_21Input) => raw["session.pending.list"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint5_22Request = Parameters[0] -type Endpoint5_22Input = { readonly sessionID: Endpoint5_22Request["params"]["sessionID"] } -const Endpoint5_22 = (raw: RawClient["server.session"]) => (input: Endpoint5_22Input) => +type Endpoint6_22Request = Parameters[0] +type Endpoint6_22Input = { readonly sessionID: Endpoint6_22Request["params"]["sessionID"] } +const Endpoint6_22 = (raw: RawClient["server.session"]) => (input: Endpoint6_22Input) => raw["session.instructions.entry.list"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint5_23Request = Parameters[0] -type Endpoint5_23Input = { - readonly sessionID: Endpoint5_23Request["params"]["sessionID"] - readonly key: Endpoint5_23Request["params"]["key"] - readonly value: Endpoint5_23Request["payload"]["value"] +type Endpoint6_23Request = Parameters[0] +type Endpoint6_23Input = { + readonly sessionID: Endpoint6_23Request["params"]["sessionID"] + readonly key: Endpoint6_23Request["params"]["key"] + readonly value: Endpoint6_23Request["payload"]["value"] } -const Endpoint5_23 = (raw: RawClient["server.session"]) => (input: Endpoint5_23Input) => +const Endpoint6_23 = (raw: RawClient["server.session"]) => (input: Endpoint6_23Input) => raw["session.instructions.entry.put"]({ params: { sessionID: input["sessionID"], key: input["key"] }, payload: { value: input["value"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_24Request = Parameters[0] -type Endpoint5_24Input = { - readonly sessionID: Endpoint5_24Request["params"]["sessionID"] - readonly key: Endpoint5_24Request["params"]["key"] +type Endpoint6_24Request = Parameters[0] +type Endpoint6_24Input = { + readonly sessionID: Endpoint6_24Request["params"]["sessionID"] + readonly key: Endpoint6_24Request["params"]["key"] } -const Endpoint5_24 = (raw: RawClient["server.session"]) => (input: Endpoint5_24Input) => +const Endpoint6_24 = (raw: RawClient["server.session"]) => (input: Endpoint6_24Input) => raw["session.instructions.entry.remove"]({ params: { sessionID: input["sessionID"], key: input["key"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint5_25Request = Parameters[0] -type Endpoint5_25Input = { - readonly sessionID: Endpoint5_25Request["params"]["sessionID"] - readonly after?: Endpoint5_25Request["query"]["after"] - readonly follow?: Endpoint5_25Request["query"]["follow"] +type Endpoint6_25Request = Parameters[0] +type Endpoint6_25Input = { + readonly sessionID: Endpoint6_25Request["params"]["sessionID"] + readonly after?: Endpoint6_25Request["query"]["after"] + readonly follow?: Endpoint6_25Request["query"]["follow"] } -const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25Input) => +const Endpoint6_25 = (raw: RawClient["server.session"]) => (input: Endpoint6_25Input) => Stream.unwrap( raw["session.log"]({ params: { sessionID: input["sessionID"] }, @@ -373,89 +407,89 @@ const Endpoint5_25 = (raw: RawClient["server.session"]) => (input: Endpoint5_25I ), ) -type Endpoint5_26Request = Parameters[0] -type Endpoint5_26Input = { readonly sessionID: Endpoint5_26Request["params"]["sessionID"] } -const Endpoint5_26 = (raw: RawClient["server.session"]) => (input: Endpoint5_26Input) => +type Endpoint6_26Request = Parameters[0] +type Endpoint6_26Input = { readonly sessionID: Endpoint6_26Request["params"]["sessionID"] } +const Endpoint6_26 = (raw: RawClient["server.session"]) => (input: Endpoint6_26Input) => raw["session.interrupt"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_27Request = Parameters[0] -type Endpoint5_27Input = { readonly sessionID: Endpoint5_27Request["params"]["sessionID"] } -const Endpoint5_27 = (raw: RawClient["server.session"]) => (input: Endpoint5_27Input) => +type Endpoint6_27Request = Parameters[0] +type Endpoint6_27Input = { readonly sessionID: Endpoint6_27Request["params"]["sessionID"] } +const Endpoint6_27 = (raw: RawClient["server.session"]) => (input: Endpoint6_27Input) => raw["session.background"]({ params: { sessionID: input["sessionID"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint5_28Request = Parameters[0] -type Endpoint5_28Input = { - readonly sessionID: Endpoint5_28Request["params"]["sessionID"] - readonly messageID: Endpoint5_28Request["params"]["messageID"] +type Endpoint6_28Request = Parameters[0] +type Endpoint6_28Input = { + readonly sessionID: Endpoint6_28Request["params"]["sessionID"] + readonly messageID: Endpoint6_28Request["params"]["messageID"] } -const Endpoint5_28 = (raw: RawClient["server.session"]) => (input: Endpoint5_28Input) => +const Endpoint6_28 = (raw: RawClient["server.session"]) => (input: Endpoint6_28Input) => raw["session.message"]({ params: { sessionID: input["sessionID"], messageID: input["messageID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -const adaptGroup5 = (raw: RawClient["server.session"]) => ({ - list: Endpoint5_0(raw), - create: Endpoint5_1(raw), - active: Endpoint5_2(raw), - get: Endpoint5_3(raw), - remove: Endpoint5_4(raw), - fork: Endpoint5_5(raw), - switchAgent: Endpoint5_6(raw), - switchModel: Endpoint5_7(raw), - rename: Endpoint5_8(raw), - move: Endpoint5_9(raw), - prompt: Endpoint5_10(raw), - command: Endpoint5_11(raw), - skill: Endpoint5_12(raw), - synthetic: Endpoint5_13(raw), - shell: Endpoint5_14(raw), - compact: Endpoint5_15(raw), - wait: Endpoint5_16(raw), - revert: { stage: Endpoint5_17(raw), clear: Endpoint5_18(raw), commit: Endpoint5_19(raw) }, - context: Endpoint5_20(raw), - pending: { list: Endpoint5_21(raw) }, - instructions: { entry: { list: Endpoint5_22(raw), put: Endpoint5_23(raw), remove: Endpoint5_24(raw) } }, - log: Endpoint5_25(raw), - interrupt: Endpoint5_26(raw), - background: Endpoint5_27(raw), - message: Endpoint5_28(raw), +const adaptGroup6 = (raw: RawClient["server.session"]) => ({ + list: Endpoint6_0(raw), + create: Endpoint6_1(raw), + active: Endpoint6_2(raw), + get: Endpoint6_3(raw), + remove: Endpoint6_4(raw), + fork: Endpoint6_5(raw), + switchAgent: Endpoint6_6(raw), + switchModel: Endpoint6_7(raw), + rename: Endpoint6_8(raw), + move: Endpoint6_9(raw), + prompt: Endpoint6_10(raw), + command: Endpoint6_11(raw), + skill: Endpoint6_12(raw), + synthetic: Endpoint6_13(raw), + shell: Endpoint6_14(raw), + compact: Endpoint6_15(raw), + wait: Endpoint6_16(raw), + revert: { stage: Endpoint6_17(raw), clear: Endpoint6_18(raw), commit: Endpoint6_19(raw) }, + context: Endpoint6_20(raw), + pending: { list: Endpoint6_21(raw) }, + instructions: { entry: { list: Endpoint6_22(raw), put: Endpoint6_23(raw), remove: Endpoint6_24(raw) } }, + log: Endpoint6_25(raw), + interrupt: Endpoint6_26(raw), + background: Endpoint6_27(raw), + message: Endpoint6_28(raw), }) -type Endpoint6_0Request = Parameters[0] -type Endpoint6_0Input = { - readonly sessionID: Endpoint6_0Request["params"]["sessionID"] - readonly limit?: Endpoint6_0Request["query"]["limit"] - readonly order?: Endpoint6_0Request["query"]["order"] - readonly cursor?: Endpoint6_0Request["query"]["cursor"] +type Endpoint7_0Request = Parameters[0] +type Endpoint7_0Input = { + readonly sessionID: Endpoint7_0Request["params"]["sessionID"] + readonly limit?: Endpoint7_0Request["query"]["limit"] + readonly order?: Endpoint7_0Request["query"]["order"] + readonly cursor?: Endpoint7_0Request["query"]["cursor"] } -const Endpoint6_0 = (raw: RawClient["server.message"]) => (input: Endpoint6_0Input) => +const Endpoint7_0 = (raw: RawClient["server.message"]) => (input: Endpoint7_0Input) => raw["session.messages"]({ params: { sessionID: input["sessionID"] }, query: { limit: input["limit"], order: input["order"], cursor: input["cursor"] }, }).pipe(Effect.mapError(mapClientError)) -const adaptGroup6 = (raw: RawClient["server.message"]) => ({ list: Endpoint6_0(raw) }) +const adaptGroup7 = (raw: RawClient["server.message"]) => ({ list: Endpoint7_0(raw) }) -type Endpoint7_0Request = Parameters[0] -type Endpoint7_0Input = { readonly location?: Endpoint7_0Request["query"]["location"] } -const Endpoint7_0 = (raw: RawClient["server.model"]) => (input?: Endpoint7_0Input) => +type Endpoint8_0Request = Parameters[0] +type Endpoint8_0Input = { readonly location?: Endpoint8_0Request["query"]["location"] } +const Endpoint8_0 = (raw: RawClient["server.model"]) => (input?: Endpoint8_0Input) => raw["model.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint7_1Request = Parameters[0] -type Endpoint7_1Input = { readonly location?: Endpoint7_1Request["query"]["location"] } -const Endpoint7_1 = (raw: RawClient["server.model"]) => (input?: Endpoint7_1Input) => +type Endpoint8_1Request = Parameters[0] +type Endpoint8_1Input = { readonly location?: Endpoint8_1Request["query"]["location"] } +const Endpoint8_1 = (raw: RawClient["server.model"]) => (input?: Endpoint8_1Input) => raw["model.default"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup7 = (raw: RawClient["server.model"]) => ({ list: Endpoint7_0(raw), default: Endpoint7_1(raw) }) +const adaptGroup8 = (raw: RawClient["server.model"]) => ({ list: Endpoint8_0(raw), default: Endpoint8_1(raw) }) -type Endpoint8_0Request = Parameters[0] -type Endpoint8_0Input = { - readonly location?: Endpoint8_0Request["query"]["location"] - readonly prompt: Endpoint8_0Request["payload"]["prompt"] - readonly model?: Endpoint8_0Request["payload"]["model"] +type Endpoint9_0Request = Parameters[0] +type Endpoint9_0Input = { + readonly location?: Endpoint9_0Request["query"]["location"] + readonly prompt: Endpoint9_0Request["payload"]["prompt"] + readonly model?: Endpoint9_0Request["payload"]["model"] } -const Endpoint8_0 = (raw: RawClient["server.generate"]) => (input: Endpoint8_0Input) => +const Endpoint9_0 = (raw: RawClient["server.generate"]) => (input: Endpoint9_0Input) => raw["generate.text"]({ query: { location: input["location"] }, payload: { prompt: input["prompt"], model: input["model"] }, @@ -464,200 +498,200 @@ const Endpoint8_0 = (raw: RawClient["server.generate"]) => (input: Endpoint8_0In Effect.map((value) => value.data), ) -const adaptGroup8 = (raw: RawClient["server.generate"]) => ({ text: Endpoint8_0(raw) }) +const adaptGroup9 = (raw: RawClient["server.generate"]) => ({ text: Endpoint9_0(raw) }) -type Endpoint9_0Request = Parameters[0] -type Endpoint9_0Input = { readonly location?: Endpoint9_0Request["query"]["location"] } -const Endpoint9_0 = (raw: RawClient["server.provider"]) => (input?: Endpoint9_0Input) => +type Endpoint10_0Request = Parameters[0] +type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["location"] } +const Endpoint10_0 = (raw: RawClient["server.provider"]) => (input?: Endpoint10_0Input) => raw["provider.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint9_1Request = Parameters[0] -type Endpoint9_1Input = { - readonly providerID: Endpoint9_1Request["params"]["providerID"] - readonly location?: Endpoint9_1Request["query"]["location"] +type Endpoint10_1Request = Parameters[0] +type Endpoint10_1Input = { + readonly providerID: Endpoint10_1Request["params"]["providerID"] + readonly location?: Endpoint10_1Request["query"]["location"] } -const Endpoint9_1 = (raw: RawClient["server.provider"]) => (input: Endpoint9_1Input) => +const Endpoint10_1 = (raw: RawClient["server.provider"]) => (input: Endpoint10_1Input) => raw["provider.get"]({ params: { providerID: input["providerID"] }, query: { location: input["location"] } }).pipe( Effect.mapError(mapClientError), ) -const adaptGroup9 = (raw: RawClient["server.provider"]) => ({ list: Endpoint9_0(raw), get: Endpoint9_1(raw) }) +const adaptGroup10 = (raw: RawClient["server.provider"]) => ({ list: Endpoint10_0(raw), get: Endpoint10_1(raw) }) -type Endpoint10_0Request = Parameters[0] -type Endpoint10_0Input = { readonly location?: Endpoint10_0Request["query"]["location"] } -const Endpoint10_0 = (raw: RawClient["server.integration"]) => (input?: Endpoint10_0Input) => +type Endpoint11_0Request = Parameters[0] +type Endpoint11_0Input = { readonly location?: Endpoint11_0Request["query"]["location"] } +const Endpoint11_0 = (raw: RawClient["server.integration"]) => (input?: Endpoint11_0Input) => raw["integration.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint10_1Request = Parameters[0] -type Endpoint10_1Input = { - readonly integrationID: Endpoint10_1Request["params"]["integrationID"] - readonly location?: Endpoint10_1Request["query"]["location"] +type Endpoint11_1Request = Parameters[0] +type Endpoint11_1Input = { + readonly integrationID: Endpoint11_1Request["params"]["integrationID"] + readonly location?: Endpoint11_1Request["query"]["location"] } -const Endpoint10_1 = (raw: RawClient["server.integration"]) => (input: Endpoint10_1Input) => +const Endpoint11_1 = (raw: RawClient["server.integration"]) => (input: Endpoint11_1Input) => raw["integration.get"]({ params: { integrationID: input["integrationID"] }, query: { location: input["location"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint10_2Request = Parameters[0] -type Endpoint10_2Input = { - readonly integrationID: Endpoint10_2Request["params"]["integrationID"] - readonly location?: Endpoint10_2Request["query"]["location"] - readonly key: Endpoint10_2Request["payload"]["key"] - readonly label?: Endpoint10_2Request["payload"]["label"] +type Endpoint11_2Request = Parameters[0] +type Endpoint11_2Input = { + readonly integrationID: Endpoint11_2Request["params"]["integrationID"] + readonly location?: Endpoint11_2Request["query"]["location"] + readonly key: Endpoint11_2Request["payload"]["key"] + readonly label?: Endpoint11_2Request["payload"]["label"] } -const Endpoint10_2 = (raw: RawClient["server.integration"]) => (input: Endpoint10_2Input) => +const Endpoint11_2 = (raw: RawClient["server.integration"]) => (input: Endpoint11_2Input) => raw["integration.connect.key"]({ params: { integrationID: input["integrationID"] }, query: { location: input["location"] }, payload: { key: input["key"], label: input["label"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint10_3Request = Parameters[0] -type Endpoint10_3Input = { - readonly integrationID: Endpoint10_3Request["params"]["integrationID"] - readonly location?: Endpoint10_3Request["query"]["location"] - readonly methodID: Endpoint10_3Request["payload"]["methodID"] - readonly inputs: Endpoint10_3Request["payload"]["inputs"] - readonly label?: Endpoint10_3Request["payload"]["label"] +type Endpoint11_3Request = Parameters[0] +type Endpoint11_3Input = { + readonly integrationID: Endpoint11_3Request["params"]["integrationID"] + readonly location?: Endpoint11_3Request["query"]["location"] + readonly methodID: Endpoint11_3Request["payload"]["methodID"] + readonly inputs: Endpoint11_3Request["payload"]["inputs"] + readonly label?: Endpoint11_3Request["payload"]["label"] } -const Endpoint10_3 = (raw: RawClient["server.integration"]) => (input: Endpoint10_3Input) => +const Endpoint11_3 = (raw: RawClient["server.integration"]) => (input: Endpoint11_3Input) => raw["integration.connect.oauth"]({ params: { integrationID: input["integrationID"] }, query: { location: input["location"] }, payload: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint10_4Request = Parameters[0] -type Endpoint10_4Input = { - readonly attemptID: Endpoint10_4Request["params"]["attemptID"] - readonly location?: Endpoint10_4Request["query"]["location"] +type Endpoint11_4Request = Parameters[0] +type Endpoint11_4Input = { + readonly attemptID: Endpoint11_4Request["params"]["attemptID"] + readonly location?: Endpoint11_4Request["query"]["location"] } -const Endpoint10_4 = (raw: RawClient["server.integration"]) => (input: Endpoint10_4Input) => +const Endpoint11_4 = (raw: RawClient["server.integration"]) => (input: Endpoint11_4Input) => raw["integration.attempt.status"]({ params: { attemptID: input["attemptID"] }, query: { location: input["location"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint10_5Request = Parameters[0] -type Endpoint10_5Input = { - readonly attemptID: Endpoint10_5Request["params"]["attemptID"] - readonly location?: Endpoint10_5Request["query"]["location"] - readonly code?: Endpoint10_5Request["payload"]["code"] +type Endpoint11_5Request = Parameters[0] +type Endpoint11_5Input = { + readonly attemptID: Endpoint11_5Request["params"]["attemptID"] + readonly location?: Endpoint11_5Request["query"]["location"] + readonly code?: Endpoint11_5Request["payload"]["code"] } -const Endpoint10_5 = (raw: RawClient["server.integration"]) => (input: Endpoint10_5Input) => +const Endpoint11_5 = (raw: RawClient["server.integration"]) => (input: Endpoint11_5Input) => raw["integration.attempt.complete"]({ params: { attemptID: input["attemptID"] }, query: { location: input["location"] }, payload: { code: input["code"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint10_6Request = Parameters[0] -type Endpoint10_6Input = { - readonly attemptID: Endpoint10_6Request["params"]["attemptID"] - readonly location?: Endpoint10_6Request["query"]["location"] +type Endpoint11_6Request = Parameters[0] +type Endpoint11_6Input = { + readonly attemptID: Endpoint11_6Request["params"]["attemptID"] + readonly location?: Endpoint11_6Request["query"]["location"] } -const Endpoint10_6 = (raw: RawClient["server.integration"]) => (input: Endpoint10_6Input) => +const Endpoint11_6 = (raw: RawClient["server.integration"]) => (input: Endpoint11_6Input) => raw["integration.attempt.cancel"]({ params: { attemptID: input["attemptID"] }, query: { location: input["location"] }, }).pipe(Effect.mapError(mapClientError)) -const adaptGroup10 = (raw: RawClient["server.integration"]) => ({ - list: Endpoint10_0(raw), - get: Endpoint10_1(raw), - connect: { key: Endpoint10_2(raw), oauth: Endpoint10_3(raw) }, - attempt: { status: Endpoint10_4(raw), complete: Endpoint10_5(raw), cancel: Endpoint10_6(raw) }, +const adaptGroup11 = (raw: RawClient["server.integration"]) => ({ + list: Endpoint11_0(raw), + get: Endpoint11_1(raw), + connect: { key: Endpoint11_2(raw), oauth: Endpoint11_3(raw) }, + attempt: { status: Endpoint11_4(raw), complete: Endpoint11_5(raw), cancel: Endpoint11_6(raw) }, }) -type Endpoint11_0Request = Parameters[0] -type Endpoint11_0Input = { readonly location?: Endpoint11_0Request["query"]["location"] } -const Endpoint11_0 = (raw: RawClient["server.mcp"]) => (input?: Endpoint11_0Input) => +type Endpoint12_0Request = Parameters[0] +type Endpoint12_0Input = { readonly location?: Endpoint12_0Request["query"]["location"] } +const Endpoint12_0 = (raw: RawClient["server.mcp"]) => (input?: Endpoint12_0Input) => raw["mcp.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint11_1Request = Parameters[0] -type Endpoint11_1Input = { readonly location?: Endpoint11_1Request["query"]["location"] } -const Endpoint11_1 = (raw: RawClient["server.mcp"]) => (input?: Endpoint11_1Input) => +type Endpoint12_1Request = Parameters[0] +type Endpoint12_1Input = { readonly location?: Endpoint12_1Request["query"]["location"] } +const Endpoint12_1 = (raw: RawClient["server.mcp"]) => (input?: Endpoint12_1Input) => raw["mcp.resource.catalog"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup11 = (raw: RawClient["server.mcp"]) => ({ - list: Endpoint11_0(raw), - resource: { catalog: Endpoint11_1(raw) }, +const adaptGroup12 = (raw: RawClient["server.mcp"]) => ({ + list: Endpoint12_0(raw), + resource: { catalog: Endpoint12_1(raw) }, }) -type Endpoint12_0Request = Parameters[0] -type Endpoint12_0Input = { - readonly credentialID: Endpoint12_0Request["params"]["credentialID"] - readonly location?: Endpoint12_0Request["query"]["location"] - readonly label: Endpoint12_0Request["payload"]["label"] +type Endpoint13_0Request = Parameters[0] +type Endpoint13_0Input = { + readonly credentialID: Endpoint13_0Request["params"]["credentialID"] + readonly location?: Endpoint13_0Request["query"]["location"] + readonly label: Endpoint13_0Request["payload"]["label"] } -const Endpoint12_0 = (raw: RawClient["server.credential"]) => (input: Endpoint12_0Input) => +const Endpoint13_0 = (raw: RawClient["server.credential"]) => (input: Endpoint13_0Input) => raw["credential.update"]({ params: { credentialID: input["credentialID"] }, query: { location: input["location"] }, payload: { label: input["label"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint12_1Request = Parameters[0] -type Endpoint12_1Input = { - readonly credentialID: Endpoint12_1Request["params"]["credentialID"] - readonly location?: Endpoint12_1Request["query"]["location"] +type Endpoint13_1Request = Parameters[0] +type Endpoint13_1Input = { + readonly credentialID: Endpoint13_1Request["params"]["credentialID"] + readonly location?: Endpoint13_1Request["query"]["location"] } -const Endpoint12_1 = (raw: RawClient["server.credential"]) => (input: Endpoint12_1Input) => +const Endpoint13_1 = (raw: RawClient["server.credential"]) => (input: Endpoint13_1Input) => raw["credential.remove"]({ params: { credentialID: input["credentialID"] }, query: { location: input["location"] }, }).pipe(Effect.mapError(mapClientError)) -const adaptGroup12 = (raw: RawClient["server.credential"]) => ({ update: Endpoint12_0(raw), remove: Endpoint12_1(raw) }) +const adaptGroup13 = (raw: RawClient["server.credential"]) => ({ update: Endpoint13_0(raw), remove: Endpoint13_1(raw) }) -const Endpoint13_0 = (raw: RawClient["server.project"]) => () => +const Endpoint14_0 = (raw: RawClient["server.project"]) => () => raw["project.list"]({}).pipe(Effect.mapError(mapClientError)) -type Endpoint13_1Request = Parameters[0] -type Endpoint13_1Input = { readonly location?: Endpoint13_1Request["query"]["location"] } -const Endpoint13_1 = (raw: RawClient["server.project"]) => (input?: Endpoint13_1Input) => +type Endpoint14_1Request = Parameters[0] +type Endpoint14_1Input = { readonly location?: Endpoint14_1Request["query"]["location"] } +const Endpoint14_1 = (raw: RawClient["server.project"]) => (input?: Endpoint14_1Input) => raw["project.current"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint13_2Request = Parameters[0] -type Endpoint13_2Input = { - readonly projectID: Endpoint13_2Request["params"]["projectID"] - readonly location?: Endpoint13_2Request["query"]["location"] +type Endpoint14_2Request = Parameters[0] +type Endpoint14_2Input = { + readonly projectID: Endpoint14_2Request["params"]["projectID"] + readonly location?: Endpoint14_2Request["query"]["location"] } -const Endpoint13_2 = (raw: RawClient["server.project"]) => (input: Endpoint13_2Input) => +const Endpoint14_2 = (raw: RawClient["server.project"]) => (input: Endpoint14_2Input) => raw["project.directories"]({ params: { projectID: input["projectID"] }, query: { location: input["location"] }, }).pipe(Effect.mapError(mapClientError)) -const adaptGroup13 = (raw: RawClient["server.project"]) => ({ - list: Endpoint13_0(raw), - current: Endpoint13_1(raw), - directories: Endpoint13_2(raw), +const adaptGroup14 = (raw: RawClient["server.project"]) => ({ + list: Endpoint14_0(raw), + current: Endpoint14_1(raw), + directories: Endpoint14_2(raw), }) -type Endpoint14_0Request = Parameters[0] -type Endpoint14_0Input = { readonly location?: Endpoint14_0Request["query"]["location"] } -const Endpoint14_0 = (raw: RawClient["server.form"]) => (input?: Endpoint14_0Input) => +type Endpoint15_0Request = Parameters[0] +type Endpoint15_0Input = { readonly location?: Endpoint15_0Request["query"]["location"] } +const Endpoint15_0 = (raw: RawClient["server.form"]) => (input?: Endpoint15_0Input) => raw["form.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint14_1Request = Parameters[0] -type Endpoint14_1Input = { readonly sessionID: Endpoint14_1Request["params"]["sessionID"] } -const Endpoint14_1 = (raw: RawClient["server.form"]) => (input: Endpoint14_1Input) => +type Endpoint15_1Request = Parameters[0] +type Endpoint15_1Input = { readonly sessionID: Endpoint15_1Request["params"]["sessionID"] } +const Endpoint15_1 = (raw: RawClient["server.form"]) => (input: Endpoint15_1Input) => raw["session.form.list"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint14_2Request = Parameters[0] -type Endpoint14_2Input = { - readonly sessionID: Endpoint14_2Request["params"]["sessionID"] - readonly id?: Endpoint14_2Request["payload"]["id"] - readonly title: Endpoint14_2Request["payload"]["title"] - readonly metadata?: Endpoint14_2Request["payload"]["metadata"] - readonly fields: Endpoint14_2Request["payload"]["fields"] +type Endpoint15_2Request = Parameters[0] +type Endpoint15_2Input = { + readonly sessionID: Endpoint15_2Request["params"]["sessionID"] + readonly id?: Endpoint15_2Request["payload"]["id"] + readonly title: Endpoint15_2Request["payload"]["title"] + readonly metadata?: Endpoint15_2Request["payload"]["metadata"] + readonly fields: Endpoint15_2Request["payload"]["fields"] } -const Endpoint14_2 = (raw: RawClient["server.form"]) => (input: Endpoint14_2Input) => +const Endpoint15_2 = (raw: RawClient["server.form"]) => (input: Endpoint15_2Input) => raw["session.form.create"]({ params: { sessionID: input["sessionID"] }, payload: { id: input["id"], title: input["title"], metadata: input["metadata"], fields: input["fields"] }, @@ -666,90 +700,90 @@ const Endpoint14_2 = (raw: RawClient["server.form"]) => (input: Endpoint14_2Inpu Effect.map((value) => value.data), ) -type Endpoint14_3Request = Parameters[0] -type Endpoint14_3Input = { - readonly sessionID: Endpoint14_3Request["params"]["sessionID"] - readonly formID: Endpoint14_3Request["params"]["formID"] +type Endpoint15_3Request = Parameters[0] +type Endpoint15_3Input = { + readonly sessionID: Endpoint15_3Request["params"]["sessionID"] + readonly formID: Endpoint15_3Request["params"]["formID"] } -const Endpoint14_3 = (raw: RawClient["server.form"]) => (input: Endpoint14_3Input) => +const Endpoint15_3 = (raw: RawClient["server.form"]) => (input: Endpoint15_3Input) => raw["session.form.get"]({ params: { sessionID: input["sessionID"], formID: input["formID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint14_4Request = Parameters[0] -type Endpoint14_4Input = { - readonly sessionID: Endpoint14_4Request["params"]["sessionID"] - readonly formID: Endpoint14_4Request["params"]["formID"] +type Endpoint15_4Request = Parameters[0] +type Endpoint15_4Input = { + readonly sessionID: Endpoint15_4Request["params"]["sessionID"] + readonly formID: Endpoint15_4Request["params"]["formID"] } -const Endpoint14_4 = (raw: RawClient["server.form"]) => (input: Endpoint14_4Input) => +const Endpoint15_4 = (raw: RawClient["server.form"]) => (input: Endpoint15_4Input) => raw["session.form.state"]({ params: { sessionID: input["sessionID"], formID: input["formID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint14_5Request = Parameters[0] -type Endpoint14_5Input = { - readonly sessionID: Endpoint14_5Request["params"]["sessionID"] - readonly formID: Endpoint14_5Request["params"]["formID"] - readonly answer: Endpoint14_5Request["payload"]["answer"] +type Endpoint15_5Request = Parameters[0] +type Endpoint15_5Input = { + readonly sessionID: Endpoint15_5Request["params"]["sessionID"] + readonly formID: Endpoint15_5Request["params"]["formID"] + readonly answer: Endpoint15_5Request["payload"]["answer"] } -const Endpoint14_5 = (raw: RawClient["server.form"]) => (input: Endpoint14_5Input) => +const Endpoint15_5 = (raw: RawClient["server.form"]) => (input: Endpoint15_5Input) => raw["session.form.reply"]({ params: { sessionID: input["sessionID"], formID: input["formID"] }, payload: { answer: input["answer"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint14_6Request = Parameters[0] -type Endpoint14_6Input = { - readonly sessionID: Endpoint14_6Request["params"]["sessionID"] - readonly formID: Endpoint14_6Request["params"]["formID"] +type Endpoint15_6Request = Parameters[0] +type Endpoint15_6Input = { + readonly sessionID: Endpoint15_6Request["params"]["sessionID"] + readonly formID: Endpoint15_6Request["params"]["formID"] } -const Endpoint14_6 = (raw: RawClient["server.form"]) => (input: Endpoint14_6Input) => +const Endpoint15_6 = (raw: RawClient["server.form"]) => (input: Endpoint15_6Input) => raw["session.form.cancel"]({ params: { sessionID: input["sessionID"], formID: input["formID"] } }).pipe( Effect.mapError(mapClientError), ) -const adaptGroup14 = (raw: RawClient["server.form"]) => ({ - request: { list: Endpoint14_0(raw) }, - list: Endpoint14_1(raw), - create: Endpoint14_2(raw), - get: Endpoint14_3(raw), - state: Endpoint14_4(raw), - reply: Endpoint14_5(raw), - cancel: Endpoint14_6(raw), +const adaptGroup15 = (raw: RawClient["server.form"]) => ({ + request: { list: Endpoint15_0(raw) }, + list: Endpoint15_1(raw), + create: Endpoint15_2(raw), + get: Endpoint15_3(raw), + state: Endpoint15_4(raw), + reply: Endpoint15_5(raw), + cancel: Endpoint15_6(raw), }) -type Endpoint15_0Request = Parameters[0] -type Endpoint15_0Input = { readonly location?: Endpoint15_0Request["query"]["location"] } -const Endpoint15_0 = (raw: RawClient["server.permission"]) => (input?: Endpoint15_0Input) => +type Endpoint16_0Request = Parameters[0] +type Endpoint16_0Input = { readonly location?: Endpoint16_0Request["query"]["location"] } +const Endpoint16_0 = (raw: RawClient["server.permission"]) => (input?: Endpoint16_0Input) => raw["permission.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint15_1Request = Parameters[0] -type Endpoint15_1Input = { readonly projectID?: Endpoint15_1Request["query"]["projectID"] } -const Endpoint15_1 = (raw: RawClient["server.permission"]) => (input?: Endpoint15_1Input) => +type Endpoint16_1Request = Parameters[0] +type Endpoint16_1Input = { readonly projectID?: Endpoint16_1Request["query"]["projectID"] } +const Endpoint16_1 = (raw: RawClient["server.permission"]) => (input?: Endpoint16_1Input) => raw["permission.saved.list"]({ query: { projectID: input?.["projectID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint15_2Request = Parameters[0] -type Endpoint15_2Input = { readonly id: Endpoint15_2Request["params"]["id"] } -const Endpoint15_2 = (raw: RawClient["server.permission"]) => (input: Endpoint15_2Input) => +type Endpoint16_2Request = Parameters[0] +type Endpoint16_2Input = { readonly id: Endpoint16_2Request["params"]["id"] } +const Endpoint16_2 = (raw: RawClient["server.permission"]) => (input: Endpoint16_2Input) => raw["permission.saved.remove"]({ params: { id: input["id"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint15_3Request = Parameters[0] -type Endpoint15_3Input = { - readonly sessionID: Endpoint15_3Request["params"]["sessionID"] - readonly id?: Endpoint15_3Request["payload"]["id"] - readonly action: Endpoint15_3Request["payload"]["action"] - readonly resources: Endpoint15_3Request["payload"]["resources"] - readonly save?: Endpoint15_3Request["payload"]["save"] - readonly metadata?: Endpoint15_3Request["payload"]["metadata"] - readonly source?: Endpoint15_3Request["payload"]["source"] - readonly agent?: Endpoint15_3Request["payload"]["agent"] +type Endpoint16_3Request = Parameters[0] +type Endpoint16_3Input = { + readonly sessionID: Endpoint16_3Request["params"]["sessionID"] + readonly id?: Endpoint16_3Request["payload"]["id"] + readonly action: Endpoint16_3Request["payload"]["action"] + readonly resources: Endpoint16_3Request["payload"]["resources"] + readonly save?: Endpoint16_3Request["payload"]["save"] + readonly metadata?: Endpoint16_3Request["payload"]["metadata"] + readonly source?: Endpoint16_3Request["payload"]["source"] + readonly agent?: Endpoint16_3Request["payload"]["agent"] } -const Endpoint15_3 = (raw: RawClient["server.permission"]) => (input: Endpoint15_3Input) => +const Endpoint16_3 = (raw: RawClient["server.permission"]) => (input: Endpoint16_3Input) => raw["session.permission.create"]({ params: { sessionID: input["sessionID"] }, payload: { @@ -766,86 +800,86 @@ const Endpoint15_3 = (raw: RawClient["server.permission"]) => (input: Endpoint15 Effect.map((value) => value.data), ) -type Endpoint15_4Request = Parameters[0] -type Endpoint15_4Input = { readonly sessionID: Endpoint15_4Request["params"]["sessionID"] } -const Endpoint15_4 = (raw: RawClient["server.permission"]) => (input: Endpoint15_4Input) => +type Endpoint16_4Request = Parameters[0] +type Endpoint16_4Input = { readonly sessionID: Endpoint16_4Request["params"]["sessionID"] } +const Endpoint16_4 = (raw: RawClient["server.permission"]) => (input: Endpoint16_4Input) => raw["session.permission.list"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint15_5Request = Parameters[0] -type Endpoint15_5Input = { - readonly sessionID: Endpoint15_5Request["params"]["sessionID"] - readonly requestID: Endpoint15_5Request["params"]["requestID"] +type Endpoint16_5Request = Parameters[0] +type Endpoint16_5Input = { + readonly sessionID: Endpoint16_5Request["params"]["sessionID"] + readonly requestID: Endpoint16_5Request["params"]["requestID"] } -const Endpoint15_5 = (raw: RawClient["server.permission"]) => (input: Endpoint15_5Input) => +const Endpoint16_5 = (raw: RawClient["server.permission"]) => (input: Endpoint16_5Input) => raw["session.permission.get"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint15_6Request = Parameters[0] -type Endpoint15_6Input = { - readonly sessionID: Endpoint15_6Request["params"]["sessionID"] - readonly requestID: Endpoint15_6Request["params"]["requestID"] - readonly reply: Endpoint15_6Request["payload"]["reply"] - readonly message?: Endpoint15_6Request["payload"]["message"] +type Endpoint16_6Request = Parameters[0] +type Endpoint16_6Input = { + readonly sessionID: Endpoint16_6Request["params"]["sessionID"] + readonly requestID: Endpoint16_6Request["params"]["requestID"] + readonly reply: Endpoint16_6Request["payload"]["reply"] + readonly message?: Endpoint16_6Request["payload"]["message"] } -const Endpoint15_6 = (raw: RawClient["server.permission"]) => (input: Endpoint15_6Input) => +const Endpoint16_6 = (raw: RawClient["server.permission"]) => (input: Endpoint16_6Input) => raw["session.permission.reply"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] }, payload: { reply: input["reply"], message: input["message"] }, }).pipe(Effect.mapError(mapClientError)) -const adaptGroup15 = (raw: RawClient["server.permission"]) => ({ - request: { list: Endpoint15_0(raw) }, - saved: { list: Endpoint15_1(raw), remove: Endpoint15_2(raw) }, - create: Endpoint15_3(raw), - list: Endpoint15_4(raw), - get: Endpoint15_5(raw), - reply: Endpoint15_6(raw), +const adaptGroup16 = (raw: RawClient["server.permission"]) => ({ + request: { list: Endpoint16_0(raw) }, + saved: { list: Endpoint16_1(raw), remove: Endpoint16_2(raw) }, + create: Endpoint16_3(raw), + list: Endpoint16_4(raw), + get: Endpoint16_5(raw), + reply: Endpoint16_6(raw), }) -type Endpoint16_0Request = Parameters[0] -type Endpoint16_0Input = { - readonly location?: Endpoint16_0Request["query"]["location"] - readonly path?: Endpoint16_0Request["query"]["path"] +type Endpoint17_0Request = Parameters[0] +type Endpoint17_0Input = { + readonly location?: Endpoint17_0Request["query"]["location"] + readonly path?: Endpoint17_0Request["query"]["path"] } -const Endpoint16_0 = (raw: RawClient["server.fs"]) => (input?: Endpoint16_0Input) => +const Endpoint17_0 = (raw: RawClient["server.fs"]) => (input?: Endpoint17_0Input) => raw["fs.list"]({ query: { location: input?.["location"], path: input?.["path"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint16_1Request = Parameters[0] -type Endpoint16_1Input = { - readonly location?: Endpoint16_1Request["query"]["location"] - readonly query: Endpoint16_1Request["query"]["query"] - readonly type?: Endpoint16_1Request["query"]["type"] - readonly limit?: Endpoint16_1Request["query"]["limit"] +type Endpoint17_1Request = Parameters[0] +type Endpoint17_1Input = { + readonly location?: Endpoint17_1Request["query"]["location"] + readonly query: Endpoint17_1Request["query"]["query"] + readonly type?: Endpoint17_1Request["query"]["type"] + readonly limit?: Endpoint17_1Request["query"]["limit"] } -const Endpoint16_1 = (raw: RawClient["server.fs"]) => (input: Endpoint16_1Input) => +const Endpoint17_1 = (raw: RawClient["server.fs"]) => (input: Endpoint17_1Input) => raw["fs.find"]({ query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] }, }).pipe(Effect.mapError(mapClientError)) -const adaptGroup16 = (raw: RawClient["server.fs"]) => ({ list: Endpoint16_0(raw), find: Endpoint16_1(raw) }) +const adaptGroup17 = (raw: RawClient["server.fs"]) => ({ list: Endpoint17_0(raw), find: Endpoint17_1(raw) }) -type Endpoint17_0Request = Parameters[0] -type Endpoint17_0Input = { readonly location?: Endpoint17_0Request["query"]["location"] } -const Endpoint17_0 = (raw: RawClient["server.command"]) => (input?: Endpoint17_0Input) => +type Endpoint18_0Request = Parameters[0] +type Endpoint18_0Input = { readonly location?: Endpoint18_0Request["query"]["location"] } +const Endpoint18_0 = (raw: RawClient["server.command"]) => (input?: Endpoint18_0Input) => raw["command.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup17 = (raw: RawClient["server.command"]) => ({ list: Endpoint17_0(raw) }) +const adaptGroup18 = (raw: RawClient["server.command"]) => ({ list: Endpoint18_0(raw) }) -type Endpoint18_0Request = Parameters[0] -type Endpoint18_0Input = { readonly location?: Endpoint18_0Request["query"]["location"] } -const Endpoint18_0 = (raw: RawClient["server.skill"]) => (input?: Endpoint18_0Input) => +type Endpoint19_0Request = Parameters[0] +type Endpoint19_0Input = { readonly location?: Endpoint19_0Request["query"]["location"] } +const Endpoint19_0 = (raw: RawClient["server.skill"]) => (input?: Endpoint19_0Input) => raw["skill.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup18 = (raw: RawClient["server.skill"]) => ({ list: Endpoint18_0(raw) }) +const adaptGroup19 = (raw: RawClient["server.skill"]) => ({ list: Endpoint19_0(raw) }) -const Endpoint19_0 = (raw: RawClient["server.event"]) => () => +const Endpoint20_0 = (raw: RawClient["server.event"]) => () => Stream.unwrap( raw["event.subscribe"]({}).pipe( Effect.mapError(mapClientError), @@ -853,23 +887,23 @@ const Endpoint19_0 = (raw: RawClient["server.event"]) => () => ), ) -const adaptGroup19 = (raw: RawClient["server.event"]) => ({ subscribe: Endpoint19_0(raw) }) +const adaptGroup20 = (raw: RawClient["server.event"]) => ({ subscribe: Endpoint20_0(raw) }) -type Endpoint20_0Request = Parameters[0] -type Endpoint20_0Input = { readonly location?: Endpoint20_0Request["query"]["location"] } -const Endpoint20_0 = (raw: RawClient["server.pty"]) => (input?: Endpoint20_0Input) => +type Endpoint21_0Request = Parameters[0] +type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] } +const Endpoint21_0 = (raw: RawClient["server.pty"]) => (input?: Endpoint21_0Input) => raw["pty.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint20_1Request = Parameters[0] -type Endpoint20_1Input = { - readonly location?: Endpoint20_1Request["query"]["location"] - readonly command?: Endpoint20_1Request["payload"]["command"] - readonly args?: Endpoint20_1Request["payload"]["args"] - readonly cwd?: Endpoint20_1Request["payload"]["cwd"] - readonly title?: Endpoint20_1Request["payload"]["title"] - readonly env?: Endpoint20_1Request["payload"]["env"] +type Endpoint21_1Request = Parameters[0] +type Endpoint21_1Input = { + readonly location?: Endpoint21_1Request["query"]["location"] + readonly command?: Endpoint21_1Request["payload"]["command"] + readonly args?: Endpoint21_1Request["payload"]["args"] + readonly cwd?: Endpoint21_1Request["payload"]["cwd"] + readonly title?: Endpoint21_1Request["payload"]["title"] + readonly env?: Endpoint21_1Request["payload"]["env"] } -const Endpoint20_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint20_1Input) => +const Endpoint21_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint21_1Input) => raw["pty.create"]({ query: { location: input?.["location"] }, payload: { @@ -881,275 +915,276 @@ const Endpoint20_1 = (raw: RawClient["server.pty"]) => (input?: Endpoint20_1Inpu }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint20_2Request = Parameters[0] -type Endpoint20_2Input = { - readonly ptyID: Endpoint20_2Request["params"]["ptyID"] - readonly location?: Endpoint20_2Request["query"]["location"] +type Endpoint21_2Request = Parameters[0] +type Endpoint21_2Input = { + readonly ptyID: Endpoint21_2Request["params"]["ptyID"] + readonly location?: Endpoint21_2Request["query"]["location"] } -const Endpoint20_2 = (raw: RawClient["server.pty"]) => (input: Endpoint20_2Input) => +const Endpoint21_2 = (raw: RawClient["server.pty"]) => (input: Endpoint21_2Input) => raw["pty.get"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint20_3Request = Parameters[0] -type Endpoint20_3Input = { - readonly ptyID: Endpoint20_3Request["params"]["ptyID"] - readonly location?: Endpoint20_3Request["query"]["location"] - readonly title?: Endpoint20_3Request["payload"]["title"] - readonly size?: Endpoint20_3Request["payload"]["size"] +type Endpoint21_3Request = Parameters[0] +type Endpoint21_3Input = { + readonly ptyID: Endpoint21_3Request["params"]["ptyID"] + readonly location?: Endpoint21_3Request["query"]["location"] + readonly title?: Endpoint21_3Request["payload"]["title"] + readonly size?: Endpoint21_3Request["payload"]["size"] } -const Endpoint20_3 = (raw: RawClient["server.pty"]) => (input: Endpoint20_3Input) => +const Endpoint21_3 = (raw: RawClient["server.pty"]) => (input: Endpoint21_3Input) => raw["pty.update"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] }, payload: { title: input["title"], size: input["size"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint20_4Request = Parameters[0] -type Endpoint20_4Input = { - readonly ptyID: Endpoint20_4Request["params"]["ptyID"] - readonly location?: Endpoint20_4Request["query"]["location"] +type Endpoint21_4Request = Parameters[0] +type Endpoint21_4Input = { + readonly ptyID: Endpoint21_4Request["params"]["ptyID"] + readonly location?: Endpoint21_4Request["query"]["location"] } -const Endpoint20_4 = (raw: RawClient["server.pty"]) => (input: Endpoint20_4Input) => +const Endpoint21_4 = (raw: RawClient["server.pty"]) => (input: Endpoint21_4Input) => raw["pty.remove"]({ params: { ptyID: input["ptyID"] }, query: { location: input["location"] } }).pipe( Effect.mapError(mapClientError), ) -const adaptGroup20 = (raw: RawClient["server.pty"]) => ({ - list: Endpoint20_0(raw), - create: Endpoint20_1(raw), - get: Endpoint20_2(raw), - update: Endpoint20_3(raw), - remove: Endpoint20_4(raw), +const adaptGroup21 = (raw: RawClient["server.pty"]) => ({ + list: Endpoint21_0(raw), + create: Endpoint21_1(raw), + get: Endpoint21_2(raw), + update: Endpoint21_3(raw), + remove: Endpoint21_4(raw), }) -type Endpoint21_0Request = Parameters[0] -type Endpoint21_0Input = { readonly location?: Endpoint21_0Request["query"]["location"] } -const Endpoint21_0 = (raw: RawClient["server.shell"]) => (input?: Endpoint21_0Input) => +type Endpoint22_0Request = Parameters[0] +type Endpoint22_0Input = { readonly location?: Endpoint22_0Request["query"]["location"] } +const Endpoint22_0 = (raw: RawClient["server.shell"]) => (input?: Endpoint22_0Input) => raw["shell.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint21_1Request = Parameters[0] -type Endpoint21_1Input = { - readonly location?: Endpoint21_1Request["query"]["location"] - readonly command: Endpoint21_1Request["payload"]["command"] - readonly cwd?: Endpoint21_1Request["payload"]["cwd"] - readonly timeout: Endpoint21_1Request["payload"]["timeout"] - readonly metadata?: Endpoint21_1Request["payload"]["metadata"] +type Endpoint22_1Request = Parameters[0] +type Endpoint22_1Input = { + readonly location?: Endpoint22_1Request["query"]["location"] + readonly command: Endpoint22_1Request["payload"]["command"] + readonly cwd?: Endpoint22_1Request["payload"]["cwd"] + readonly timeout: Endpoint22_1Request["payload"]["timeout"] + readonly metadata?: Endpoint22_1Request["payload"]["metadata"] } -const Endpoint21_1 = (raw: RawClient["server.shell"]) => (input: Endpoint21_1Input) => +const Endpoint22_1 = (raw: RawClient["server.shell"]) => (input: Endpoint22_1Input) => raw["shell.create"]({ query: { location: input["location"] }, payload: { command: input["command"], cwd: input["cwd"], timeout: input["timeout"], metadata: input["metadata"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint21_2Request = Parameters[0] -type Endpoint21_2Input = { - readonly id: Endpoint21_2Request["params"]["id"] - readonly location?: Endpoint21_2Request["query"]["location"] +type Endpoint22_2Request = Parameters[0] +type Endpoint22_2Input = { + readonly id: Endpoint22_2Request["params"]["id"] + readonly location?: Endpoint22_2Request["query"]["location"] } -const Endpoint21_2 = (raw: RawClient["server.shell"]) => (input: Endpoint21_2Input) => +const Endpoint22_2 = (raw: RawClient["server.shell"]) => (input: Endpoint22_2Input) => raw["shell.get"]({ params: { id: input["id"] }, query: { location: input["location"] } }).pipe( Effect.mapError(mapClientError), ) -type Endpoint21_3Request = Parameters[0] -type Endpoint21_3Input = { - readonly id: Endpoint21_3Request["params"]["id"] - readonly location?: Endpoint21_3Request["query"]["location"] - readonly timeout: Endpoint21_3Request["payload"]["timeout"] +type Endpoint22_3Request = Parameters[0] +type Endpoint22_3Input = { + readonly id: Endpoint22_3Request["params"]["id"] + readonly location?: Endpoint22_3Request["query"]["location"] + readonly timeout: Endpoint22_3Request["payload"]["timeout"] } -const Endpoint21_3 = (raw: RawClient["server.shell"]) => (input: Endpoint21_3Input) => +const Endpoint22_3 = (raw: RawClient["server.shell"]) => (input: Endpoint22_3Input) => raw["shell.timeout"]({ params: { id: input["id"] }, query: { location: input["location"] }, payload: { timeout: input["timeout"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint21_4Request = Parameters[0] -type Endpoint21_4Input = { - readonly id: Endpoint21_4Request["params"]["id"] - readonly location?: Endpoint21_4Request["query"]["location"] - readonly cursor?: Endpoint21_4Request["query"]["cursor"] - readonly limit?: Endpoint21_4Request["query"]["limit"] +type Endpoint22_4Request = Parameters[0] +type Endpoint22_4Input = { + readonly id: Endpoint22_4Request["params"]["id"] + readonly location?: Endpoint22_4Request["query"]["location"] + readonly cursor?: Endpoint22_4Request["query"]["cursor"] + readonly limit?: Endpoint22_4Request["query"]["limit"] } -const Endpoint21_4 = (raw: RawClient["server.shell"]) => (input: Endpoint21_4Input) => +const Endpoint22_4 = (raw: RawClient["server.shell"]) => (input: Endpoint22_4Input) => raw["shell.output"]({ params: { id: input["id"] }, query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint21_5Request = Parameters[0] -type Endpoint21_5Input = { - readonly id: Endpoint21_5Request["params"]["id"] - readonly location?: Endpoint21_5Request["query"]["location"] +type Endpoint22_5Request = Parameters[0] +type Endpoint22_5Input = { + readonly id: Endpoint22_5Request["params"]["id"] + readonly location?: Endpoint22_5Request["query"]["location"] } -const Endpoint21_5 = (raw: RawClient["server.shell"]) => (input: Endpoint21_5Input) => +const Endpoint22_5 = (raw: RawClient["server.shell"]) => (input: Endpoint22_5Input) => raw["shell.remove"]({ params: { id: input["id"] }, query: { location: input["location"] } }).pipe( Effect.mapError(mapClientError), ) -const adaptGroup21 = (raw: RawClient["server.shell"]) => ({ - list: Endpoint21_0(raw), - create: Endpoint21_1(raw), - get: Endpoint21_2(raw), - timeout: Endpoint21_3(raw), - output: Endpoint21_4(raw), - remove: Endpoint21_5(raw), +const adaptGroup22 = (raw: RawClient["server.shell"]) => ({ + list: Endpoint22_0(raw), + create: Endpoint22_1(raw), + get: Endpoint22_2(raw), + timeout: Endpoint22_3(raw), + output: Endpoint22_4(raw), + remove: Endpoint22_5(raw), }) -type Endpoint22_0Request = Parameters[0] -type Endpoint22_0Input = { readonly location?: Endpoint22_0Request["query"]["location"] } -const Endpoint22_0 = (raw: RawClient["server.question"]) => (input?: Endpoint22_0Input) => +type Endpoint23_0Request = Parameters[0] +type Endpoint23_0Input = { readonly location?: Endpoint23_0Request["query"]["location"] } +const Endpoint23_0 = (raw: RawClient["server.question"]) => (input?: Endpoint23_0Input) => raw["question.request.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint22_1Request = Parameters[0] -type Endpoint22_1Input = { readonly sessionID: Endpoint22_1Request["params"]["sessionID"] } -const Endpoint22_1 = (raw: RawClient["server.question"]) => (input: Endpoint22_1Input) => +type Endpoint23_1Request = Parameters[0] +type Endpoint23_1Input = { readonly sessionID: Endpoint23_1Request["params"]["sessionID"] } +const Endpoint23_1 = (raw: RawClient["server.question"]) => (input: Endpoint23_1Input) => raw["session.question.list"]({ params: { sessionID: input["sessionID"] } }).pipe( Effect.mapError(mapClientError), Effect.map((value) => value.data), ) -type Endpoint22_2Request = Parameters[0] -type Endpoint22_2Input = { - readonly sessionID: Endpoint22_2Request["params"]["sessionID"] - readonly requestID: Endpoint22_2Request["params"]["requestID"] - readonly answers: Endpoint22_2Request["payload"]["answers"] +type Endpoint23_2Request = Parameters[0] +type Endpoint23_2Input = { + readonly sessionID: Endpoint23_2Request["params"]["sessionID"] + readonly requestID: Endpoint23_2Request["params"]["requestID"] + readonly answers: Endpoint23_2Request["payload"]["answers"] } -const Endpoint22_2 = (raw: RawClient["server.question"]) => (input: Endpoint22_2Input) => +const Endpoint23_2 = (raw: RawClient["server.question"]) => (input: Endpoint23_2Input) => raw["session.question.reply"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] }, payload: { answers: input["answers"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint22_3Request = Parameters[0] -type Endpoint22_3Input = { - readonly sessionID: Endpoint22_3Request["params"]["sessionID"] - readonly requestID: Endpoint22_3Request["params"]["requestID"] +type Endpoint23_3Request = Parameters[0] +type Endpoint23_3Input = { + readonly sessionID: Endpoint23_3Request["params"]["sessionID"] + readonly requestID: Endpoint23_3Request["params"]["requestID"] } -const Endpoint22_3 = (raw: RawClient["server.question"]) => (input: Endpoint22_3Input) => +const Endpoint23_3 = (raw: RawClient["server.question"]) => (input: Endpoint23_3Input) => raw["session.question.reject"]({ params: { sessionID: input["sessionID"], requestID: input["requestID"] } }).pipe( Effect.mapError(mapClientError), ) -const adaptGroup22 = (raw: RawClient["server.question"]) => ({ - request: { list: Endpoint22_0(raw) }, - list: Endpoint22_1(raw), - reply: Endpoint22_2(raw), - reject: Endpoint22_3(raw), +const adaptGroup23 = (raw: RawClient["server.question"]) => ({ + request: { list: Endpoint23_0(raw) }, + list: Endpoint23_1(raw), + reply: Endpoint23_2(raw), + reject: Endpoint23_3(raw), }) -type Endpoint23_0Request = Parameters[0] -type Endpoint23_0Input = { readonly location?: Endpoint23_0Request["query"]["location"] } -const Endpoint23_0 = (raw: RawClient["server.reference"]) => (input?: Endpoint23_0Input) => +type Endpoint24_0Request = Parameters[0] +type Endpoint24_0Input = { readonly location?: Endpoint24_0Request["query"]["location"] } +const Endpoint24_0 = (raw: RawClient["server.reference"]) => (input?: Endpoint24_0Input) => raw["reference.list"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup23 = (raw: RawClient["server.reference"]) => ({ list: Endpoint23_0(raw) }) +const adaptGroup24 = (raw: RawClient["server.reference"]) => ({ list: Endpoint24_0(raw) }) -type Endpoint24_0Request = Parameters[0] -type Endpoint24_0Input = { - readonly projectID: Endpoint24_0Request["params"]["projectID"] - readonly location?: Endpoint24_0Request["query"]["location"] - readonly strategy: Endpoint24_0Request["payload"]["strategy"] - readonly directory: Endpoint24_0Request["payload"]["directory"] - readonly name?: Endpoint24_0Request["payload"]["name"] +type Endpoint25_0Request = Parameters[0] +type Endpoint25_0Input = { + readonly projectID: Endpoint25_0Request["params"]["projectID"] + readonly location?: Endpoint25_0Request["query"]["location"] + readonly strategy: Endpoint25_0Request["payload"]["strategy"] + readonly directory: Endpoint25_0Request["payload"]["directory"] + readonly name?: Endpoint25_0Request["payload"]["name"] } -const Endpoint24_0 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint24_0Input) => +const Endpoint25_0 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint25_0Input) => raw["projectCopy.create"]({ params: { projectID: input["projectID"] }, query: { location: input["location"] }, payload: { strategy: input["strategy"], directory: input["directory"], name: input["name"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint24_1Request = Parameters[0] -type Endpoint24_1Input = { - readonly projectID: Endpoint24_1Request["params"]["projectID"] - readonly location?: Endpoint24_1Request["query"]["location"] - readonly directory: Endpoint24_1Request["payload"]["directory"] - readonly force: Endpoint24_1Request["payload"]["force"] +type Endpoint25_1Request = Parameters[0] +type Endpoint25_1Input = { + readonly projectID: Endpoint25_1Request["params"]["projectID"] + readonly location?: Endpoint25_1Request["query"]["location"] + readonly directory: Endpoint25_1Request["payload"]["directory"] + readonly force: Endpoint25_1Request["payload"]["force"] } -const Endpoint24_1 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint24_1Input) => +const Endpoint25_1 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint25_1Input) => raw["projectCopy.remove"]({ params: { projectID: input["projectID"] }, query: { location: input["location"] }, payload: { directory: input["directory"], force: input["force"] }, }).pipe(Effect.mapError(mapClientError)) -type Endpoint24_2Request = Parameters[0] -type Endpoint24_2Input = { - readonly projectID: Endpoint24_2Request["params"]["projectID"] - readonly location?: Endpoint24_2Request["query"]["location"] +type Endpoint25_2Request = Parameters[0] +type Endpoint25_2Input = { + readonly projectID: Endpoint25_2Request["params"]["projectID"] + readonly location?: Endpoint25_2Request["query"]["location"] } -const Endpoint24_2 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint24_2Input) => +const Endpoint25_2 = (raw: RawClient["server.projectCopy"]) => (input: Endpoint25_2Input) => raw["projectCopy.refresh"]({ params: { projectID: input["projectID"] }, query: { location: input["location"] }, }).pipe(Effect.mapError(mapClientError)) -const adaptGroup24 = (raw: RawClient["server.projectCopy"]) => ({ - create: Endpoint24_0(raw), - remove: Endpoint24_1(raw), - refresh: Endpoint24_2(raw), +const adaptGroup25 = (raw: RawClient["server.projectCopy"]) => ({ + create: Endpoint25_0(raw), + remove: Endpoint25_1(raw), + refresh: Endpoint25_2(raw), }) -type Endpoint25_0Request = Parameters[0] -type Endpoint25_0Input = { readonly location?: Endpoint25_0Request["query"]["location"] } -const Endpoint25_0 = (raw: RawClient["server.vcs"]) => (input?: Endpoint25_0Input) => +type Endpoint26_0Request = Parameters[0] +type Endpoint26_0Input = { readonly location?: Endpoint26_0Request["query"]["location"] } +const Endpoint26_0 = (raw: RawClient["server.vcs"]) => (input?: Endpoint26_0Input) => raw["vcs.status"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -type Endpoint25_1Request = Parameters[0] -type Endpoint25_1Input = { - readonly location?: Endpoint25_1Request["query"]["location"] - readonly mode: Endpoint25_1Request["query"]["mode"] - readonly context?: Endpoint25_1Request["query"]["context"] +type Endpoint26_1Request = Parameters[0] +type Endpoint26_1Input = { + readonly location?: Endpoint26_1Request["query"]["location"] + readonly mode: Endpoint26_1Request["query"]["mode"] + readonly context?: Endpoint26_1Request["query"]["context"] } -const Endpoint25_1 = (raw: RawClient["server.vcs"]) => (input: Endpoint25_1Input) => +const Endpoint26_1 = (raw: RawClient["server.vcs"]) => (input: Endpoint26_1Input) => raw["vcs.diff"]({ query: { location: input["location"], mode: input["mode"], context: input["context"] } }).pipe( Effect.mapError(mapClientError), ) -const adaptGroup25 = (raw: RawClient["server.vcs"]) => ({ status: Endpoint25_0(raw), diff: Endpoint25_1(raw) }) +const adaptGroup26 = (raw: RawClient["server.vcs"]) => ({ status: Endpoint26_0(raw), diff: Endpoint26_1(raw) }) -const Endpoint26_0 = (raw: RawClient["server.debug"]) => () => +const Endpoint27_0 = (raw: RawClient["server.debug"]) => () => raw["debug.location"]({}).pipe(Effect.mapError(mapClientError)) -type Endpoint26_1Request = Parameters[0] -type Endpoint26_1Input = { readonly location?: Endpoint26_1Request["query"]["location"] } -const Endpoint26_1 = (raw: RawClient["server.debug"]) => (input?: Endpoint26_1Input) => +type Endpoint27_1Request = Parameters[0] +type Endpoint27_1Input = { readonly location?: Endpoint27_1Request["query"]["location"] } +const Endpoint27_1 = (raw: RawClient["server.debug"]) => (input?: Endpoint27_1Input) => raw["debug.location.evict"]({ query: { location: input?.["location"] } }).pipe(Effect.mapError(mapClientError)) -const adaptGroup26 = (raw: RawClient["server.debug"]) => ({ - location: { list: Endpoint26_0(raw), evict: Endpoint26_1(raw) }, +const adaptGroup27 = (raw: RawClient["server.debug"]) => ({ + location: { list: Endpoint27_0(raw), evict: Endpoint27_1(raw) }, }) const adaptClient = (raw: RawClient) => ({ health: adaptGroup0(raw["server.health"]), server: adaptGroup1(raw["server.server"]), - location: adaptGroup2(raw["server.location"]), - agent: adaptGroup3(raw["server.agent"]), - plugin: adaptGroup4(raw["server.plugin"]), - session: adaptGroup5(raw["server.session"]), - message: adaptGroup6(raw["server.message"]), - model: adaptGroup7(raw["server.model"]), - generate: adaptGroup8(raw["server.generate"]), - provider: adaptGroup9(raw["server.provider"]), - integration: adaptGroup10(raw["server.integration"]), - mcp: adaptGroup11(raw["server.mcp"]), - credential: adaptGroup12(raw["server.credential"]), - project: adaptGroup13(raw["server.project"]), - form: adaptGroup14(raw["server.form"]), - permission: adaptGroup15(raw["server.permission"]), - file: adaptGroup16(raw["server.fs"]), - command: adaptGroup17(raw["server.command"]), - skill: adaptGroup18(raw["server.skill"]), - event: adaptGroup19(raw["server.event"]), - pty: adaptGroup20(raw["server.pty"]), - shell: adaptGroup21(raw["server.shell"]), - question: adaptGroup22(raw["server.question"]), - reference: adaptGroup23(raw["server.reference"]), - projectCopy: adaptGroup24(raw["server.projectCopy"]), - vcs: adaptGroup25(raw["server.vcs"]), - debug: adaptGroup26(raw["server.debug"]), + pairing: adaptGroup2(raw["server.pairing"]), + location: adaptGroup3(raw["server.location"]), + agent: adaptGroup4(raw["server.agent"]), + plugin: adaptGroup5(raw["server.plugin"]), + session: adaptGroup6(raw["server.session"]), + message: adaptGroup7(raw["server.message"]), + model: adaptGroup8(raw["server.model"]), + generate: adaptGroup9(raw["server.generate"]), + provider: adaptGroup10(raw["server.provider"]), + integration: adaptGroup11(raw["server.integration"]), + mcp: adaptGroup12(raw["server.mcp"]), + credential: adaptGroup13(raw["server.credential"]), + project: adaptGroup14(raw["server.project"]), + form: adaptGroup15(raw["server.form"]), + permission: adaptGroup16(raw["server.permission"]), + file: adaptGroup17(raw["server.fs"]), + command: adaptGroup18(raw["server.command"]), + skill: adaptGroup19(raw["server.skill"]), + event: adaptGroup20(raw["server.event"]), + pty: adaptGroup21(raw["server.pty"]), + shell: adaptGroup22(raw["server.shell"]), + question: adaptGroup23(raw["server.question"]), + reference: adaptGroup24(raw["server.reference"]), + projectCopy: adaptGroup25(raw["server.projectCopy"]), + vcs: adaptGroup26(raw["server.vcs"]), + debug: adaptGroup27(raw["server.debug"]), }) export const make = (options?: { readonly baseUrl?: URL | string }) => diff --git a/packages/client/src/promise/generated/client.ts b/packages/client/src/promise/generated/client.ts index bed325d97689..c34e2b031933 100644 --- a/packages/client/src/promise/generated/client.ts +++ b/packages/client/src/promise/generated/client.ts @@ -1,6 +1,12 @@ import type { HealthGetOutput, ServerGetOutput, + PairingInvitationCreateOutput, + PairingRedeemInput, + PairingRedeemOutput, + PairingDeviceListOutput, + PairingDeviceRevokeInput, + PairingDeviceRevokeOutput, LocationGetInput, LocationGetOutput, AgentListInput, @@ -329,17 +335,73 @@ export function make(options: ClientOptions) { health: { get: (requestOptions?: RequestOptions) => request( - { method: "GET", path: `/api/health`, successStatus: 200, declaredStatuses: [401, 400], empty: false }, + { method: "GET", path: `/api/health`, successStatus: 200, declaredStatuses: [401, 403, 400], empty: false }, requestOptions, ), }, server: { get: (requestOptions?: RequestOptions) => request( - { method: "GET", path: `/api/server`, successStatus: 200, declaredStatuses: [401, 400], empty: false }, + { method: "GET", path: `/api/server`, successStatus: 200, declaredStatuses: [401, 403, 400], empty: false }, requestOptions, ), }, + pairing: { + invitation: { + create: (requestOptions?: RequestOptions) => + request( + { + method: "POST", + path: `/api/pairing/invitation`, + successStatus: 200, + declaredStatuses: [401, 403, 503, 400], + empty: false, + }, + requestOptions, + ), + }, + redeem: (input: PairingRedeemInput, requestOptions?: RequestOptions) => + request( + { + method: "POST", + path: `/api/pairing/redeem`, + body: { + token: input["token"], + requestID: input["requestID"], + deviceName: input["deviceName"], + credential: input["credential"], + }, + successStatus: 200, + declaredStatuses: [400, 409, 410, 401, 403], + empty: false, + }, + requestOptions, + ), + device: { + list: (requestOptions?: RequestOptions) => + request( + { + method: "GET", + path: `/api/pairing/device`, + successStatus: 200, + declaredStatuses: [401, 403, 400], + empty: false, + }, + requestOptions, + ), + revoke: (input: PairingDeviceRevokeInput, requestOptions?: RequestOptions) => + request( + { + method: "DELETE", + path: `/api/pairing/device/${encodeURIComponent(input.deviceID)}`, + successStatus: 204, + declaredStatuses: [401, 403, 404, 400], + empty: true, + }, + requestOptions, + ), + }, + }, location: { get: (input?: LocationGetInput, requestOptions?: RequestOptions) => request( @@ -348,7 +410,7 @@ export function make(options: ClientOptions) { path: `/api/location`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -362,7 +424,7 @@ export function make(options: ClientOptions) { path: `/api/agent`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -376,7 +438,7 @@ export function make(options: ClientOptions) { path: `/api/plugin`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -400,7 +462,7 @@ export function make(options: ClientOptions) { cursor: input?.["cursor"], }, successStatus: 200, - declaredStatuses: [400, 401], + declaredStatuses: [400, 401, 403], empty: false, }, requestOptions, @@ -417,7 +479,7 @@ export function make(options: ClientOptions) { location: input?.["location"], }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -428,7 +490,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/active`, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -439,7 +501,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -450,7 +512,7 @@ export function make(options: ClientOptions) { method: "DELETE", path: `/api/session/${encodeURIComponent(input.sessionID)}`, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -462,7 +524,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/fork`, body: { messageID: input["messageID"] }, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -474,7 +536,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/agent`, body: { agent: input["agent"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -486,7 +548,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/model`, body: { model: input["model"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -498,7 +560,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/rename`, body: { title: input["title"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -510,7 +572,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/move`, body: { destination: input["destination"], moveChanges: input["moveChanges"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -530,7 +592,7 @@ export function make(options: ClientOptions) { resume: input["resume"], }, successStatus: 200, - declaredStatuses: [409, 400, 404, 401], + declaredStatuses: [409, 400, 404, 401, 403], empty: false, }, requestOptions, @@ -552,7 +614,7 @@ export function make(options: ClientOptions) { resume: input["resume"], }, successStatus: 200, - declaredStatuses: [409, 400, 404, 500, 401], + declaredStatuses: [409, 400, 404, 500, 401, 403], empty: false, }, requestOptions, @@ -564,7 +626,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/skill`, body: { id: input["id"], skill: input["skill"], resume: input["resume"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -583,7 +645,7 @@ export function make(options: ClientOptions) { resume: input["resume"], }, successStatus: 200, - declaredStatuses: [409, 404, 400, 401], + declaredStatuses: [409, 404, 400, 401, 403], empty: false, }, requestOptions, @@ -595,7 +657,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/shell`, body: { id: input["id"], command: input["command"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -607,7 +669,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/compact`, body: { id: input["id"] }, successStatus: 200, - declaredStatuses: [409, 404, 400, 401], + declaredStatuses: [409, 404, 400, 401, 403], empty: false, }, requestOptions, @@ -618,7 +680,7 @@ export function make(options: ClientOptions) { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/wait`, successStatus: 204, - declaredStatuses: [404, 503, 400, 401], + declaredStatuses: [404, 503, 400, 401, 403], empty: true, }, requestOptions, @@ -631,7 +693,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/stage`, body: { messageID: input["messageID"], files: input["files"] }, successStatus: 200, - declaredStatuses: [404, 409, 500, 400, 401], + declaredStatuses: [404, 409, 500, 400, 401, 403], empty: false, }, requestOptions, @@ -642,7 +704,7 @@ export function make(options: ClientOptions) { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/clear`, successStatus: 204, - declaredStatuses: [404, 409, 500, 400, 401], + declaredStatuses: [404, 409, 500, 400, 401, 403], empty: true, }, requestOptions, @@ -653,7 +715,7 @@ export function make(options: ClientOptions) { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/revert/commit`, successStatus: 204, - declaredStatuses: [404, 409, 400, 401], + declaredStatuses: [404, 409, 400, 401, 403], empty: true, }, requestOptions, @@ -665,7 +727,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/context`, successStatus: 200, - declaredStatuses: [404, 500, 400, 401], + declaredStatuses: [404, 500, 400, 401, 403], empty: false, }, requestOptions, @@ -677,7 +739,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/pending`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -691,7 +753,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/instructions/entries`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -703,7 +765,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/instructions/entries/${encodeURIComponent(input.key)}`, body: { value: input["value"] }, successStatus: 204, - declaredStatuses: [404, 413, 400, 401], + declaredStatuses: [404, 413, 400, 401, 403], empty: true, }, requestOptions, @@ -714,7 +776,7 @@ export function make(options: ClientOptions) { method: "DELETE", path: `/api/session/${encodeURIComponent(input.sessionID)}/instructions/entries/${encodeURIComponent(input.key)}`, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -728,7 +790,7 @@ export function make(options: ClientOptions) { path: `/api/experimental/session/${encodeURIComponent(input.sessionID)}/log`, query: { after: input["after"], follow: input["follow"] }, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -739,7 +801,7 @@ export function make(options: ClientOptions) { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/interrupt`, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -750,7 +812,7 @@ export function make(options: ClientOptions) { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/background`, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -761,7 +823,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/message/${encodeURIComponent(input.messageID)}`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -775,7 +837,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/message`, query: { limit: input["limit"], order: input["order"], cursor: input["cursor"] }, successStatus: 200, - declaredStatuses: [400, 404, 500, 401], + declaredStatuses: [400, 404, 500, 401, 403], empty: false, }, requestOptions, @@ -789,7 +851,7 @@ export function make(options: ClientOptions) { path: `/api/model`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [503, 401, 400], + declaredStatuses: [503, 401, 403, 400], empty: false, }, requestOptions, @@ -801,7 +863,7 @@ export function make(options: ClientOptions) { path: `/api/model/default`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [503, 401, 400], + declaredStatuses: [503, 401, 403, 400], empty: false, }, requestOptions, @@ -816,7 +878,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { prompt: input["prompt"], model: input["model"] }, successStatus: 200, - declaredStatuses: [400, 503, 401], + declaredStatuses: [400, 503, 401, 403], empty: false, }, requestOptions, @@ -830,7 +892,7 @@ export function make(options: ClientOptions) { path: `/api/provider`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [503, 401, 400], + declaredStatuses: [503, 401, 403, 400], empty: false, }, requestOptions, @@ -842,7 +904,7 @@ export function make(options: ClientOptions) { path: `/api/provider/${encodeURIComponent(input.providerID)}`, query: { location: input["location"] }, successStatus: 200, - declaredStatuses: [404, 503, 401, 400], + declaredStatuses: [404, 503, 401, 403, 400], empty: false, }, requestOptions, @@ -856,7 +918,7 @@ export function make(options: ClientOptions) { path: `/api/integration`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -868,7 +930,7 @@ export function make(options: ClientOptions) { path: `/api/integration/${encodeURIComponent(input.integrationID)}`, query: { location: input["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -882,7 +944,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { key: input["key"], label: input["label"] }, successStatus: 204, - declaredStatuses: [400, 401], + declaredStatuses: [400, 401, 403], empty: true, }, requestOptions, @@ -895,7 +957,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { methodID: input["methodID"], inputs: input["inputs"], label: input["label"] }, successStatus: 200, - declaredStatuses: [400, 401], + declaredStatuses: [400, 401, 403], empty: false, }, requestOptions, @@ -909,7 +971,7 @@ export function make(options: ClientOptions) { path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, query: { location: input["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -922,7 +984,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { code: input["code"] }, successStatus: 204, - declaredStatuses: [400, 401], + declaredStatuses: [400, 401, 403], empty: true, }, requestOptions, @@ -934,7 +996,7 @@ export function make(options: ClientOptions) { path: `/api/integration/attempt/${encodeURIComponent(input.attemptID)}`, query: { location: input["location"] }, successStatus: 204, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: true, }, requestOptions, @@ -949,7 +1011,7 @@ export function make(options: ClientOptions) { path: `/api/mcp`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -962,7 +1024,7 @@ export function make(options: ClientOptions) { path: `/api/mcp/resource`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -978,7 +1040,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { label: input["label"] }, successStatus: 204, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: true, }, requestOptions, @@ -990,7 +1052,7 @@ export function make(options: ClientOptions) { path: `/api/credential/${encodeURIComponent(input.credentialID)}`, query: { location: input["location"] }, successStatus: 204, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: true, }, requestOptions, @@ -999,7 +1061,7 @@ export function make(options: ClientOptions) { project: { list: (requestOptions?: RequestOptions) => request( - { method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [401, 400], empty: false }, + { method: "GET", path: `/api/project`, successStatus: 200, declaredStatuses: [401, 403, 400], empty: false }, requestOptions, ), current: (input?: ProjectCurrentInput, requestOptions?: RequestOptions) => @@ -1009,7 +1071,7 @@ export function make(options: ClientOptions) { path: `/api/project/current`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1021,7 +1083,7 @@ export function make(options: ClientOptions) { path: `/api/project/${encodeURIComponent(input.projectID)}/directories`, query: { location: input["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1036,7 +1098,7 @@ export function make(options: ClientOptions) { path: `/api/form/request`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1048,7 +1110,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/form`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -1060,7 +1122,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/form`, body: { id: input["id"], title: input["title"], metadata: input["metadata"], fields: input["fields"] }, successStatus: 200, - declaredStatuses: [404, 409, 400, 401], + declaredStatuses: [404, 409, 400, 401, 403], empty: false, }, requestOptions, @@ -1071,7 +1133,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -1082,7 +1144,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/state`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -1094,7 +1156,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/reply`, body: { answer: input["answer"] }, successStatus: 204, - declaredStatuses: [404, 409, 400, 401], + declaredStatuses: [404, 409, 400, 401, 403], empty: true, }, requestOptions, @@ -1105,7 +1167,7 @@ export function make(options: ClientOptions) { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/form/${encodeURIComponent(input.formID)}/cancel`, successStatus: 204, - declaredStatuses: [404, 409, 400, 401], + declaredStatuses: [404, 409, 400, 401, 403], empty: true, }, requestOptions, @@ -1120,7 +1182,7 @@ export function make(options: ClientOptions) { path: `/api/permission/request`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1134,7 +1196,7 @@ export function make(options: ClientOptions) { path: `/api/permission/saved`, query: { projectID: input?.["projectID"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1145,7 +1207,7 @@ export function make(options: ClientOptions) { method: "DELETE", path: `/api/permission/saved/${encodeURIComponent(input.id)}`, successStatus: 204, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: true, }, requestOptions, @@ -1166,7 +1228,7 @@ export function make(options: ClientOptions) { agent: input["agent"], }, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -1177,7 +1239,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/permission`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -1188,7 +1250,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -1200,7 +1262,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/permission/${encodeURIComponent(input.requestID)}/reply`, body: { reply: input["reply"], message: input["message"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -1214,7 +1276,7 @@ export function make(options: ClientOptions) { path: `/api/fs/read/${encodePath(input.path)}`, query: { location: input["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, binary: true, }, @@ -1227,7 +1289,7 @@ export function make(options: ClientOptions) { path: `/api/fs/list`, query: { location: input?.["location"], path: input?.["path"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1239,7 +1301,7 @@ export function make(options: ClientOptions) { path: `/api/fs/find`, query: { location: input["location"], query: input["query"], type: input["type"], limit: input["limit"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1253,7 +1315,7 @@ export function make(options: ClientOptions) { path: `/api/command`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1267,7 +1329,7 @@ export function make(options: ClientOptions) { path: `/api/skill`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1276,7 +1338,7 @@ export function make(options: ClientOptions) { event: { subscribe: (requestOptions?: RequestOptions): AsyncIterable => sse( - { method: "GET", path: `/api/event`, successStatus: 200, declaredStatuses: [401, 400], empty: false }, + { method: "GET", path: `/api/event`, successStatus: 200, declaredStatuses: [401, 403, 400], empty: false }, requestOptions, ), }, @@ -1288,7 +1350,7 @@ export function make(options: ClientOptions) { path: `/api/pty`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1307,7 +1369,7 @@ export function make(options: ClientOptions) { env: input?.["env"], }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1319,7 +1381,7 @@ export function make(options: ClientOptions) { path: `/api/pty/${encodeURIComponent(input.ptyID)}`, query: { location: input["location"] }, successStatus: 200, - declaredStatuses: [404, 401, 400], + declaredStatuses: [404, 401, 403, 400], empty: false, }, requestOptions, @@ -1332,7 +1394,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { title: input["title"], size: input["size"] }, successStatus: 200, - declaredStatuses: [404, 401, 400], + declaredStatuses: [404, 401, 403, 400], empty: false, }, requestOptions, @@ -1344,7 +1406,7 @@ export function make(options: ClientOptions) { path: `/api/pty/${encodeURIComponent(input.ptyID)}`, query: { location: input["location"] }, successStatus: 204, - declaredStatuses: [404, 401, 400], + declaredStatuses: [404, 401, 403, 400], empty: true, }, requestOptions, @@ -1358,7 +1420,7 @@ export function make(options: ClientOptions) { path: `/api/shell`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1376,7 +1438,7 @@ export function make(options: ClientOptions) { metadata: input["metadata"], }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1388,7 +1450,7 @@ export function make(options: ClientOptions) { path: `/api/shell/${encodeURIComponent(input.id)}`, query: { location: input["location"] }, successStatus: 200, - declaredStatuses: [404, 401, 400], + declaredStatuses: [404, 401, 403, 400], empty: false, }, requestOptions, @@ -1401,7 +1463,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { timeout: input["timeout"] }, successStatus: 200, - declaredStatuses: [404, 401, 400], + declaredStatuses: [404, 401, 403, 400], empty: false, }, requestOptions, @@ -1413,7 +1475,7 @@ export function make(options: ClientOptions) { path: `/api/shell/${encodeURIComponent(input.id)}/output`, query: { location: input["location"], cursor: input["cursor"], limit: input["limit"] }, successStatus: 200, - declaredStatuses: [404, 401, 400], + declaredStatuses: [404, 401, 403, 400], empty: false, }, requestOptions, @@ -1425,7 +1487,7 @@ export function make(options: ClientOptions) { path: `/api/shell/${encodeURIComponent(input.id)}`, query: { location: input["location"] }, successStatus: 204, - declaredStatuses: [404, 401, 400], + declaredStatuses: [404, 401, 403, 400], empty: true, }, requestOptions, @@ -1440,7 +1502,7 @@ export function make(options: ClientOptions) { path: `/api/question/request`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1452,7 +1514,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/session/${encodeURIComponent(input.sessionID)}/question`, successStatus: 200, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: false, }, requestOptions, @@ -1464,7 +1526,7 @@ export function make(options: ClientOptions) { path: `/api/session/${encodeURIComponent(input.sessionID)}/question/${encodeURIComponent(input.requestID)}/reply`, body: { answers: input["answers"] }, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -1475,7 +1537,7 @@ export function make(options: ClientOptions) { method: "POST", path: `/api/session/${encodeURIComponent(input.sessionID)}/question/${encodeURIComponent(input.requestID)}/reject`, successStatus: 204, - declaredStatuses: [404, 400, 401], + declaredStatuses: [404, 400, 401, 403], empty: true, }, requestOptions, @@ -1489,7 +1551,7 @@ export function make(options: ClientOptions) { path: `/api/reference`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1504,7 +1566,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { strategy: input["strategy"], directory: input["directory"], name: input["name"] }, successStatus: 200, - declaredStatuses: [400, 401], + declaredStatuses: [400, 401, 403], empty: false, }, requestOptions, @@ -1517,7 +1579,7 @@ export function make(options: ClientOptions) { query: { location: input["location"] }, body: { directory: input["directory"], force: input["force"] }, successStatus: 204, - declaredStatuses: [400, 401], + declaredStatuses: [400, 401, 403], empty: true, }, requestOptions, @@ -1529,7 +1591,7 @@ export function make(options: ClientOptions) { path: `/experimental/project/${encodeURIComponent(input.projectID)}/copy/refresh`, query: { location: input["location"] }, successStatus: 204, - declaredStatuses: [400, 401], + declaredStatuses: [400, 401, 403], empty: true, }, requestOptions, @@ -1543,7 +1605,7 @@ export function make(options: ClientOptions) { path: `/api/vcs/status`, query: { location: input?.["location"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1555,7 +1617,7 @@ export function make(options: ClientOptions) { path: `/api/vcs/diff`, query: { location: input["location"], mode: input["mode"], context: input["context"] }, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1569,7 +1631,7 @@ export function make(options: ClientOptions) { method: "GET", path: `/api/debug/location`, successStatus: 200, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: false, }, requestOptions, @@ -1581,7 +1643,7 @@ export function make(options: ClientOptions) { path: `/api/debug/location`, query: { location: input?.["location"] }, successStatus: 204, - declaredStatuses: [401, 400], + declaredStatuses: [401, 403, 400], empty: true, }, requestOptions, diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index 255beaa1bb9d..cd0fc3539a7b 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -1,5 +1,17 @@ export type JsonValue = null | boolean | number | string | Array | { [key: string]: JsonValue } +export type PairingInvitation = { v: 1; kind: "shuvcode.pair"; urls: Array; token: string; expiresAt: string } + +export type PairingRedeemResponse = { deviceID: string } + +export type PairingDevice = { + deviceID: string + name: string + createdAt: string + updatedAt: string + revokedAt?: string | null +} + export type ModelRef = { id: string; providerID: string; variant?: string } export type ProviderSettings = { [x: string]: JsonValue } @@ -2326,6 +2338,10 @@ export type UnauthorizedError = { readonly _tag: "UnauthorizedError"; readonly m export const isUnauthorizedError = (value: unknown): value is UnauthorizedError => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "UnauthorizedError" +export type ForbiddenError = { readonly _tag: "ForbiddenError"; readonly message: string } +export const isForbiddenError = (value: unknown): value is ForbiddenError => + typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ForbiddenError" + export type InvalidRequestError = { readonly _tag: "InvalidRequestError" readonly message: string @@ -2335,6 +2351,36 @@ export type InvalidRequestError = { export const isInvalidRequestError = (value: unknown): value is InvalidRequestError => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "InvalidRequestError" +export type ServiceUnavailableError = { + readonly _tag: "ServiceUnavailableError" + readonly message: string + readonly service?: string | undefined +} +export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError => + typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError" + +export type PairingConflictError = { readonly _tag: "PairingConflictError"; readonly message: string } +export const isPairingConflictError = (value: unknown): value is PairingConflictError => + typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "PairingConflictError" + +export type PairingInvitationUnavailableError = { + readonly _tag: "PairingInvitationUnavailableError" + readonly message: string +} +export const isPairingInvitationUnavailableError = (value: unknown): value is PairingInvitationUnavailableError => + typeof value === "object" && + value !== null && + "_tag" in value && + value["_tag"] === "PairingInvitationUnavailableError" + +export type PairingDeviceNotFoundError = { + readonly _tag: "PairingDeviceNotFoundError" + readonly deviceID: string + readonly message: string +} +export const isPairingDeviceNotFoundError = (value: unknown): value is PairingDeviceNotFoundError => + typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "PairingDeviceNotFoundError" + export type InvalidCursorError = { readonly _tag: "InvalidCursorError"; readonly message: string } export const isInvalidCursorError = (value: unknown): value is InvalidCursorError => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "InvalidCursorError" @@ -2388,14 +2434,6 @@ export type SkillNotFoundError = { export const isSkillNotFoundError = (value: unknown): value is SkillNotFoundError => typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "SkillNotFoundError" -export type ServiceUnavailableError = { - readonly _tag: "ServiceUnavailableError" - readonly message: string - readonly service?: string | undefined -} -export const isServiceUnavailableError = (value: unknown): value is ServiceUnavailableError => - typeof value === "object" && value !== null && "_tag" in value && value["_tag"] === "ServiceUnavailableError" - export type SessionBusyError = { readonly _tag: "SessionBusyError" readonly sessionID: string @@ -2487,6 +2525,43 @@ export type HealthGetOutput = { healthy: true; version: string; pid: number } export type ServerGetOutput = { urls: Array } +export type PairingInvitationCreateOutput = PairingInvitation + +export type PairingRedeemInput = { + readonly token: { + readonly token: string + readonly requestID: string + readonly deviceName: string + readonly credential: string + }["token"] + readonly requestID: { + readonly token: string + readonly requestID: string + readonly deviceName: string + readonly credential: string + }["requestID"] + readonly deviceName: { + readonly token: string + readonly requestID: string + readonly deviceName: string + readonly credential: string + }["deviceName"] + readonly credential: { + readonly token: string + readonly requestID: string + readonly deviceName: string + readonly credential: string + }["credential"] +} + +export type PairingRedeemOutput = PairingRedeemResponse + +export type PairingDeviceListOutput = Array + +export type PairingDeviceRevokeInput = { readonly deviceID: { readonly deviceID: string }["deviceID"] } + +export type PairingDeviceRevokeOutput = void + export type LocationGetInput = { readonly location?: { readonly location?: { readonly directory?: string | undefined; readonly workspace?: string | undefined } | undefined diff --git a/packages/client/test/promise.test.ts b/packages/client/test/promise.test.ts index ba822f28e566..b41e5a873d3e 100644 --- a/packages/client/test/promise.test.ts +++ b/packages/client/test/promise.test.ts @@ -7,6 +7,7 @@ test("exposes every standard HTTP API group", () => { expect(Object.keys(client)).toEqual([ "health", "server", + "pairing", "location", "agent", "plugin", @@ -16,7 +17,7 @@ test("exposes every standard HTTP API group", () => { "generate", "provider", "integration", - "server.mcp", + "mcp", "credential", "project", "form", @@ -77,7 +78,7 @@ test("MCP resource catalog uses the public HTTP contract", async () => { }, }) - const result = await client["server.mcp"].resource.catalog({ location: { directory: "/tmp/project" } }) + const result = await client.mcp.resource.catalog({ location: { directory: "/tmp/project" } }) expect(result.data.resources[0]?.uri).toBe("docs://readme") expect(request?.method).toBe("GET") diff --git a/packages/core/schema.json b/packages/core/schema.json index 82a395b4e407..3d699913282d 100644 --- a/packages/core/schema.json +++ b/packages/core/schema.json @@ -1,9 +1,9 @@ { "version": "7", "dialect": "sqlite", - "id": "5f0a1db8-d4bf-42c3-becb-96b46fe66bed", + "id": "506fd542-6599-4831-93eb-97f9373bdf02", "prevIds": [ - "666138ef-82cb-4a9a-a765-e6669a436ff3" + "5f0a1db8-d4bf-42c3-becb-96b46fe66bed" ], "ddl": [ { @@ -38,6 +38,10 @@ "name": "event", "entityType": "tables" }, + { + "name": "pairing_device", + "entityType": "tables" + }, { "name": "permission", "entityType": "tables" @@ -556,6 +560,86 @@ "entityType": "columns", "table": "event" }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "pairing_device" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "request_id", + "entityType": "columns", + "table": "pairing_device" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "name", + "entityType": "columns", + "table": "pairing_device" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "credential_hash", + "entityType": "columns", + "table": "pairing_device" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "invitation_hash", + "entityType": "columns", + "table": "pairing_device" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_created", + "entityType": "columns", + "table": "pairing_device" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_updated", + "entityType": "columns", + "table": "pairing_device" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "time_revoked", + "entityType": "columns", + "table": "pairing_device" + }, { "type": "text", "notNull": false, @@ -1844,6 +1928,15 @@ "table": "event", "entityType": "pks" }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "pairing_device_pk", + "table": "pairing_device", + "entityType": "pks" + }, { "columns": [ "id" @@ -1974,6 +2067,48 @@ "entityType": "indexes", "table": "event" }, + { + "columns": [ + { + "value": "request_id", + "isExpression": false + } + ], + "isUnique": true, + "where": null, + "origin": "manual", + "name": "pairing_device_request_id_unique", + "entityType": "indexes", + "table": "pairing_device" + }, + { + "columns": [ + { + "value": "credential_hash", + "isExpression": false + } + ], + "isUnique": true, + "where": null, + "origin": "manual", + "name": "pairing_device_credential_hash_unique", + "entityType": "indexes", + "table": "pairing_device" + }, + { + "columns": [ + { + "value": "invitation_hash", + "isExpression": false + } + ], + "isUnique": true, + "where": null, + "origin": "manual", + "name": "pairing_device_invitation_hash_unique", + "entityType": "indexes", + "table": "pairing_device" + }, { "columns": [ { diff --git a/packages/core/src/database/migration.gen.ts b/packages/core/src/database/migration.gen.ts index 2039393030b1..7d612945bed9 100644 --- a/packages/core/src/database/migration.gen.ts +++ b/packages/core/src/database/migration.gen.ts @@ -54,5 +54,6 @@ export const migrations = ( import("./migration/20260709163752_time_suspended"), import("./migration/20260709190621_session_pending_table"), import("./migration/20260710025429_instruction_sync"), + import("./migration/20260714225613_mobile_pairing"), ]) ).map((module) => module.default) satisfies DatabaseMigration.Migration[] diff --git a/packages/core/src/database/migration/20260714225613_mobile_pairing.ts b/packages/core/src/database/migration/20260714225613_mobile_pairing.ts new file mode 100644 index 000000000000..508ca25c9dba --- /dev/null +++ b/packages/core/src/database/migration/20260714225613_mobile_pairing.ts @@ -0,0 +1,29 @@ +import { Effect } from "effect" +import type { DatabaseMigration } from "../migration" + +export default { + id: "20260714225613_mobile_pairing", + up(tx) { + return Effect.gen(function* () { + yield* tx.run(` + CREATE TABLE \`pairing_device\` ( + \`id\` text PRIMARY KEY, + \`request_id\` text NOT NULL, + \`name\` text NOT NULL, + \`credential_hash\` text NOT NULL, + \`invitation_hash\` text NOT NULL, + \`time_created\` integer NOT NULL, + \`time_updated\` integer NOT NULL, + \`time_revoked\` integer + ); + `) + yield* tx.run(`CREATE UNIQUE INDEX \`pairing_device_request_id_unique\` ON \`pairing_device\` (\`request_id\`);`) + yield* tx.run( + `CREATE UNIQUE INDEX \`pairing_device_credential_hash_unique\` ON \`pairing_device\` (\`credential_hash\`);`, + ) + yield* tx.run( + `CREATE UNIQUE INDEX \`pairing_device_invitation_hash_unique\` ON \`pairing_device\` (\`invitation_hash\`);`, + ) + }) + }, +} satisfies DatabaseMigration.Migration diff --git a/packages/core/src/database/schema.gen.ts b/packages/core/src/database/schema.gen.ts index 7d9fc7937b52..385b66a6104b 100644 --- a/packages/core/src/database/schema.gen.ts +++ b/packages/core/src/database/schema.gen.ts @@ -87,6 +87,18 @@ export default { CONSTRAINT \`fk_event_aggregate_id_event_sequence_aggregate_id_fk\` FOREIGN KEY (\`aggregate_id\`) REFERENCES \`event_sequence\`(\`aggregate_id\`) ON DELETE CASCADE ); `) + yield* tx.run(` + CREATE TABLE \`pairing_device\` ( + \`id\` text PRIMARY KEY, + \`request_id\` text NOT NULL, + \`name\` text NOT NULL, + \`credential_hash\` text NOT NULL, + \`invitation_hash\` text NOT NULL, + \`time_created\` integer NOT NULL, + \`time_updated\` integer NOT NULL, + \`time_revoked\` integer + ); + `) yield* tx.run(` CREATE TABLE \`permission\` ( \`id\` text PRIMARY KEY, @@ -249,6 +261,13 @@ export default { `) yield* tx.run(`CREATE UNIQUE INDEX \`event_aggregate_seq_idx\` ON \`event\` (\`aggregate_id\`,\`seq\`);`) yield* tx.run(`CREATE INDEX \`event_aggregate_type_seq_idx\` ON \`event\` (\`aggregate_id\`,\`type\`,\`seq\`);`) + yield* tx.run(`CREATE UNIQUE INDEX \`pairing_device_request_id_unique\` ON \`pairing_device\` (\`request_id\`);`) + yield* tx.run( + `CREATE UNIQUE INDEX \`pairing_device_credential_hash_unique\` ON \`pairing_device\` (\`credential_hash\`);`, + ) + yield* tx.run( + `CREATE UNIQUE INDEX \`pairing_device_invitation_hash_unique\` ON \`pairing_device\` (\`invitation_hash\`);`, + ) yield* tx.run( `CREATE UNIQUE INDEX \`permission_project_action_resource_idx\` ON \`permission\` (\`project_id\`,\`action\`,\`resource\`);`, ) diff --git a/packages/core/src/pairing.ts b/packages/core/src/pairing.ts new file mode 100644 index 000000000000..04aa600d6657 --- /dev/null +++ b/packages/core/src/pairing.ts @@ -0,0 +1,195 @@ +export * as Pairing from "./pairing" + +import { asc, eq, or } from "drizzle-orm" +import { Context, Data, Duration, Effect, Layer, Semaphore } from "effect" +import { Pairing } from "@opencode-ai/schema/pairing" +import { Database } from "./database/database" +import { makeGlobalNode } from "./effect/app-node" +import { PairingDeviceTable } from "./pairing/sql" +import { Hash } from "./util/hash" + +const DEFAULT_TTL = Duration.minutes(3) +const CAPACITY = 1_024 + +export class Conflict extends Data.TaggedError("PairingConflict")<{ + readonly message: string +}> {} + +export class InvitationUnavailable extends Data.TaggedError("PairingInvitationUnavailable")<{ + readonly message: string +}> {} + +export class DeviceNotFound extends Data.TaggedError("PairingDeviceNotFound")<{ + readonly deviceID: Pairing.DeviceID + readonly message: string +}> {} + +export class InvalidRequest extends Data.TaggedError("PairingInvalidRequest")<{ + readonly message: string +}> {} + +export class CapacityExceeded extends Data.TaggedError("PairingCapacityExceeded")<{ + readonly message: string +}> {} + +export type Principal = + | { readonly type: "administrator" } + | { readonly type: "device"; readonly deviceID: Pairing.DeviceID } + +export interface Interface { + readonly issue: (input: { + readonly urls: ReadonlyArray + }) => Effect.Effect + readonly redeem: ( + input: Pairing.RedeemRequest, + ) => Effect.Effect + readonly authenticate: (credential: string) => Effect.Effect + readonly list: () => Effect.Effect> + readonly revoke: (deviceID: Pairing.DeviceID) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/Pairing") {} + +export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => + Effect.gen(function* () { + const { db } = yield* Database.Service + const lock = Semaphore.makeUnsafe(1) + const invitations = new Map() + + const rowDevice = (row: typeof PairingDeviceTable.$inferSelect): Pairing.Device => ({ + deviceID: row.id, + name: row.name, + createdAt: new Date(row.time_created).toISOString(), + updatedAt: new Date(row.time_updated).toISOString(), + ...(row.time_revoked === null ? {} : { revokedAt: new Date(row.time_revoked).toISOString() }), + }) + + return Service.of({ + issue: Effect.fn("Pairing.issue")(function* (input) { + const urls = yield* Effect.try({ + try: () => Pairing.advertisedURLs(input.urls), + catch: () => new InvalidRequest({ message: "Invalid advertised pairing URL" }), + }) + if (urls.length === 0) return yield* new InvalidRequest({ message: "No pairing URL is available" }) + return yield* lock.withPermit( + Effect.gen(function* () { + const now = Date.now() + for (const [digest, invitation] of invitations) { + if (invitation.expiresAt <= now) invitations.delete(digest) + } + if (invitations.size >= capacity) + return yield* new CapacityExceeded({ message: "Too many outstanding pairing invitations" }) + const token = Pairing.InvitationToken.make( + Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("base64url"), + ) + const expiresAt = now + Duration.toMillis(Duration.fromInputUnsafe(ttl)) + invitations.set(Hash.sha256(token), { expiresAt }) + const result = { + v: 1 as const, + kind: "shuvcode.pair" as const, + urls, + token, + expiresAt: new Date(expiresAt).toISOString(), + } + if (Buffer.byteLength(JSON.stringify(result)) > 4_096) { + invitations.delete(Hash.sha256(token)) + return yield* new InvalidRequest({ message: "Pairing invitation exceeds the scanner size limit" }) + } + return result + }), + ) + }), + redeem: Effect.fn("Pairing.redeem")(function* (input) { + if (!/^scd_v1_[A-Za-z0-9_-]{43}$/.test(input.credential)) + return yield* new InvalidRequest({ message: "Invalid device credential" }) + if (input.deviceName !== input.deviceName.trim() || Array.from(input.deviceName).length > 80) + return yield* new InvalidRequest({ message: "Invalid device name" }) + const invitationHash = Hash.sha256(input.token) + const credentialHash = Hash.sha256(input.credential) + return yield* lock.withPermit( + Effect.gen(function* () { + const existing = yield* db + .select() + .from(PairingDeviceTable) + .where( + or( + eq(PairingDeviceTable.request_id, input.requestID), + eq(PairingDeviceTable.invitation_hash, invitationHash), + eq(PairingDeviceTable.credential_hash, credentialHash), + ), + ) + .get() + .pipe(Effect.orDie) + if (existing) { + if ( + existing.request_id === input.requestID && + existing.invitation_hash === invitationHash && + existing.credential_hash === credentialHash + ) + return { deviceID: existing.id } + return yield* new Conflict({ message: "Pairing redemption does not match the committed enrollment" }) + } + + const invitation = invitations.get(invitationHash) + if (!invitation || invitation.expiresAt <= Date.now()) { + invitations.delete(invitationHash) + return yield* new InvitationUnavailable({ message: "Pairing invitation is unavailable" }) + } + invitations.delete(invitationHash) + const deviceID = Pairing.DeviceID.create() + yield* db + .insert(PairingDeviceTable) + .values({ + id: deviceID, + request_id: input.requestID, + name: input.deviceName, + credential_hash: credentialHash, + invitation_hash: invitationHash, + }) + .run() + .pipe(Effect.orDie) + return { deviceID } + }), + ) + }), + authenticate: Effect.fn("Pairing.authenticate")(function* (credential) { + if (!/^scd_v1_[A-Za-z0-9_-]{43}$/.test(credential)) return + const row = yield* db + .select({ id: PairingDeviceTable.id, time_revoked: PairingDeviceTable.time_revoked }) + .from(PairingDeviceTable) + .where(eq(PairingDeviceTable.credential_hash, Hash.sha256(credential))) + .get() + .pipe(Effect.orDie) + if (!row || row.time_revoked !== null) return + return { type: "device" as const, deviceID: row.id } + }), + list: Effect.fn("Pairing.list")(function* () { + return (yield* db + .select() + .from(PairingDeviceTable) + .orderBy(asc(PairingDeviceTable.time_created)) + .all() + .pipe(Effect.orDie)).map(rowDevice) + }), + revoke: Effect.fn("Pairing.revoke")(function* (deviceID) { + const row = yield* db + .select({ id: PairingDeviceTable.id, time_revoked: PairingDeviceTable.time_revoked }) + .from(PairingDeviceTable) + .where(eq(PairingDeviceTable.id, deviceID)) + .get() + .pipe(Effect.orDie) + if (!row) return yield* new DeviceNotFound({ deviceID, message: "Pairing device not found" }) + if (row.time_revoked !== null) return + yield* db + .update(PairingDeviceTable) + .set({ time_revoked: Date.now() }) + .where(eq(PairingDeviceTable.id, deviceID)) + .run() + .pipe(Effect.orDie) + }), + }) + }) + +const layer = Layer.effect(Service, make()) + +export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] }) diff --git a/packages/core/src/pairing/sql.ts b/packages/core/src/pairing/sql.ts new file mode 100644 index 000000000000..67972ff78e6f --- /dev/null +++ b/packages/core/src/pairing/sql.ts @@ -0,0 +1,21 @@ +import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core" +import { Pairing } from "@opencode-ai/schema/pairing" +import { Timestamps } from "../database/schema.sql" + +export const PairingDeviceTable = sqliteTable( + "pairing_device", + { + id: text().$type().primaryKey(), + request_id: text().$type().notNull(), + name: text().$type().notNull(), + credential_hash: text().notNull(), + invitation_hash: text().notNull(), + ...Timestamps, + time_revoked: integer(), + }, + (table) => [ + uniqueIndex("pairing_device_request_id_unique").on(table.request_id), + uniqueIndex("pairing_device_credential_hash_unique").on(table.credential_hash), + uniqueIndex("pairing_device_invitation_hash_unique").on(table.invitation_hash), + ], +) diff --git a/packages/core/test/pairing.test.ts b/packages/core/test/pairing.test.ts new file mode 100644 index 000000000000..2e8e92990712 --- /dev/null +++ b/packages/core/test/pairing.test.ts @@ -0,0 +1,239 @@ +import { describe, expect } from "bun:test" +import { Duration, Effect, Exit, Layer } from "effect" +import { eq } from "drizzle-orm" +import { Pairing } from "@opencode-ai/core/pairing" +import { PairingDeviceTable } from "@opencode-ai/core/pairing/sql" +import { Database } from "@opencode-ai/core/database/database" +import { Pairing as PairingSchema } from "@opencode-ai/schema/pairing" +import { LayerNode } from "@opencode-ai/core/effect/layer-node" +import { testEffect } from "./lib/effect" + +const it = testEffect(Layer.merge(LayerNode.compile(Pairing.node), LayerNode.compile(Database.node))) +const credential = PairingSchema.DeviceCredential.make("scd_v1_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") +const requestID = PairingSchema.RequestID.make("1da45bb5-9a85-4a29-955b-c7d6d74f13de") + +describe("Pairing.Service", () => { + it.effect("enrolls once, reconciles exact retries, and authenticates the device", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const input = { + token: invitation.token, + requestID, + deviceName: PairingSchema.DeviceName.make("Shuv's iPhone"), + credential, + } + + const enrolled = yield* pairing.redeem(input) + expect(yield* pairing.redeem(input)).toEqual(enrolled) + expect(yield* pairing.authenticate(credential)).toEqual({ type: "device", deviceID: enrolled.deviceID }) + expect(yield* pairing.list()).toHaveLength(1) + }), + ) + + it.effect("consumes an invitation once and rejects changed retries", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + yield* pairing.redeem({ + token: invitation.token, + requestID, + deviceName: PairingSchema.DeviceName.make("Phone"), + credential, + }) + + const changed = yield* pairing + .redeem({ + token: invitation.token, + requestID: PairingSchema.RequestID.make("6cf0f5d4-d96a-4909-a7f2-69416da670d6"), + deviceName: PairingSchema.DeviceName.make("Phone"), + credential: PairingSchema.DeviceCredential.make("scd_v1_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"), + }) + .pipe(Effect.flip) + expect(changed._tag).toBe("PairingConflict") + }), + ) + + it.effect("revokes one credential without affecting another", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const first = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const firstDevice = yield* pairing.redeem({ + token: first.token, + requestID, + deviceName: PairingSchema.DeviceName.make("Phone"), + credential, + }) + const secondCredential = PairingSchema.DeviceCredential.make("scd_v1_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC") + const second = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const secondDevice = yield* pairing.redeem({ + token: second.token, + requestID: PairingSchema.RequestID.make("0fcfa724-fae1-4610-91bd-7cb78ec36f89"), + deviceName: PairingSchema.DeviceName.make("iPad"), + credential: secondCredential, + }) + + yield* pairing.revoke(firstDevice.deviceID) + expect(yield* pairing.authenticate(credential)).toBeUndefined() + expect(yield* pairing.authenticate(secondCredential)).toEqual({ type: "device", deviceID: secondDevice.deviceID }) + }), + ) + + it.effect("serializes concurrent exact redemptions to one durable device", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const input = { + token: invitation.token, + requestID: PairingSchema.RequestID.make("e3b12d77-d8c0-45ee-9942-694ab055b8ad"), + deviceName: PairingSchema.DeviceName.make("Concurrent Phone"), + credential: PairingSchema.DeviceCredential.make("scd_v1_EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"), + } + + const results = yield* Effect.all([pairing.redeem(input), pairing.redeem(input)], { concurrency: 2 }) + expect(results[0]).toEqual(results[1]) + expect((yield* pairing.list()).filter((device) => device.deviceID === results[0].deviceID)).toHaveLength(1) + }), + ) + + it.effect("fails competing concurrent redemptions closed after creating one device", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const first = pairing.redeem({ + token: invitation.token, + requestID: PairingSchema.RequestID.make("de7bb697-5475-40d0-b56c-900bfcf64e30"), + deviceName: PairingSchema.DeviceName.make("First"), + credential: PairingSchema.DeviceCredential.make("scd_v1_FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + }) + const second = pairing.redeem({ + token: invitation.token, + requestID: PairingSchema.RequestID.make("98089dad-ee55-49c7-831e-6e3c07238169"), + deviceName: PairingSchema.DeviceName.make("Second"), + credential: PairingSchema.DeviceCredential.make("scd_v1_GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"), + }) + + const exits = yield* Effect.all([Effect.exit(first), Effect.exit(second)], { concurrency: 2 }) + expect(exits.filter(Exit.isSuccess)).toHaveLength(1) + expect(exits.filter(Exit.isFailure)).toHaveLength(1) + expect((yield* pairing.list()).filter((device) => ["First", "Second"].includes(device.name))).toHaveLength(1) + }), + ) + + it.effect("detects request-only and credential-only uniqueness conflicts", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const first = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const firstRequest = PairingSchema.RequestID.make("7707d867-522d-42f5-8438-e2280d4822c4") + const firstCredential = PairingSchema.DeviceCredential.make("scd_v1_HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH") + yield* pairing.redeem({ + token: first.token, + requestID: firstRequest, + deviceName: PairingSchema.DeviceName.make("Original"), + credential: firstCredential, + }) + const second = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + expect( + (yield* pairing + .redeem({ + token: second.token, + requestID: firstRequest, + deviceName: PairingSchema.DeviceName.make("Changed request"), + credential: PairingSchema.DeviceCredential.make("scd_v1_IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"), + }) + .pipe(Effect.flip))._tag, + ).toBe("PairingConflict") + expect( + (yield* pairing + .redeem({ + token: second.token, + requestID: PairingSchema.RequestID.make("12332213-820a-49f7-8901-23187b2f8ec1"), + deviceName: PairingSchema.DeviceName.make("Changed credential"), + credential: firstCredential, + }) + .pipe(Effect.flip))._tag, + ).toBe("PairingConflict") + }), + ) + + it.effect("stores only credential and invitation digests", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const secret = PairingSchema.DeviceCredential.make("scd_v1_JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ") + const result = yield* pairing.redeem({ + token: invitation.token, + requestID: PairingSchema.RequestID.make("5724ec39-cb0b-4cd9-be63-ef51cc2f3468"), + deviceName: PairingSchema.DeviceName.make("Private"), + credential: secret, + }) + const row = yield* (yield* Database.Service).db + .select() + .from(PairingDeviceTable) + .where(eq(PairingDeviceTable.id, result.deviceID)) + .get() + + expect(row?.credential_hash).toMatch(/^[a-f0-9]{64}$/) + expect(row?.invitation_hash).toMatch(/^[a-f0-9]{64}$/) + expect(JSON.stringify(row)).not.toContain(secret) + expect(JSON.stringify(row)).not.toContain(invitation.token) + }), + ) + + it.effect("invalidates outstanding invitations when the service restarts", () => + Effect.gen(function* () { + const before = yield* Pairing.make() + const invitation = yield* before.issue({ urls: ["https://shuvdev.example"] }) + const committed = yield* before.issue({ urls: ["https://shuvdev.example"] }) + const committedInput = { + token: committed.token, + requestID: PairingSchema.RequestID.make("46a5de7e-716e-4c1e-8d37-685f79de3712"), + deviceName: PairingSchema.DeviceName.make("Committed"), + credential: PairingSchema.DeviceCredential.make("scd_v1_MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"), + } + const committedDevice = yield* before.redeem(committedInput) + const after = yield* Pairing.make() + expect(yield* after.redeem(committedInput)).toEqual(committedDevice) + const error = yield* after + .redeem({ + token: invitation.token, + requestID: PairingSchema.RequestID.make("72fba88a-4921-418c-88c7-6a917509f77d"), + deviceName: PairingSchema.DeviceName.make("Restarted"), + credential: PairingSchema.DeviceCredential.make("scd_v1_KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"), + }) + .pipe(Effect.flip) + expect(error._tag).toBe("PairingInvitationUnavailable") + }), + ) + + it.live("rejects expired and malformed invitations and reports capacity", () => + Effect.gen(function* () { + const pairing = yield* Pairing.make(Duration.millis(2), 1) + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + expect((yield* pairing.issue({ urls: ["https://shuvdev.example"] }).pipe(Effect.flip))._tag).toBe( + "PairingCapacityExceeded", + ) + yield* Effect.sleep(Duration.millis(5)) + expect( + (yield* pairing + .redeem({ + token: invitation.token, + requestID: PairingSchema.RequestID.make("901cc739-b2f7-4df7-aa66-898c9afb1c79"), + deviceName: PairingSchema.DeviceName.make("Expired"), + credential: PairingSchema.DeviceCredential.make("scd_v1_LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"), + }) + .pipe(Effect.flip))._tag, + ).toBe("PairingInvitationUnavailable") + expect( + (yield* pairing + .redeem({ + token: "malformed" as PairingSchema.InvitationToken, + requestID: PairingSchema.RequestID.make("7b00ed9a-e7e8-4667-b88f-8d68ba93d050"), + deviceName: PairingSchema.DeviceName.make("Malformed"), + credential: "bad" as PairingSchema.DeviceCredential, + }) + .pipe(Effect.flip))._tag, + ).toBe("PairingInvalidRequest") + }), + ) +}) diff --git a/packages/docs/openapi.json b/packages/docs/openapi.json index 543a41817372..3405e912fc99 100644 --- a/packages/docs/openapi.json +++ b/packages/docs/openapi.json @@ -8,9 +8,7 @@ "paths": { "/api/health": { "get": { - "tags": [ - "health" - ], + "tags": ["health"], "operationId": "v2.health.get", "parameters": [], "security": [], @@ -24,9 +22,7 @@ "properties": { "healthy": { "type": "boolean", - "enum": [ - true - ] + "enum": [true] }, "version": { "type": "string" @@ -40,11 +36,7 @@ ] } }, - "required": [ - "healthy", - "version", - "pid" - ], + "required": ["healthy", "version", "pid"], "additionalProperties": false } } @@ -69,6 +61,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Check whether the API server is ready to accept requests.", @@ -77,9 +79,7 @@ }, "/api/server": { "get": { - "tags": [ - "server" - ], + "tags": ["server"], "operationId": "v2.server.get", "parameters": [], "security": [], @@ -98,9 +98,7 @@ } } }, - "required": [ - "urls" - ], + "required": ["urls"], "additionalProperties": false } } @@ -125,17 +123,338 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Return the URLs that can be used to connect to this server.", "summary": "Get server information" } }, - "/api/location": { + "/api/pairing/invitation": { + "post": { + "tags": ["pairing"], + "operationId": "v2.pairing.invitation.create", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Pairing.Invitation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pairing.Invitation" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/UnauthorizedError" + }, + { + "$ref": "#/components/schemas/UnauthorizedError" + } + ] + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ForbiddenError" + }, + { + "$ref": "#/components/schemas/ForbiddenError" + } + ] + } + } + } + }, + "503": { + "description": "ServiceUnavailableError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServiceUnavailableError" + } + } + } + } + }, + "summary": "Create pairing invitation" + } + }, + "/api/pairing/redeem": { + "post": { + "tags": ["pairing"], + "operationId": "v2.pairing.redeem", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Pairing.RedeemResponse", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pairing.RedeemResponse" + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/InvalidRequestError1" + }, + { + "$ref": "#/components/schemas/InvalidRequestError" + } + ] + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, + "409": { + "description": "PairingConflictError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PairingConflictError" + } + } + } + }, + "410": { + "description": "PairingInvitationUnavailableError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PairingInvitationUnavailableError" + } + } + } + } + }, + "summary": "Redeem pairing invitation", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pairing.RedeemRequest" + } + } + }, + "required": true + } + } + }, + "/api/pairing/device": { "get": { - "tags": [ - "location" + "tags": ["pairing"], + "operationId": "v2.pairing.device.list", + "parameters": [], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pairing.Device" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/UnauthorizedError" + }, + { + "$ref": "#/components/schemas/UnauthorizedError" + } + ] + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ForbiddenError" + }, + { + "$ref": "#/components/schemas/ForbiddenError" + } + ] + } + } + } + } + }, + "summary": "List paired devices" + } + }, + "/api/pairing/device/{deviceID}": { + "delete": { + "tags": ["pairing"], + "operationId": "v2.pairing.device.revoke", + "parameters": [ + { + "name": "deviceID", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^device_" + } + ] + }, + "required": true + } ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/UnauthorizedError" + }, + { + "$ref": "#/components/schemas/UnauthorizedError" + } + ] + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/ForbiddenError" + }, + { + "$ref": "#/components/schemas/ForbiddenError" + } + ] + } + } + } + }, + "404": { + "description": "PairingDeviceNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PairingDeviceNotFoundError" + } + } + } + } + }, + "summary": "Revoke paired device" + } + }, + "/api/location": { + "get": { + "tags": ["location"], "operationId": "v2.location.get", "parameters": [ { @@ -210,6 +529,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Resolve the requested location or the server default location.", @@ -218,9 +547,7 @@ }, "/api/agent": { "get": { - "tags": [ - "agent" - ], + "tags": ["agent"], "operationId": "v2.agent.list", "parameters": [ { @@ -283,10 +610,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -311,6 +635,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve currently registered agents.", @@ -319,9 +653,7 @@ }, "/api/plugin": { "get": { - "tags": [ - "plugin" - ], + "tags": ["plugin"], "operationId": "v2.plugin.list", "parameters": [ { @@ -384,10 +716,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -412,6 +741,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve currently loaded plugins.", @@ -420,9 +759,7 @@ }, "/api/session": { "get": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.list", "parameters": [ { @@ -468,10 +805,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "asc", - "desc" - ] + "enum": ["asc", "desc"] }, { "type": "null" @@ -513,9 +847,7 @@ }, { "type": "string", - "enum": [ - "null" - ] + "enum": ["null"] } ], "description": "Filter by parent session. Use null to return only root sessions." @@ -630,15 +962,23 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve sessions in the requested order. Items keep that order across pages; use cursor.next or cursor.previous to move through the ordered list.", "summary": "List sessions" }, "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.create", "parameters": [], "security": [], @@ -654,9 +994,7 @@ "$ref": "#/components/schemas/Session.Info" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -681,6 +1019,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Create a session at the requested location.", @@ -747,9 +1095,7 @@ }, "/api/session/active": { "get": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.active", "parameters": [], "security": [], @@ -770,9 +1116,7 @@ } } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -797,6 +1141,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve foreground Session drains currently owned by this OpenCode process. Sessions absent from the result are inactive.", @@ -805,9 +1159,7 @@ }, "/api/session/{sessionID}": { "get": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.get", "parameters": [ { @@ -837,9 +1189,7 @@ "$ref": "#/components/schemas/Session.Info" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -865,6 +1215,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -887,9 +1247,7 @@ "summary": "Get session" }, "delete": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.remove", "parameters": [ { @@ -931,6 +1289,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -955,9 +1323,7 @@ }, "/api/session/{sessionID}/fork": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.fork", "parameters": [ { @@ -987,9 +1353,7 @@ "$ref": "#/components/schemas/Session.Info" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -1015,6 +1379,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | MessageNotFoundError", "content": { @@ -1070,9 +1444,7 @@ }, "/api/session/{sessionID}/agent": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.switchAgent", "parameters": [ { @@ -1114,6 +1486,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -1144,9 +1526,7 @@ "type": "string" } }, - "required": [ - "agent" - ], + "required": ["agent"], "additionalProperties": false } } @@ -1157,9 +1537,7 @@ }, "/api/session/{sessionID}/model": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.switchModel", "parameters": [ { @@ -1201,6 +1579,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -1231,9 +1619,7 @@ "$ref": "#/components/schemas/Model.Ref" } }, - "required": [ - "model" - ], + "required": ["model"], "additionalProperties": false } } @@ -1244,9 +1630,7 @@ }, "/api/session/{sessionID}/rename": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.rename", "parameters": [ { @@ -1288,6 +1672,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -1318,9 +1712,7 @@ "type": "string" } }, - "required": [ - "title" - ], + "required": ["title"], "additionalProperties": false } } @@ -1331,9 +1723,7 @@ }, "/api/session/{sessionID}/move": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.move", "parameters": [ { @@ -1382,6 +1772,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -1415,9 +1815,7 @@ "type": "string" } }, - "required": [ - "directory" - ], + "required": ["directory"], "additionalProperties": false }, "moveChanges": { @@ -1431,9 +1829,7 @@ ] } }, - "required": [ - "destination" - ], + "required": ["destination"], "additionalProperties": false } } @@ -1444,9 +1840,7 @@ }, "/api/session/{sessionID}/prompt": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.prompt", "parameters": [ { @@ -1473,12 +1867,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SessionInput.Admitted" + "$ref": "#/components/schemas/SessionPending.User" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -1511,6 +1903,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -1562,17 +1964,29 @@ } ] }, - "prompt": { - "$ref": "#/components/schemas/PromptInput" + "text": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PromptInput.FileAttachment" + } + }, + "agents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Prompt.AgentAttachment" + } + }, + "metadata": { + "type": "object" }, "delivery": { "anyOf": [ { "type": "string", - "enum": [ - "steer", - "queue" - ] + "enum": ["steer", "queue"] }, { "type": "null" @@ -1590,9 +2004,7 @@ ] } }, - "required": [ - "prompt" - ], + "required": ["text"], "additionalProperties": false } } @@ -1603,9 +2015,7 @@ }, "/api/session/{sessionID}/command": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.command", "parameters": [ { @@ -1632,12 +2042,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SessionInput.Admitted" + "$ref": "#/components/schemas/SessionPending.User" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -1670,6 +2078,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | CommandNotFoundError", "content": { @@ -1783,10 +2201,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "steer", - "queue" - ] + "enum": ["steer", "queue"] }, { "type": "null" @@ -1804,9 +2219,7 @@ ] } }, - "required": [ - "command" - ], + "required": ["command"], "additionalProperties": false } } @@ -1817,9 +2230,7 @@ }, "/api/session/{sessionID}/skill": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.skill", "parameters": [ { @@ -1851,12 +2262,22 @@ } } }, - "401": { - "description": "UnauthorizedError", + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "403": { + "description": "ForbiddenError", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UnauthorizedError" + "$ref": "#/components/schemas/ForbiddenError" } } } @@ -1919,9 +2340,7 @@ ] } }, - "required": [ - "skill" - ], + "required": ["skill"], "additionalProperties": false } } @@ -1932,9 +2351,7 @@ }, "/api/session/{sessionID}/synthetic": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.synthetic", "parameters": [ { @@ -1953,8 +2370,22 @@ ], "security": [], "responses": { - "204": { - "description": "" + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/SessionPending.Synthetic" + } + }, + "required": ["data"], + "additionalProperties": false + } + } + } }, "400": { "description": "InvalidRequestError", @@ -1976,6 +2407,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -1992,9 +2433,19 @@ } } } + }, + "409": { + "description": "ConflictError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConflictError" + } + } + } } }, - "description": "Append a synthetic message to a session and resume execution.", + "description": "Durably admit synthetic session input and schedule execution unless resume is false.", "summary": "Add synthetic message", "requestBody": { "content": { @@ -2002,6 +2453,21 @@ "schema": { "type": "object", "properties": { + "id": { + "anyOf": [ + { + "type": "string", + "allOf": [ + { + "pattern": "^msg_" + } + ] + }, + { + "type": "null" + } + ] + }, "text": { "type": "string" }, @@ -2018,6 +2484,17 @@ "metadata": { "type": "object" }, + "delivery": { + "anyOf": [ + { + "type": "string", + "enum": ["steer", "queue"] + }, + { + "type": "null" + } + ] + }, "resume": { "anyOf": [ { @@ -2029,9 +2506,7 @@ ] } }, - "required": [ - "text" - ], + "required": ["text"], "additionalProperties": false } } @@ -2042,9 +2517,7 @@ }, "/api/session/{sessionID}/shell": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.shell", "parameters": [ { @@ -2086,6 +2559,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2131,9 +2614,7 @@ "type": "string" } }, - "required": [ - "command" - ], + "required": ["command"], "additionalProperties": false } } @@ -2144,9 +2625,7 @@ }, "/api/session/{sessionID}/compact": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.compact", "parameters": [ { @@ -2173,12 +2652,10 @@ "type": "object", "properties": { "data": { - "$ref": "#/components/schemas/SessionInput.Compaction" + "$ref": "#/components/schemas/SessionPending.Compaction" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -2204,6 +2681,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2266,9 +2753,7 @@ }, "/api/session/{sessionID}/wait": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.wait", "parameters": [ { @@ -2310,6 +2795,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2344,9 +2839,7 @@ }, "/api/session/{sessionID}/revert/stage": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.revert.stage", "parameters": [ { @@ -2376,9 +2869,7 @@ "$ref": "#/components/schemas/Session.Revert" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -2404,6 +2895,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "MessageNotFoundError | SessionNotFoundError", "content": { @@ -2472,9 +2973,7 @@ ] } }, - "required": [ - "messageID" - ], + "required": ["messageID"], "additionalProperties": false } } @@ -2485,9 +2984,7 @@ }, "/api/session/{sessionID}/revert/clear": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.revert.clear", "parameters": [ { @@ -2529,6 +3026,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2572,9 +3079,7 @@ }, "/api/session/{sessionID}/revert/commit": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.revert.commit", "parameters": [ { @@ -2616,6 +3121,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2649,9 +3164,7 @@ }, "/api/session/{sessionID}/context": { "get": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.context", "parameters": [ { @@ -2684,9 +3197,7 @@ } } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -2712,6 +3223,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2744,11 +3265,102 @@ "summary": "Get session context" } }, - "/api/session/{sessionID}/instructions/entries": { + "/api/session/{sessionID}/pending": { "get": { - "tags": [ - "session" + "tags": ["session"], + "operationId": "v2.session.pending.list", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "required": true + } ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionPending.Info" + } + } + }, + "required": ["data"], + "additionalProperties": false + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundError" + }, + { + "$ref": "#/components/schemas/SessionNotFoundError" + } + ] + } + } + } + } + }, + "description": "List durable admitted session work not yet visible in projected history, ordered by admission. Includes unpromoted user and synthetic inputs and unhandled compaction barriers. The runner owns consumption; items disappear once promoted or handled.", + "summary": "List pending session work" + } + }, + "/api/session/{sessionID}/instructions/entries": { + "get": { + "tags": ["session"], "operationId": "v2.session.instructions.entry.list", "parameters": [ { @@ -2781,9 +3393,7 @@ } } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -2809,6 +3419,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2833,9 +3453,7 @@ }, "/api/session/{sessionID}/instructions/entries/{key}": { "put": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.instructions.entry.put", "parameters": [ { @@ -2885,6 +3503,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -2901,6 +3529,16 @@ } } } + }, + "413": { + "description": "InstructionEntryValueTooLargeError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InstructionEntryValueTooLargeError" + } + } + } } }, "description": "Attach or replace one durable instruction entry. Changes announce as updates at the next step boundary.", @@ -2913,9 +3551,7 @@ "properties": { "value": {} }, - "required": [ - "value" - ], + "required": ["value"], "additionalProperties": false } } @@ -2924,9 +3560,7 @@ } }, "delete": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.instructions.entry.remove", "parameters": [ { @@ -2976,6 +3610,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -3000,9 +3644,7 @@ }, "/api/experimental/session/{sessionID}/log": { "get": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.log", "parameters": [ { @@ -3040,10 +3682,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "true", - "false" - ] + "enum": ["true", "false"] }, { "type": "null" @@ -3079,11 +3718,7 @@ "$ref": "#/components/schemas/SessionLogItemStream" } }, - "required": [ - "id", - "event", - "data" - ], + "required": ["id", "event", "data"], "additionalProperties": false }, "x-effect-stream": { @@ -3097,18 +3732,13 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "Fail" - ] + "enum": ["Fail"] }, "error": { "not": {} } }, - "required": [ - "_tag", - "error" - ], + "required": ["_tag", "error"], "additionalProperties": false }, { @@ -3116,16 +3746,11 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "Die" - ] + "enum": ["Die"] }, "defect": {} }, - "required": [ - "_tag", - "defect" - ], + "required": ["_tag", "defect"], "additionalProperties": false }, { @@ -3133,9 +3758,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "Interrupt" - ] + "enum": ["Interrupt"] }, "fiberId": { "anyOf": [ @@ -3148,10 +3771,7 @@ ] } }, - "required": [ - "_tag", - "fiberId" - ], + "required": ["_tag", "fiberId"], "additionalProperties": false } ] @@ -3185,6 +3805,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -3209,9 +3839,7 @@ }, "/api/session/{sessionID}/interrupt": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.interrupt", "parameters": [ { @@ -3253,6 +3881,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -3277,9 +3915,7 @@ }, "/api/session/{sessionID}/background": { "post": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.background", "parameters": [ { @@ -3321,6 +3957,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -3345,9 +3991,7 @@ }, "/api/session/{sessionID}/message/{messageID}": { "get": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.session.message", "parameters": [ { @@ -3390,9 +4034,7 @@ "$ref": "#/components/schemas/Session.Message.Info" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -3418,6 +4060,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | MessageNotFoundError", "content": { @@ -3445,9 +4097,7 @@ }, "/api/session/{sessionID}/message": { "get": { - "tags": [ - "session" - ], + "tags": ["session"], "operationId": "v2.message.list", "parameters": [ { @@ -3486,10 +4136,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "asc", - "desc" - ] + "enum": ["asc", "desc"] }, { "type": "null" @@ -3555,6 +4202,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -3589,9 +4246,7 @@ }, "/api/model": { "get": { - "tags": [ - "model" - ], + "tags": ["model"], "operationId": "v2.model.list", "parameters": [ { @@ -3654,10 +4309,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -3683,6 +4335,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "503": { "description": "ServiceUnavailableError", "content": { @@ -3700,9 +4362,7 @@ }, "/api/model/default": { "get": { - "tags": [ - "model" - ], + "tags": ["model"], "operationId": "v2.model.default", "parameters": [ { @@ -3769,10 +4429,7 @@ ] } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -3798,6 +4455,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "503": { "description": "ServiceUnavailableError", "content": { @@ -3815,9 +4482,7 @@ }, "/api/generate": { "post": { - "tags": [ - "generate" - ], + "tags": ["generate"], "operationId": "v2.generate.text", "parameters": [ { @@ -3900,6 +4565,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "503": { "description": "ServiceUnavailableError", "content": { @@ -3933,9 +4608,7 @@ ] } }, - "required": [ - "prompt" - ], + "required": ["prompt"], "additionalProperties": false } } @@ -3946,9 +4619,7 @@ }, "/api/provider": { "get": { - "tags": [ - "provider" - ], + "tags": ["provider"], "operationId": "v2.provider.list", "parameters": [ { @@ -4011,10 +4682,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -4040,6 +4708,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "503": { "description": "ServiceUnavailableError", "content": { @@ -4057,9 +4735,7 @@ }, "/api/provider/{providerID}": { "get": { - "tags": [ - "provider" - ], + "tags": ["provider"], "operationId": "v2.provider.get", "parameters": [ { @@ -4127,10 +4803,7 @@ "$ref": "#/components/schemas/ProviderV2.Info" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -4156,6 +4829,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "ProviderNotFoundError", "content": { @@ -4183,9 +4866,7 @@ }, "/api/integration": { "get": { - "tags": [ - "integration" - ], + "tags": ["integration"], "operationId": "v2.integration.list", "parameters": [ { @@ -4248,10 +4929,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -4276,6 +4954,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve available integrations and their authentication methods.", @@ -4284,9 +4972,7 @@ }, "/api/integration/{integrationID}": { "get": { - "tags": [ - "integration" - ], + "tags": ["integration"], "operationId": "v2.integration.get", "parameters": [ { @@ -4361,10 +5047,7 @@ ] } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -4389,6 +5072,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve one integration and its authentication methods.", @@ -4397,9 +5090,7 @@ }, "/api/integration/{integrationID}/connect/key": { "post": { - "tags": [ - "integration" - ], + "tags": ["integration"], "operationId": "v2.integration.connect.key", "parameters": [ { @@ -4482,6 +5173,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Run a key authentication method and store the resulting credential.", @@ -4506,9 +5207,7 @@ ] } }, - "required": [ - "key" - ], + "required": ["key"], "additionalProperties": false } } @@ -4519,9 +5218,7 @@ }, "/api/integration/{integrationID}/connect/oauth": { "post": { - "tags": [ - "integration" - ], + "tags": ["integration"], "operationId": "v2.integration.connect.oauth", "parameters": [ { @@ -4589,10 +5286,7 @@ "$ref": "#/components/schemas/Integration.Attempt" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -4624,6 +5318,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Start an OAuth attempt and return the authorization details.", @@ -4654,10 +5358,7 @@ ] } }, - "required": [ - "methodID", - "inputs" - ], + "required": ["methodID", "inputs"], "additionalProperties": false } } @@ -4668,9 +5369,7 @@ }, "/api/integration/attempt/{attemptID}": { "get": { - "tags": [ - "integration" - ], + "tags": ["integration"], "operationId": "v2.integration.attempt.status", "parameters": [ { @@ -4738,10 +5437,7 @@ "$ref": "#/components/schemas/Integration.AttemptStatus" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -4766,15 +5462,23 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Poll the current status of an OAuth attempt.", "summary": "Get OAuth attempt status" }, "delete": { - "tags": [ - "integration" - ], + "tags": ["integration"], "operationId": "v2.integration.attempt.cancel", "parameters": [ { @@ -4850,6 +5554,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Cancel an OAuth attempt and release its resources.", @@ -4858,9 +5572,7 @@ }, "/api/integration/attempt/{attemptID}/complete": { "post": { - "tags": [ - "integration" - ], + "tags": ["integration"], "operationId": "v2.integration.attempt.complete", "parameters": [ { @@ -4943,6 +5655,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Complete a code-based OAuth attempt and store the resulting credential.", @@ -4974,9 +5696,7 @@ }, "/api/mcp": { "get": { - "tags": [ - "mcp" - ], + "tags": ["mcp"], "operationId": "v2.mcp.list", "parameters": [ { @@ -5039,10 +5759,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -5067,6 +5784,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve configured MCP servers and their connection status.", @@ -5075,9 +5802,7 @@ }, "/api/mcp/resource": { "get": { - "tags": [ - "mcp" - ], + "tags": ["mcp"], "operationId": "v2.mcp.resource.catalog", "parameters": [ { @@ -5137,10 +5862,7 @@ "$ref": "#/components/schemas/Mcp.ResourceCatalog" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -5165,6 +5887,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve resources and resource templates from connected MCP servers.", @@ -5173,9 +5905,7 @@ }, "/api/credential/{credentialID}": { "patch": { - "tags": [ - "credential" - ], + "tags": ["credential"], "operationId": "v2.credential.update", "parameters": [ { @@ -5251,6 +5981,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Update a stored credential label.", @@ -5265,9 +6005,7 @@ "type": "string" } }, - "required": [ - "label" - ], + "required": ["label"], "additionalProperties": false } } @@ -5276,9 +6014,7 @@ } }, "delete": { - "tags": [ - "credential" - ], + "tags": ["credential"], "operationId": "v2.credential.remove", "parameters": [ { @@ -5354,6 +6090,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Remove a stored integration credential.", @@ -5362,9 +6108,7 @@ }, "/api/project": { "get": { - "tags": [ - "project" - ], + "tags": ["project"], "operationId": "v2.project.list", "parameters": [], "security": [], @@ -5401,6 +6145,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List known projects.", @@ -5409,9 +6163,7 @@ }, "/api/project/current": { "get": { - "tags": [ - "project" - ], + "tags": ["project"], "operationId": "v2.project.current", "parameters": [ { @@ -5486,6 +6238,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Resolve the project for the requested location.", @@ -5494,9 +6256,7 @@ }, "/api/project/{projectID}/directories": { "get": { - "tags": [ - "project" - ], + "tags": ["project"], "operationId": "v2.project.directories", "parameters": [ { @@ -5579,6 +6339,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List known local absolute directories for a project.", @@ -5587,9 +6357,7 @@ }, "/api/form/request": { "get": { - "tags": [ - "form" - ], + "tags": ["form"], "operationId": "v2.form.request.list", "parameters": [ { @@ -5648,21 +6416,11 @@ "data": { "type": "array", "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.FormInfo" - }, - { - "$ref": "#/components/schemas/Form.UrlInfo" - } - ] + "$ref": "#/components/schemas/Form.Info" } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -5687,6 +6445,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve pending forms for a location.", @@ -5695,9 +6463,7 @@ }, "/api/session/{sessionID}/form": { "get": { - "tags": [ - "form" - ], + "tags": ["form"], "operationId": "v2.session.form.list", "parameters": [ { @@ -5721,20 +6487,11 @@ "data": { "type": "array", "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.FormInfo" - }, - { - "$ref": "#/components/schemas/Form.UrlInfo" - } - ] + "$ref": "#/components/schemas/Form.Info" } } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -5760,6 +6517,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -5782,9 +6549,7 @@ "summary": "List session forms" }, "post": { - "tags": [ - "form" - ], + "tags": ["form"], "operationId": "v2.session.form.create", "parameters": [ { @@ -5806,19 +6571,10 @@ "type": "object", "properties": { "data": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.FormInfo" - }, - { - "$ref": "#/components/schemas/Form.UrlInfo" - } - ] + "$ref": "#/components/schemas/Form.Info" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -5851,6 +6607,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -5895,9 +6661,7 @@ }, "/api/session/{sessionID}/form/{formID}": { "get": { - "tags": [ - "form" - ], + "tags": ["form"], "operationId": "v2.session.form.get", "parameters": [ { @@ -5932,19 +6696,10 @@ "type": "object", "properties": { "data": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.FormInfo" - }, - { - "$ref": "#/components/schemas/Form.UrlInfo" - } - ] + "$ref": "#/components/schemas/Form.Info" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -5970,6 +6725,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | FormNotFoundError", "content": { @@ -5997,9 +6762,7 @@ }, "/api/session/{sessionID}/form/{formID}/state": { "get": { - "tags": [ - "form" - ], + "tags": ["form"], "operationId": "v2.session.form.state", "parameters": [ { @@ -6037,9 +6800,7 @@ "$ref": "#/components/schemas/Form.State" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -6065,6 +6826,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | FormNotFoundError", "content": { @@ -6092,9 +6863,7 @@ }, "/api/session/{sessionID}/form/{formID}/reply": { "post": { - "tags": [ - "form" - ], + "tags": ["form"], "operationId": "v2.session.form.reply", "parameters": [ { @@ -6151,6 +6920,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | FormNotFoundError", "content": { @@ -6198,9 +6977,7 @@ }, "/api/session/{sessionID}/form/{formID}/cancel": { "post": { - "tags": [ - "form" - ], + "tags": ["form"], "operationId": "v2.session.form.cancel", "parameters": [ { @@ -6250,6 +7027,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | FormNotFoundError", "content": { @@ -6287,9 +7074,7 @@ }, "/api/permission/request": { "get": { - "tags": [ - "permission" - ], + "tags": ["permission"], "operationId": "v2.permission.request.list", "parameters": [ { @@ -6352,10 +7137,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -6380,6 +7162,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve pending permission requests for a location.", @@ -6388,9 +7180,7 @@ }, "/api/permission/saved": { "get": { - "tags": [ - "permission" - ], + "tags": ["permission"], "operationId": "v2.permission.saved.list", "parameters": [ { @@ -6425,9 +7215,7 @@ } } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -6452,6 +7240,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve saved permissions, optionally filtered by project.", @@ -6460,9 +7258,7 @@ }, "/api/permission/saved/{id}": { "delete": { - "tags": [ - "permission" - ], + "tags": ["permission"], "operationId": "v2.permission.saved.remove", "parameters": [ { @@ -6498,6 +7294,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Remove a saved permission by ID.", @@ -6506,9 +7312,7 @@ }, "/api/session/{sessionID}/permission": { "post": { - "tags": [ - "permission" - ], + "tags": ["permission"], "operationId": "v2.session.permission.create", "parameters": [ { @@ -6549,16 +7353,11 @@ "$ref": "#/components/schemas/PermissionV2.Effect" } }, - "required": [ - "id", - "effect" - ], + "required": ["id", "effect"], "additionalProperties": false } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -6584,6 +7383,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -6657,10 +7466,7 @@ ] } }, - "required": [ - "action", - "resources" - ], + "required": ["action", "resources"], "additionalProperties": false } } @@ -6669,9 +7475,7 @@ } }, "get": { - "tags": [ - "permission" - ], + "tags": ["permission"], "operationId": "v2.session.permission.list", "parameters": [ { @@ -6704,9 +7508,7 @@ } } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -6732,6 +7534,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -6756,9 +7568,7 @@ }, "/api/session/{sessionID}/permission/{requestID}": { "get": { - "tags": [ - "permission" - ], + "tags": ["permission"], "operationId": "v2.session.permission.get", "parameters": [ { @@ -6801,9 +7611,7 @@ "$ref": "#/components/schemas/PermissionV2.Request" } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -6829,6 +7637,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | PermissionNotFoundError", "content": { @@ -6856,9 +7674,7 @@ }, "/api/session/{sessionID}/permission/{requestID}/reply": { "post": { - "tags": [ - "permission" - ], + "tags": ["permission"], "operationId": "v2.session.permission.reply", "parameters": [ { @@ -6913,6 +7729,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | PermissionNotFoundError", "content": { @@ -6956,9 +7782,7 @@ ] } }, - "required": [ - "reply" - ], + "required": ["reply"], "additionalProperties": false } } @@ -6969,9 +7793,7 @@ }, "/api/fs/read/*": { "get": { - "tags": [ - "filesystem" - ], + "tags": ["filesystem"], "operationId": "v2.fs.read", "parameters": [ { @@ -7047,6 +7869,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Serve one file relative to the requested location.", @@ -7055,9 +7887,7 @@ }, "/api/fs/list": { "get": { - "tags": [ - "filesystem" - ], + "tags": ["filesystem"], "operationId": "v2.fs.list", "parameters": [ { @@ -7135,10 +7965,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -7163,6 +7990,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List direct children of one directory relative to the requested location.", @@ -7171,9 +8008,7 @@ }, "/api/fs/find": { "get": { - "tags": [ - "filesystem" - ], + "tags": ["filesystem"], "operationId": "v2.fs.find", "parameters": [ { @@ -7229,10 +8064,7 @@ "in": "query", "schema": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] }, "required": false }, @@ -7271,10 +8103,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -7299,6 +8128,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Find recursively ranked filesystem entries relative to the requested location.", @@ -7307,9 +8146,7 @@ }, "/api/command": { "get": { - "tags": [ - "command" - ], + "tags": ["command"], "operationId": "v2.command.list", "parameters": [ { @@ -7372,10 +8209,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -7400,6 +8234,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve currently registered commands.", @@ -7408,9 +8252,7 @@ }, "/api/skill": { "get": { - "tags": [ - "skill" - ], + "tags": ["skill"], "operationId": "v2.skill.list", "parameters": [ { @@ -7473,10 +8315,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -7501,6 +8340,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve currently registered skills.", @@ -7509,9 +8358,7 @@ }, "/api/event": { "get": { - "tags": [ - "event" - ], + "tags": ["event"], "operationId": "v2.event.subscribe", "parameters": [], "security": [], @@ -7540,11 +8387,7 @@ "$ref": "#/components/schemas/V2EventStream" } }, - "required": [ - "id", - "event", - "data" - ], + "required": ["id", "event", "data"], "additionalProperties": false }, "x-effect-stream": { @@ -7558,18 +8401,13 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "Fail" - ] + "enum": ["Fail"] }, "error": { "not": {} } }, - "required": [ - "_tag", - "error" - ], + "required": ["_tag", "error"], "additionalProperties": false }, { @@ -7577,16 +8415,11 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "Die" - ] + "enum": ["Die"] }, "defect": {} }, - "required": [ - "_tag", - "defect" - ], + "required": ["_tag", "defect"], "additionalProperties": false }, { @@ -7594,9 +8427,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "Interrupt" - ] + "enum": ["Interrupt"] }, "fiberId": { "anyOf": [ @@ -7609,10 +8440,7 @@ ] } }, - "required": [ - "_tag", - "fiberId" - ], + "required": ["_tag", "fiberId"], "additionalProperties": false } ] @@ -7645,6 +8473,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Subscribe to native event payloads for the server. Volatile by contract: a slow consumer overflows and fails the stream, and events during disconnection are missed.", @@ -7653,9 +8491,7 @@ }, "/api/pty": { "get": { - "tags": [ - "pty" - ], + "tags": ["pty"], "operationId": "v2.pty.list", "parameters": [ { @@ -7718,10 +8554,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -7746,15 +8579,23 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List PTY sessions for a location, including exited sessions retained until removal.", "summary": "List PTY sessions" }, "post": { - "tags": [ - "pty" - ], + "tags": ["pty"], "operationId": "v2.pty.create", "parameters": [ { @@ -7814,10 +8655,7 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -7842,6 +8680,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Create a pseudo-terminal session for a location.", @@ -7884,9 +8732,7 @@ }, "/api/pty/{ptyID}": { "get": { - "tags": [ - "pty" - ], + "tags": ["pty"], "operationId": "v2.pty.get", "parameters": [ { @@ -7959,10 +8805,7 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -7988,6 +8831,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "PtyNotFoundError", "content": { @@ -8003,9 +8856,7 @@ "summary": "Get PTY session" }, "put": { - "tags": [ - "pty" - ], + "tags": ["pty"], "operationId": "v2.pty.update", "parameters": [ { @@ -8078,10 +8929,7 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -8107,6 +8955,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "PtyNotFoundError", "content": { @@ -8149,10 +9007,7 @@ ] } }, - "required": [ - "rows", - "cols" - ], + "required": ["rows", "cols"], "additionalProperties": false } }, @@ -8164,9 +9019,7 @@ } }, "delete": { - "tags": [ - "pty" - ], + "tags": ["pty"], "operationId": "v2.pty.remove", "parameters": [ { @@ -8248,6 +9101,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "PtyNotFoundError", "content": { @@ -8265,9 +9128,7 @@ }, "/api/pty/{ptyID}/connect-token": { "post": { - "tags": [ - "pty" - ], + "tags": ["pty"], "operationId": "v2.pty.connect.token", "parameters": [ { @@ -8340,10 +9201,7 @@ "$ref": "#/components/schemas/PtyTicket.ConnectToken" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -8374,7 +9232,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ForbiddenError" + "anyOf": [ + { + "$ref": "#/components/schemas/ForbiddenError" + }, + { + "$ref": "#/components/schemas/ForbiddenError" + } + ] } } } @@ -8396,9 +9261,7 @@ }, "/api/pty/{ptyID}/connect": { "get": { - "tags": [ - "pty" - ], + "tags": ["pty"], "operationId": "v2.pty.connect", "parameters": [ { @@ -8480,7 +9343,14 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ForbiddenError" + "anyOf": [ + { + "$ref": "#/components/schemas/ForbiddenError" + }, + { + "$ref": "#/components/schemas/ForbiddenError" + } + ] } } } @@ -8503,9 +9373,7 @@ }, "/api/shell": { "get": { - "tags": [ - "shell" - ], + "tags": ["shell"], "operationId": "v2.shell.list", "parameters": [ { @@ -8564,14 +9432,11 @@ "data": { "type": "array", "items": { - "$ref": "#/components/schemas/Shell1" + "$ref": "#/components/schemas/Shell.Info1" } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -8596,15 +9461,23 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List currently running shell commands for a location. Exited commands are not included.", "summary": "List running shell commands" }, "post": { - "tags": [ - "shell" - ], + "tags": ["shell"], "operationId": "v2.shell.create", "parameters": [ { @@ -8661,13 +9534,10 @@ "$ref": "#/components/schemas/Location.Info" }, "data": { - "$ref": "#/components/schemas/Shell1" + "$ref": "#/components/schemas/Shell.Info1" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -8692,6 +9562,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Spawn one non-interactive shell command for a location. Combined stdout/stderr is captured to a file pageable via output.", @@ -8720,10 +9600,7 @@ "type": "object" } }, - "required": [ - "command", - "timeout" - ], + "required": ["command", "timeout"], "additionalProperties": false } } @@ -8734,9 +9611,7 @@ }, "/api/shell/{id}": { "get": { - "tags": [ - "shell" - ], + "tags": ["shell"], "operationId": "v2.shell.get", "parameters": [ { @@ -8806,13 +9681,10 @@ "$ref": "#/components/schemas/Location.Info" }, "data": { - "$ref": "#/components/schemas/Shell1" + "$ref": "#/components/schemas/Shell.Info1" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -8838,6 +9710,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "ShellNotFoundError", "content": { @@ -8853,9 +9735,7 @@ "summary": "Get shell command" }, "delete": { - "tags": [ - "shell" - ], + "tags": ["shell"], "operationId": "v2.shell.remove", "parameters": [ { @@ -8937,6 +9817,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "ShellNotFoundError", "content": { @@ -8954,9 +9844,7 @@ }, "/api/shell/{id}/timeout": { "patch": { - "tags": [ - "shell" - ], + "tags": ["shell"], "operationId": "v2.shell.timeout", "parameters": [ { @@ -9026,13 +9914,10 @@ "$ref": "#/components/schemas/Location.Info" }, "data": { - "$ref": "#/components/schemas/Shell1" + "$ref": "#/components/schemas/Shell.Info1" } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -9058,6 +9943,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "ShellNotFoundError", "content": { @@ -9086,9 +9981,7 @@ ] } }, - "required": [ - "timeout" - ], + "required": ["timeout"], "additionalProperties": false } } @@ -9099,9 +9992,7 @@ }, "/api/shell/{id}/output": { "get": { - "tags": [ - "shell" - ], + "tags": ["shell"], "operationId": "v2.shell.output", "parameters": [ { @@ -9222,19 +10113,11 @@ "type": "boolean" } }, - "required": [ - "output", - "cursor", - "size", - "truncated" - ], + "required": ["output", "cursor", "size", "truncated"], "additionalProperties": false } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -9260,6 +10143,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "ShellNotFoundError", "content": { @@ -9277,9 +10170,7 @@ }, "/api/question/request": { "get": { - "tags": [ - "question" - ], + "tags": ["question"], "operationId": "v2.question.request.list", "parameters": [ { @@ -9342,10 +10233,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -9370,6 +10258,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Retrieve pending question requests for a location.", @@ -9378,9 +10276,7 @@ }, "/api/session/{sessionID}/question": { "get": { - "tags": [ - "question" - ], + "tags": ["question"], "operationId": "v2.session.question.list", "parameters": [ { @@ -9413,9 +10309,7 @@ } } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false } } @@ -9441,6 +10335,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError", "content": { @@ -9465,9 +10369,7 @@ }, "/api/session/{sessionID}/question/{requestID}/reply": { "post": { - "tags": [ - "question" - ], + "tags": ["question"], "operationId": "v2.session.question.reply", "parameters": [ { @@ -9522,6 +10424,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | QuestionNotFoundError", "content": { @@ -9559,9 +10471,7 @@ }, "/api/session/{sessionID}/question/{requestID}/reject": { "post": { - "tags": [ - "question" - ], + "tags": ["question"], "operationId": "v2.session.question.reject", "parameters": [ { @@ -9616,6 +10526,16 @@ } } }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + }, "404": { "description": "SessionNotFoundError | QuestionNotFoundError", "content": { @@ -9643,9 +10563,7 @@ }, "/api/reference": { "get": { - "tags": [ - "reference" - ], + "tags": ["reference"], "operationId": "v2.reference.list", "parameters": [ { @@ -9708,10 +10626,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -9736,6 +10651,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List references available in the requested location.", @@ -9744,9 +10669,7 @@ }, "/experimental/project/{projectID}/copy": { "post": { - "tags": [ - "projectCopy" - ], + "tags": ["projectCopy"], "operationId": "v2.projectCopy.create", "parameters": [ { @@ -9836,6 +10759,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "requestBody": { @@ -9854,10 +10787,7 @@ "type": "string" } }, - "required": [ - "strategy", - "directory" - ], + "required": ["strategy", "directory"], "additionalProperties": false } } @@ -9866,9 +10796,7 @@ } }, "delete": { - "tags": [ - "projectCopy" - ], + "tags": ["projectCopy"], "operationId": "v2.projectCopy.remove", "parameters": [ { @@ -9951,6 +10879,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "requestBody": { @@ -9966,10 +10904,7 @@ "type": "boolean" } }, - "required": [ - "directory", - "force" - ], + "required": ["directory", "force"], "additionalProperties": false } } @@ -9980,9 +10915,7 @@ }, "/experimental/project/{projectID}/copy/refresh": { "post": { - "tags": [ - "projectCopy" - ], + "tags": ["projectCopy"], "operationId": "v2.projectCopy.refresh", "parameters": [ { @@ -10065,15 +10998,23 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } } } }, "/api/vcs/status": { "get": { - "tags": [ - "vcs" - ], + "tags": ["vcs"], "operationId": "v2.vcs.status", "parameters": [ { @@ -10136,10 +11077,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -10164,6 +11102,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List uncommitted working-copy changes relative to the requested location.", @@ -10172,9 +11120,7 @@ }, "/api/vcs/diff": { "get": { - "tags": [ - "vcs" - ], + "tags": ["vcs"], "operationId": "v2.vcs.diff", "parameters": [ { @@ -10260,10 +11206,7 @@ } } }, - "required": [ - "location", - "data" - ], + "required": ["location", "data"], "additionalProperties": false } } @@ -10288,6 +11231,16 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "Diff the working copy against HEAD (mode git) or the default-branch merge base (mode branch) for the requested location.", @@ -10296,9 +11249,7 @@ }, "/api/debug/location": { "get": { - "tags": [ - "debug" - ], + "tags": ["debug"], "operationId": "v2.debug.location.list", "parameters": [], "security": [], @@ -10335,15 +11286,23 @@ } } } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } } }, "description": "List locations currently loaded by the server.", "summary": "List loaded locations" }, "delete": { - "tags": [ - "debug" - ], + "tags": ["debug"], "operationId": "v2.debug.location.evict", "parameters": [ { @@ -10400,71 +11359,309 @@ "$ref": "#/components/schemas/InvalidRequestError" } } - } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "403": { + "description": "ForbiddenError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForbiddenError" + } + } + } + } + }, + "description": "Dispose the requested location's cached services so its next use boots them fresh.", + "summary": "Evict a loaded location" + } + } + }, + "components": { + "schemas": { + "UnauthorizedError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["UnauthorizedError"] + }, + "message": { + "type": "string" + } + }, + "required": ["_tag", "message"], + "additionalProperties": false + }, + "ForbiddenError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["ForbiddenError"] + }, + "message": { + "type": "string" + } + }, + "required": ["_tag", "message"], + "additionalProperties": false + }, + "InvalidRequestError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["InvalidRequestError"] + }, + "message": { + "type": "string" + }, + "kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": ["_tag", "message"], + "additionalProperties": false + }, + "Pairing.Invitation": { + "type": "object", + "properties": { + "v": { + "type": "number", + "enum": [1] + }, + "kind": { + "type": "string", + "enum": ["shuvcode.pair"] + }, + "urls": { + "type": "array", + "items": { + "type": "string" + } + }, + "token": { + "type": "string", + "allOf": [ + { + "pattern": "^[A-Za-z0-9_-]{43}$" + } + ] + }, + "expiresAt": { + "type": "string", + "allOf": [ + { + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" + } + ] + } + }, + "required": ["v", "kind", "urls", "token", "expiresAt"], + "additionalProperties": false + }, + "ServiceUnavailableError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["ServiceUnavailableError"] + }, + "message": { + "type": "string" + }, + "service": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": ["_tag", "message"], + "additionalProperties": false + }, + "Pairing.RedeemRequest": { + "type": "object", + "properties": { + "token": { + "type": "string", + "allOf": [ + { + "pattern": "^[A-Za-z0-9_-]{43}$" + } + ] + }, + "requestID": { + "type": "string", + "allOf": [ + { + "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + } + ] + }, + "deviceName": { + "type": "string" + }, + "credential": { + "type": "string", + "allOf": [ + { + "pattern": "^scd_v1_[A-Za-z0-9_-]{43}$" + } + ] + } + }, + "required": ["token", "requestID", "deviceName", "credential"], + "additionalProperties": false + }, + "Pairing.RedeemResponse": { + "type": "object", + "properties": { + "deviceID": { + "type": "string", + "allOf": [ + { + "pattern": "^device_" + } + ] + } + }, + "required": ["deviceID"], + "additionalProperties": false + }, + "InvalidRequestError1": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["InvalidRequestError"] + }, + "message": { + "type": "string" + }, + "kind": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ] }, - "401": { - "description": "UnauthorizedError", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/UnauthorizedError" - } + "field": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" } - } + ] } }, - "description": "Dispose the requested location's cached services so its next use boots them fresh.", - "summary": "Evict a loaded location" - } - } - }, - "components": { - "schemas": { - "UnauthorizedError": { + "required": ["_tag", "message"], + "additionalProperties": false + }, + "PairingConflictError": { "type": "object", "properties": { "_tag": { "type": "string", - "enum": [ - "UnauthorizedError" - ] + "enum": ["PairingConflictError"] }, "message": { "type": "string" } }, - "required": [ - "_tag", - "message" - ], + "required": ["_tag", "message"], "additionalProperties": false }, - "InvalidRequestError": { + "PairingInvitationUnavailableError": { "type": "object", "properties": { "_tag": { "type": "string", - "enum": [ - "InvalidRequestError" - ] + "enum": ["PairingInvitationUnavailableError"] }, "message": { "type": "string" + } + }, + "required": ["_tag", "message"], + "additionalProperties": false + }, + "Pairing.Device": { + "type": "object", + "properties": { + "deviceID": { + "type": "string", + "allOf": [ + { + "pattern": "^device_" + } + ] }, - "kind": { - "anyOf": [ + "name": { + "type": "string" + }, + "createdAt": { + "type": "string", + "allOf": [ { - "type": "string" - }, + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" + } + ] + }, + "updatedAt": { + "type": "string", + "allOf": [ { - "type": "null" + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" } ] }, - "field": { + "revokedAt": { "anyOf": [ { - "type": "string" + "type": "string", + "allOf": [ + { + "pattern": "^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$" + } + ] }, { "type": "null" @@ -10472,10 +11669,24 @@ ] } }, - "required": [ - "_tag", - "message" - ], + "required": ["deviceID", "name", "createdAt", "updatedAt"], + "additionalProperties": false + }, + "PairingDeviceNotFoundError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["PairingDeviceNotFoundError"] + }, + "deviceID": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": ["_tag", "deviceID", "message"], "additionalProperties": false }, "Location.Info": { @@ -10502,17 +11713,11 @@ "type": "string" } }, - "required": [ - "id", - "directory" - ], + "required": ["id", "directory"], "additionalProperties": false } }, - "required": [ - "directory", - "project" - ], + "required": ["directory", "project"], "additionalProperties": false }, "Model.Ref": { @@ -10528,10 +11733,7 @@ "type": "string" } }, - "required": [ - "id", - "providerID" - ], + "required": ["id", "providerID"], "additionalProperties": false }, "Provider.Settings": { @@ -10553,11 +11755,7 @@ "type": "object" } }, - "required": [ - "settings", - "headers", - "body" - ], + "required": ["settings", "headers", "body"], "additionalProperties": false }, "Agent.Color": { @@ -10572,25 +11770,13 @@ }, { "type": "string", - "enum": [ - "primary", - "secondary", - "accent", - "success", - "warning", - "error", - "info" - ] + "enum": ["primary", "secondary", "accent", "success", "warning", "error", "info"] } ] }, "PermissionV2.Effect": { "type": "string", - "enum": [ - "allow", - "deny", - "ask" - ] + "enum": ["allow", "deny", "ask"] }, "PermissionV2.Rule": { "type": "object", @@ -10605,11 +11791,7 @@ "$ref": "#/components/schemas/PermissionV2.Effect" } }, - "required": [ - "action", - "resource", - "effect" - ], + "required": ["action", "resource", "effect"], "additionalProperties": false }, "PermissionV2.Ruleset": { @@ -10641,11 +11823,7 @@ }, "mode": { "type": "string", - "enum": [ - "subagent", - "primary", - "all" - ] + "enum": ["subagent", "primary", "all"] }, "hidden": { "type": "boolean" @@ -10665,14 +11843,7 @@ "$ref": "#/components/schemas/PermissionV2.Ruleset" } }, - "required": [ - "id", - "name", - "request", - "mode", - "hidden", - "permissions" - ], + "required": ["id", "name", "request", "mode", "hidden", "permissions"], "additionalProperties": false }, "Plugin.Info": { @@ -10682,9 +11853,7 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "Money.USD": { @@ -10712,19 +11881,11 @@ "type": "number" } }, - "required": [ - "read", - "write" - ], + "required": ["read", "write"], "additionalProperties": false } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ], + "required": ["input", "output", "reasoning", "cache"], "additionalProperties": false }, "Location.Ref": { @@ -10742,9 +11903,7 @@ ] } }, - "required": [ - "directory" - ], + "required": ["directory"], "additionalProperties": false }, "FileDiff.Info": { @@ -10774,20 +11933,10 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "patch", - "additions", - "deletions", - "status" - ], + "required": ["file", "patch", "additions", "deletions", "status"], "additionalProperties": false }, "Session.Revert": { @@ -10814,9 +11963,7 @@ } } }, - "required": [ - "messageID" - ], + "required": ["messageID"], "additionalProperties": false }, "Session.Info": { @@ -10858,9 +12005,7 @@ ] } }, - "required": [ - "sessionID" - ], + "required": ["sessionID"], "additionalProperties": false }, "projectID": { @@ -10891,10 +12036,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ], + "required": ["created", "updated"], "additionalProperties": false }, "title": { @@ -10910,15 +12052,7 @@ "$ref": "#/components/schemas/Session.Revert" } }, - "required": [ - "id", - "projectID", - "cost", - "tokens", - "time", - "title", - "location" - ], + "required": ["id", "projectID", "cost", "tokens", "time", "title", "location"], "additionalProperties": false }, "SessionsResponse": { @@ -10957,10 +12091,7 @@ "additionalProperties": false } }, - "required": [ - "data", - "cursor" - ], + "required": ["data", "cursor"], "additionalProperties": false }, "InvalidCursorError": { @@ -10968,57 +12099,13 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "InvalidCursorError" - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "_tag", - "message" - ], - "additionalProperties": false - }, - "InvalidRequestError1": { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "InvalidRequestError" - ] + "enum": ["InvalidCursorError"] }, "message": { "type": "string" - }, - "kind": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] - }, - "field": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] } }, - "required": [ - "_tag", - "message" - ], + "required": ["_tag", "message"], "additionalProperties": false }, "SessionActive": { @@ -11026,14 +12113,10 @@ "properties": { "type": { "type": "string", - "enum": [ - "running" - ] + "enum": ["running"] } }, - "required": [ - "type" - ], + "required": ["type"], "additionalProperties": false }, "SessionNotFoundError": { @@ -11041,9 +12124,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "SessionNotFoundError" - ] + "enum": ["SessionNotFoundError"] }, "sessionID": { "type": "string" @@ -11052,11 +12133,7 @@ "type": "string" } }, - "required": [ - "_tag", - "sessionID", - "message" - ], + "required": ["_tag", "sessionID", "message"], "additionalProperties": false }, "MessageNotFoundError": { @@ -11064,9 +12141,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "MessageNotFoundError" - ] + "enum": ["MessageNotFoundError"] }, "sessionID": { "type": "string" @@ -11078,12 +12153,7 @@ "type": "string" } }, - "required": [ - "_tag", - "sessionID", - "messageID", - "message" - ], + "required": ["_tag", "sessionID", "messageID", "message"], "additionalProperties": false }, "Prompt.Mention": { @@ -11099,11 +12169,7 @@ "type": "string" } }, - "required": [ - "start", - "end", - "text" - ], + "required": ["start", "end", "text"], "additionalProperties": false }, "PromptInput.FileAttachment": { @@ -11122,9 +12188,7 @@ "$ref": "#/components/schemas/Prompt.Mention" } }, - "required": [ - "uri" - ], + "required": ["uri"], "additionalProperties": false }, "Prompt.AgentAttachment": { @@ -11137,33 +12201,7 @@ "$ref": "#/components/schemas/Prompt.Mention" } }, - "required": [ - "name" - ], - "additionalProperties": false - }, - "PromptInput": { - "type": "object", - "properties": { - "text": { - "type": "string" - }, - "files": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PromptInput.FileAttachment" - } - }, - "agents": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Prompt.AgentAttachment" - } - } - }, - "required": [ - "text" - ], + "required": ["name"], "additionalProperties": false }, "Prompt.Base64": { @@ -11181,14 +12219,10 @@ "properties": { "type": { "type": "string", - "enum": [ - "inline" - ] + "enum": ["inline"] } }, - "required": [ - "type" - ], + "required": ["type"], "additionalProperties": false }, { @@ -11196,18 +12230,13 @@ "properties": { "type": { "type": "string", - "enum": [ - "uri" - ] + "enum": ["uri"] }, "uri": { "type": "string" } }, - "required": [ - "type", - "uri" - ], + "required": ["type", "uri"], "additionalProperties": false } ] @@ -11234,14 +12263,10 @@ "$ref": "#/components/schemas/Prompt.Mention" } }, - "required": [ - "data", - "mime", - "source" - ], + "required": ["data", "mime", "source"], "additionalProperties": false }, - "Prompt": { + "SessionPending.UserData": { "type": "object", "properties": { "text": { @@ -11258,14 +12283,15 @@ "items": { "$ref": "#/components/schemas/Prompt.AgentAttachment" } + }, + "metadata": { + "type": "object" } }, - "required": [ - "text" - ], + "required": ["text"], "additionalProperties": false }, - "SessionInput.Admitted": { + "SessionPending.User": { "type": "object", "properties": { "admittedSeq": { @@ -11292,36 +12318,22 @@ } ] }, - "prompt": { - "$ref": "#/components/schemas/Prompt" + "timeCreated": { + "type": "number" }, - "delivery": { + "type": { "type": "string", - "enum": [ - "steer", - "queue" - ] + "enum": ["user"] }, - "timeCreated": { - "type": "number" + "data": { + "$ref": "#/components/schemas/SessionPending.UserData" }, - "promotedSeq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] + "delivery": { + "type": "string", + "enum": ["steer", "queue"] } }, - "required": [ - "admittedSeq", - "id", - "sessionID", - "prompt", - "delivery", - "timeCreated" - ], + "required": ["admittedSeq", "id", "sessionID", "timeCreated", "type", "data", "delivery"], "additionalProperties": false }, "ConflictError": { @@ -11329,9 +12341,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "ConflictError" - ] + "enum": ["ConflictError"] }, "message": { "type": "string" @@ -11347,10 +12357,7 @@ ] } }, - "required": [ - "_tag", - "message" - ], + "required": ["_tag", "message"], "additionalProperties": false }, "CommandNotFoundError": { @@ -11358,9 +12365,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "CommandNotFoundError" - ] + "enum": ["CommandNotFoundError"] }, "command": { "type": "string" @@ -11369,11 +12374,7 @@ "type": "string" } }, - "required": [ - "_tag", - "command", - "message" - ], + "required": ["_tag", "command", "message"], "additionalProperties": false }, "CommandEvaluationError": { @@ -11381,9 +12382,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "CommandEvaluationError" - ] + "enum": ["CommandEvaluationError"] }, "command": { "type": "string" @@ -11392,11 +12391,7 @@ "type": "string" } }, - "required": [ - "_tag", - "command", - "message" - ], + "required": ["_tag", "command", "message"], "additionalProperties": false }, "SkillNotFoundError": { @@ -11404,9 +12399,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "SkillNotFoundError" - ] + "enum": ["SkillNotFoundError"] }, "skill": { "type": "string" @@ -11415,22 +12408,28 @@ "type": "string" } }, - "required": [ - "_tag", - "skill", - "message" - ], + "required": ["_tag", "skill", "message"], "additionalProperties": false }, - "SessionInput.Compaction": { + "SessionPending.SyntheticData": { "type": "object", "properties": { - "type": { - "type": "string", - "enum": [ - "compaction" - ] + "text": { + "type": "string" + }, + "description": { + "type": "string" }, + "metadata": { + "type": "object" + } + }, + "required": ["text"], + "additionalProperties": false + }, + "SessionPending.Synthetic": { + "type": "object", + "properties": { "admittedSeq": { "type": "integer", "allOf": [ @@ -11458,51 +12457,57 @@ "timeCreated": { "type": "number" }, - "handledSeq": { - "type": "integer", - "allOf": [ - { - "minimum": 0 - } - ] + "type": { + "type": "string", + "enum": ["synthetic"] + }, + "data": { + "$ref": "#/components/schemas/SessionPending.SyntheticData" + }, + "delivery": { + "type": "string", + "enum": ["steer", "queue"] } }, - "required": [ - "type", - "admittedSeq", - "id", - "sessionID", - "timeCreated" - ], + "required": ["admittedSeq", "id", "sessionID", "timeCreated", "type", "data", "delivery"], "additionalProperties": false }, - "ServiceUnavailableError": { + "SessionPending.Compaction": { "type": "object", "properties": { - "_tag": { - "type": "string", - "enum": [ - "ServiceUnavailableError" + "admittedSeq": { + "type": "integer", + "allOf": [ + { + "minimum": 0 + } ] }, - "message": { - "type": "string" - }, - "service": { - "anyOf": [ + "id": { + "type": "string", + "allOf": [ { - "type": "string" - }, + "pattern": "^msg_" + } + ] + }, + "sessionID": { + "type": "string", + "allOf": [ { - "type": "null" + "pattern": "^ses" } ] + }, + "timeCreated": { + "type": "number" + }, + "type": { + "type": "string", + "enum": ["compaction"] } }, - "required": [ - "_tag", - "message" - ], + "required": ["admittedSeq", "id", "sessionID", "timeCreated", "type"], "additionalProperties": false }, "SessionBusyError": { @@ -11510,9 +12515,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "SessionBusyError" - ] + "enum": ["SessionBusyError"] }, "sessionID": { "type": "string" @@ -11521,11 +12524,7 @@ "type": "string" } }, - "required": [ - "_tag", - "sessionID", - "message" - ], + "required": ["_tag", "sessionID", "message"], "additionalProperties": false }, "UnknownError": { @@ -11533,9 +12532,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "UnknownError" - ] + "enum": ["UnknownError"] }, "message": { "type": "string" @@ -11551,10 +12548,7 @@ ] } }, - "required": [ - "_tag", - "message" - ], + "required": ["_tag", "message"], "additionalProperties": false }, "Session.Message.AgentSelected": { @@ -11578,27 +12572,18 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "type": { "type": "string", - "enum": [ - "agent-switched" - ] + "enum": ["agent-switched"] }, "agent": { "type": "string" } }, - "required": [ - "id", - "time", - "type", - "agent" - ], + "required": ["id", "time", "type", "agent"], "additionalProperties": false }, "Session.Message.ModelSelected": { @@ -11622,16 +12607,12 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "type": { "type": "string", - "enum": [ - "model-switched" - ] + "enum": ["model-switched"] }, "model": { "$ref": "#/components/schemas/Model.Ref" @@ -11640,12 +12621,7 @@ "$ref": "#/components/schemas/Model.Ref" } }, - "required": [ - "id", - "time", - "type", - "model" - ], + "required": ["id", "time", "type", "model"], "additionalProperties": false }, "Session.Message.User": { @@ -11669,9 +12645,7 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "text": { @@ -11691,17 +12665,10 @@ }, "type": { "type": "string", - "enum": [ - "user" - ] + "enum": ["user"] } }, - "required": [ - "id", - "time", - "text", - "type" - ], + "required": ["id", "time", "text", "type"], "additionalProperties": false }, "Session.Message.Synthetic": { @@ -11725,9 +12692,7 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "text": { @@ -11738,17 +12703,10 @@ }, "type": { "type": "string", - "enum": [ - "synthetic" - ] + "enum": ["synthetic"] } }, - "required": [ - "id", - "time", - "text", - "type" - ], + "required": ["id", "time", "text", "type"], "additionalProperties": false }, "Session.Message.System": { @@ -11772,27 +12730,18 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "type": { "type": "string", - "enum": [ - "system" - ] + "enum": ["system"] }, "text": { "type": "string" } }, - "required": [ - "id", - "time", - "type", - "text" - ], + "required": ["id", "time", "type", "text"], "additionalProperties": false }, "Session.Message.Skill": { @@ -11816,16 +12765,12 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "type": { "type": "string", - "enum": [ - "skill" - ] + "enum": ["skill"] }, "skill": { "type": "string" @@ -11837,14 +12782,7 @@ "type": "string" } }, - "required": [ - "id", - "time", - "type", - "skill", - "name", - "text" - ], + "required": ["id", "time", "type", "skill", "name", "text"], "additionalProperties": false }, "Session.Message.Shell": { @@ -11871,16 +12809,12 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "type": { "type": "string", - "enum": [ - "shell" - ] + "enum": ["shell"] }, "shellID": { "type": "string", @@ -11895,12 +12829,7 @@ }, "status": { "type": "string", - "enum": [ - "running", - "exited", - "timeout", - "killed" - ] + "enum": ["running", "exited", "timeout", "killed"] }, "exit": { "anyOf": [ @@ -11911,31 +12840,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -11965,23 +12884,11 @@ "type": "boolean" } }, - "required": [ - "output", - "cursor", - "size", - "truncated" - ], + "required": ["output", "cursor", "size", "truncated"], "additionalProperties": false } }, - "required": [ - "id", - "time", - "type", - "shellID", - "command", - "status" - ], + "required": ["id", "time", "type", "shellID", "command", "status"], "additionalProperties": false }, "Session.Message.Assistant.Text": { @@ -11989,18 +12896,13 @@ "properties": { "type": { "type": "string", - "enum": [ - "text" - ] + "enum": ["text"] }, "text": { "type": "string" } }, - "required": [ - "type", - "text" - ], + "required": ["type", "text"], "additionalProperties": false }, "Session.Message.ProviderState": { @@ -12011,9 +12913,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "reasoning" - ] + "enum": ["reasoning"] }, "text": { "type": "string" @@ -12031,16 +12931,11 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false } }, - "required": [ - "type", - "text" - ], + "required": ["type", "text"], "additionalProperties": false }, "Session.Message.ToolState.Streaming": { @@ -12048,18 +12943,13 @@ "properties": { "status": { "type": "string", - "enum": [ - "streaming" - ] + "enum": ["streaming"] }, "input": { "type": "string" } }, - "required": [ - "status", - "input" - ], + "required": ["status", "input"], "additionalProperties": false }, "Tool.TextContent": { @@ -12067,18 +12957,13 @@ "properties": { "type": { "type": "string", - "enum": [ - "text" - ] + "enum": ["text"] }, "text": { "type": "string" } }, - "required": [ - "type", - "text" - ], + "required": ["type", "text"], "additionalProperties": false }, "Tool.FileContent": { @@ -12086,9 +12971,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "file" - ] + "enum": ["file"] }, "uri": { "type": "string" @@ -12100,11 +12983,7 @@ "type": "string" } }, - "required": [ - "type", - "uri", - "mime" - ], + "required": ["type", "uri", "mime"], "additionalProperties": false }, "LLM.ToolContent": { @@ -12122,9 +13001,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "running" - ] + "enum": ["running"] }, "input": { "type": "object" @@ -12139,12 +13016,7 @@ } } }, - "required": [ - "status", - "input", - "structured", - "content" - ], + "required": ["status", "input", "structured", "content"], "additionalProperties": false }, "Session.Message.ToolState.Completed": { @@ -12152,9 +13024,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "completed" - ] + "enum": ["completed"] }, "input": { "type": "object" @@ -12170,12 +13040,7 @@ }, "result": {} }, - "required": [ - "status", - "input", - "content", - "structured" - ], + "required": ["status", "input", "content", "structured"], "additionalProperties": false }, "Session.StructuredError": { @@ -12188,10 +13053,7 @@ "type": "string" } }, - "required": [ - "type", - "message" - ], + "required": ["type", "message"], "additionalProperties": false }, "Session.Message.ToolState.Error": { @@ -12199,9 +13061,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "error" - ] + "enum": ["error"] }, "input": { "type": "object" @@ -12220,13 +13080,7 @@ }, "result": {} }, - "required": [ - "status", - "input", - "content", - "structured", - "error" - ], + "required": ["status", "input", "content", "structured", "error"], "additionalProperties": false }, "Session.Message.Assistant.Tool": { @@ -12234,9 +13088,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "tool" - ] + "enum": ["tool"] }, "id": { "type": "string" @@ -12282,19 +13134,11 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false } }, - "required": [ - "type", - "id", - "name", - "state", - "time" - ], + "required": ["type", "id", "name", "state", "time"], "additionalProperties": false }, "Session.Message.Assistant.Retry": { @@ -12315,11 +13159,7 @@ "$ref": "#/components/schemas/Session.StructuredError" } }, - "required": [ - "attempt", - "at", - "error" - ], + "required": ["attempt", "at", "error"], "additionalProperties": false }, "Session.Message.Assistant": { @@ -12346,16 +13186,12 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "type": { "type": "string", - "enum": [ - "assistant" - ] + "enum": ["assistant"] }, "agent": { "type": "string" @@ -12399,14 +13235,7 @@ }, "finish": { "type": "string", - "enum": [ - "stop", - "length", - "tool-calls", - "content-filter", - "error", - "unknown" - ] + "enum": ["stop", "length", "tool-calls", "content-filter", "error", "unknown"] }, "cost": { "$ref": "#/components/schemas/Money.USD" @@ -12421,14 +13250,7 @@ "$ref": "#/components/schemas/Session.Message.Assistant.Retry" } }, - "required": [ - "id", - "time", - "type", - "agent", - "model", - "content" - ], + "required": ["id", "time", "type", "agent", "model", "content"], "additionalProperties": false }, "Session.Message.Compaction.Running": { @@ -12436,9 +13258,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "compaction" - ] + "enum": ["compaction"] }, "id": { "type": "string", @@ -12458,23 +13278,16 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "status": { "type": "string", - "enum": [ - "running" - ] + "enum": ["running"] }, "reason": { "type": "string", - "enum": [ - "auto", - "manual" - ] + "enum": ["auto", "manual"] }, "summary": { "type": "string" @@ -12483,15 +13296,7 @@ "type": "string" } }, - "required": [ - "type", - "id", - "time", - "status", - "reason", - "summary", - "recent" - ], + "required": ["type", "id", "time", "status", "reason", "summary", "recent"], "additionalProperties": false }, "Session.Message.Compaction.Completed": { @@ -12499,9 +13304,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "compaction" - ] + "enum": ["compaction"] }, "id": { "type": "string", @@ -12521,23 +13324,16 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "status": { "type": "string", - "enum": [ - "completed" - ] + "enum": ["completed"] }, "reason": { "type": "string", - "enum": [ - "auto", - "manual" - ] + "enum": ["auto", "manual"] }, "summary": { "type": "string" @@ -12546,15 +13342,7 @@ "type": "string" } }, - "required": [ - "type", - "id", - "time", - "status", - "reason", - "summary", - "recent" - ], + "required": ["type", "id", "time", "status", "reason", "summary", "recent"], "additionalProperties": false }, "Session.Message.Compaction.Failed": { @@ -12562,9 +13350,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "compaction" - ] + "enum": ["compaction"] }, "id": { "type": "string", @@ -12584,36 +13370,22 @@ "type": "number" } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "status": { "type": "string", - "enum": [ - "failed" - ] + "enum": ["failed"] }, "reason": { "type": "string", - "enum": [ - "auto", - "manual" - ] + "enum": ["auto", "manual"] }, "error": { "$ref": "#/components/schemas/Session.StructuredError" } }, - "required": [ - "type", - "id", - "time", - "status", - "reason", - "error" - ], + "required": ["type", "id", "time", "status", "reason", "error"], "additionalProperties": false }, "Session.Message.Compaction": { @@ -12660,6 +13432,19 @@ } ] }, + "SessionPending.Info": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionPending.User" + }, + { + "$ref": "#/components/schemas/SessionPending.Synthetic" + }, + { + "$ref": "#/components/schemas/SessionPending.Compaction" + } + ] + }, "InstructionEntry.Key": { "type": "string", "allOf": [ @@ -12677,10 +13462,27 @@ }, "value": {} }, - "required": [ - "key", - "value" - ], + "required": ["key", "value"], + "additionalProperties": false + }, + "InstructionEntryValueTooLargeError": { + "type": "object", + "properties": { + "_tag": { + "type": "string", + "enum": ["InstructionEntryValueTooLargeError"] + }, + "actualBytes": { + "type": "integer" + }, + "maxBytes": { + "type": "integer" + }, + "message": { + "type": "string" + } + }, + "required": ["_tag", "actualBytes", "maxBytes", "message"], "additionalProperties": false }, "session.agent.selected": { @@ -12702,9 +13504,7 @@ }, "type": { "type": "string", - "enum": [ - "session.agent.selected" - ] + "enum": ["session.agent.selected"] }, "durable": { "type": "object", @@ -12722,16 +13522,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -12752,20 +13546,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "agent" - ], + "required": ["sessionID", "agent"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.model.selected": { @@ -12787,9 +13572,7 @@ }, "type": { "type": "string", - "enum": [ - "session.model.selected" - ] + "enum": ["session.model.selected"] }, "durable": { "type": "object", @@ -12807,16 +13590,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -12837,20 +13614,11 @@ "$ref": "#/components/schemas/Model.Ref" } }, - "required": [ - "sessionID", - "model" - ], + "required": ["sessionID", "model"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.moved": { @@ -12872,9 +13640,7 @@ }, "type": { "type": "string", - "enum": [ - "session.moved" - ] + "enum": ["session.moved"] }, "durable": { "type": "object", @@ -12892,16 +13658,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -12925,20 +13685,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "location" - ], + "required": ["sessionID", "location"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.renamed": { @@ -12960,9 +13711,7 @@ }, "type": { "type": "string", - "enum": [ - "session.renamed" - ] + "enum": ["session.renamed"] }, "durable": { "type": "object", @@ -12980,16 +13729,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13010,20 +13753,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "title" - ], + "required": ["sessionID", "title"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.deleted": { @@ -13045,9 +13779,7 @@ }, "type": { "type": "string", - "enum": [ - "session.deleted" - ] + "enum": ["session.deleted"] }, "durable": { "type": "object", @@ -13065,16 +13797,10 @@ }, "version": { "type": "number", - "enum": [ - 2 - ] + "enum": [2] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13092,19 +13818,11 @@ ] } }, - "required": [ - "sessionID" - ], + "required": ["sessionID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.forked": { @@ -13126,9 +13844,7 @@ }, "type": { "type": "string", - "enum": [ - "session.forked" - ] + "enum": ["session.forked"] }, "durable": { "type": "object", @@ -13146,16 +13862,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [2] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13172,11 +13882,19 @@ } ] }, - "parentID": { - "type": "string", + "parentID": { + "type": "string", + "allOf": [ + { + "pattern": "^ses" + } + ] + }, + "parentSeq": { + "type": "integer", "allOf": [ { - "pattern": "^ses" + "minimum": -1 } ] }, @@ -13189,23 +13907,14 @@ ] } }, - "required": [ - "sessionID", - "parentID" - ], + "required": ["sessionID", "parentID", "parentSeq"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, - "session.prompt.promoted": { + "session.input.promoted": { "type": "object", "properties": { "id": { @@ -13224,9 +13933,7 @@ }, "type": { "type": "string", - "enum": [ - "session.prompt.promoted" - ] + "enum": ["session.input.promoted"] }, "durable": { "type": "object", @@ -13244,16 +13951,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13279,23 +13980,101 @@ ] } }, - "required": [ - "sessionID", - "inputID" - ], + "required": ["sessionID", "inputID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], + "additionalProperties": false + }, + "SessionPending.UserData1": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "files": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Prompt.FileAttachment" + } + }, + "agents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Prompt.AgentAttachment" + } + }, + "metadata": { + "type": "object" + } + }, + "required": ["text"], + "additionalProperties": false + }, + "SessionPending.UserMessage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["user"] + }, + "data": { + "$ref": "#/components/schemas/SessionPending.UserData1" + }, + "delivery": { + "type": "string", + "enum": ["steer", "queue"] + } + }, + "required": ["type", "data", "delivery"], + "additionalProperties": false + }, + "SessionPending.SyntheticData1": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "description": { + "type": "string" + }, + "metadata": { + "type": "object" + } + }, + "required": ["text"], + "additionalProperties": false + }, + "SessionPending.SyntheticMessage": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["synthetic"] + }, + "data": { + "$ref": "#/components/schemas/SessionPending.SyntheticData1" + }, + "delivery": { + "type": "string", + "enum": ["steer", "queue"] + } + }, + "required": ["type", "data", "delivery"], "additionalProperties": false }, - "session.prompt.admitted": { + "SessionPending.Message": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionPending.UserMessage" + }, + { + "$ref": "#/components/schemas/SessionPending.SyntheticMessage" + } + ] + }, + "session.input.admitted": { "type": "object", "properties": { "id": { @@ -13314,9 +14093,7 @@ }, "type": { "type": "string", - "enum": [ - "session.prompt.admitted" - ] + "enum": ["session.input.admitted"] }, "durable": { "type": "object", @@ -13334,16 +14111,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13368,33 +14139,15 @@ } ] }, - "prompt": { - "$ref": "#/components/schemas/Prompt" - }, - "delivery": { - "type": "string", - "enum": [ - "steer", - "queue" - ] + "input": { + "$ref": "#/components/schemas/SessionPending.Message" } }, - "required": [ - "sessionID", - "inputID", - "prompt", - "delivery" - ], + "required": ["sessionID", "inputID", "input"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.execution.started": { @@ -13416,9 +14169,7 @@ }, "type": { "type": "string", - "enum": [ - "session.execution.started" - ] + "enum": ["session.execution.started"] }, "durable": { "type": "object", @@ -13436,16 +14187,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13463,19 +14208,11 @@ ] } }, - "required": [ - "sessionID" - ], + "required": ["sessionID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.execution.succeeded": { @@ -13497,9 +14234,7 @@ }, "type": { "type": "string", - "enum": [ - "session.execution.succeeded" - ] + "enum": ["session.execution.succeeded"] }, "durable": { "type": "object", @@ -13517,16 +14252,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13544,19 +14273,11 @@ ] } }, - "required": [ - "sessionID" - ], + "required": ["sessionID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.execution.failed": { @@ -13578,9 +14299,7 @@ }, "type": { "type": "string", - "enum": [ - "session.execution.failed" - ] + "enum": ["session.execution.failed"] }, "durable": { "type": "object", @@ -13598,16 +14317,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13628,20 +14341,11 @@ "$ref": "#/components/schemas/Session.StructuredError" } }, - "required": [ - "sessionID", - "error" - ], + "required": ["sessionID", "error"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.execution.interrupted": { @@ -13663,9 +14367,7 @@ }, "type": { "type": "string", - "enum": [ - "session.execution.interrupted" - ] + "enum": ["session.execution.interrupted"] }, "durable": { "type": "object", @@ -13683,16 +14385,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13711,27 +14407,14 @@ }, "reason": { "type": "string", - "enum": [ - "user", - "shutdown", - "superseded" - ] + "enum": ["user", "shutdown", "superseded"] } }, - "required": [ - "sessionID", - "reason" - ], + "required": ["sessionID", "reason"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.instructions.updated": { @@ -13753,9 +14436,7 @@ }, "type": { "type": "string", - "enum": [ - "session.instructions.updated" - ] + "enum": ["session.instructions.updated"] }, "durable": { "type": "object", @@ -13773,16 +14454,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [2] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13799,24 +14474,31 @@ } ] }, - "text": { - "type": "string" + "delta": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string", + "allOf": [ + { + "pattern": "^[a-f0-9]{64}$" + } + ] + }, + { + "type": "string", + "enum": ["removed"] + } + ] + } } }, - "required": [ - "sessionID", - "text" - ], + "required": ["sessionID", "delta"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.synthetic": { @@ -13838,9 +14520,7 @@ }, "type": { "type": "string", - "enum": [ - "session.synthetic" - ] + "enum": ["session.synthetic"] }, "durable": { "type": "object", @@ -13858,16 +14538,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13894,20 +14568,11 @@ "type": "object" } }, - "required": [ - "sessionID", - "text" - ], + "required": ["sessionID", "text"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.skill.activated": { @@ -13929,9 +14594,7 @@ }, "type": { "type": "string", - "enum": [ - "session.skill.activated" - ] + "enum": ["session.skill.activated"] }, "durable": { "type": "object", @@ -13949,16 +14612,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -13985,25 +14642,14 @@ "type": "string" } }, - "required": [ - "sessionID", - "id", - "name", - "text" - ], + "required": ["sessionID", "id", "name", "text"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, - "Shell": { + "Shell.Info": { "type": "object", "properties": { "id": { @@ -14016,12 +14662,7 @@ }, "status": { "type": "string", - "enum": [ - "running", - "exited", - "timeout", - "killed" - ] + "enum": ["running", "exited", "timeout", "killed"] }, "command": { "type": "string" @@ -14044,29 +14685,7 @@ ] }, "exit": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] + "type": "number" }, "metadata": { "type": "object" @@ -14075,72 +14694,17 @@ "type": "object", "properties": { "started": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] + "type": "number" }, "completed": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] + "type": "number" } }, - "required": [ - "started" - ], + "required": ["started"], "additionalProperties": false } }, - "required": [ - "id", - "status", - "command", - "cwd", - "shell", - "file", - "metadata", - "time" - ], + "required": ["id", "status", "command", "cwd", "shell", "file", "metadata", "time"], "additionalProperties": false }, "session.shell.started": { @@ -14162,9 +14726,7 @@ }, "type": { "type": "string", - "enum": [ - "session.shell.started" - ] + "enum": ["session.shell.started"] }, "durable": { "type": "object", @@ -14182,16 +14744,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14209,23 +14765,14 @@ ] }, "shell": { - "$ref": "#/components/schemas/Shell" + "$ref": "#/components/schemas/Shell.Info" } }, - "required": [ - "sessionID", - "shell" - ], + "required": ["sessionID", "shell"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.shell.ended": { @@ -14247,9 +14794,7 @@ }, "type": { "type": "string", - "enum": [ - "session.shell.ended" - ] + "enum": ["session.shell.ended"] }, "durable": { "type": "object", @@ -14267,16 +14812,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14294,7 +14833,7 @@ ] }, "shell": { - "$ref": "#/components/schemas/Shell" + "$ref": "#/components/schemas/Shell.Info" }, "output": { "type": "object", @@ -14322,30 +14861,15 @@ "type": "boolean" } }, - "required": [ - "output", - "cursor", - "size", - "truncated" - ], + "required": ["output", "cursor", "size", "truncated"], "additionalProperties": false } }, - "required": [ - "sessionID", - "shell", - "output" - ], + "required": ["sessionID", "shell", "output"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.step.started": { @@ -14367,9 +14891,7 @@ }, "type": { "type": "string", - "enum": [ - "session.step.started" - ] + "enum": ["session.step.started"] }, "durable": { "type": "object", @@ -14387,16 +14909,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14431,22 +14947,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "assistantMessageID", - "agent", - "model" - ], + "required": ["sessionID", "assistantMessageID", "agent", "model"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.step.ended": { @@ -14468,9 +14973,7 @@ }, "type": { "type": "string", - "enum": [ - "session.step.ended" - ] + "enum": ["session.step.ended"] }, "durable": { "type": "object", @@ -14488,16 +14991,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14524,14 +15021,7 @@ }, "finish": { "type": "string", - "enum": [ - "stop", - "length", - "tool-calls", - "content-filter", - "error", - "unknown" - ] + "enum": ["stop", "length", "tool-calls", "content-filter", "error", "unknown"] }, "cost": { "$ref": "#/components/schemas/Money.USD" @@ -14549,23 +15039,11 @@ } } }, - "required": [ - "sessionID", - "assistantMessageID", - "finish", - "cost", - "tokens" - ], + "required": ["sessionID", "assistantMessageID", "finish", "cost", "tokens"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.step.failed": { @@ -14587,9 +15065,7 @@ }, "type": { "type": "string", - "enum": [ - "session.step.failed" - ] + "enum": ["session.step.failed"] }, "durable": { "type": "object", @@ -14607,16 +15083,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14651,21 +15121,11 @@ "$ref": "#/components/schemas/TokenUsage.Info" } }, - "required": [ - "sessionID", - "assistantMessageID", - "error" - ], + "required": ["sessionID", "assistantMessageID", "error"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.text.started": { @@ -14687,9 +15147,7 @@ }, "type": { "type": "string", - "enum": [ - "session.text.started" - ] + "enum": ["session.text.started"] }, "durable": { "type": "object", @@ -14707,16 +15165,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14750,21 +15202,11 @@ ] } }, - "required": [ - "sessionID", - "assistantMessageID", - "ordinal" - ], + "required": ["sessionID", "assistantMessageID", "ordinal"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.text.ended": { @@ -14786,9 +15228,7 @@ }, "type": { "type": "string", - "enum": [ - "session.text.ended" - ] + "enum": ["session.text.ended"] }, "durable": { "type": "object", @@ -14806,16 +15246,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14852,22 +15286,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "assistantMessageID", - "ordinal", - "text" - ], + "required": ["sessionID", "assistantMessageID", "ordinal", "text"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "Session.Message.ProviderState3": { @@ -14892,9 +15315,7 @@ }, "type": { "type": "string", - "enum": [ - "session.reasoning.started" - ] + "enum": ["session.reasoning.started"] }, "durable": { "type": "object", @@ -14912,16 +15333,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -14958,21 +15373,11 @@ "$ref": "#/components/schemas/Session.Message.ProviderState3" } }, - "required": [ - "sessionID", - "assistantMessageID", - "ordinal" - ], + "required": ["sessionID", "assistantMessageID", "ordinal"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "Session.Message.ProviderState4": { @@ -14997,9 +15402,7 @@ }, "type": { "type": "string", - "enum": [ - "session.reasoning.ended" - ] + "enum": ["session.reasoning.ended"] }, "durable": { "type": "object", @@ -15017,16 +15420,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15066,22 +15463,11 @@ "$ref": "#/components/schemas/Session.Message.ProviderState4" } }, - "required": [ - "sessionID", - "assistantMessageID", - "ordinal", - "text" - ], + "required": ["sessionID", "assistantMessageID", "ordinal", "text"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.tool.input.started": { @@ -15103,9 +15489,7 @@ }, "type": { "type": "string", - "enum": [ - "session.tool.input.started" - ] + "enum": ["session.tool.input.started"] }, "durable": { "type": "object", @@ -15123,16 +15507,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15164,22 +15542,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "assistantMessageID", - "callID", - "name" - ], + "required": ["sessionID", "assistantMessageID", "callID", "name"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.tool.input.ended": { @@ -15201,9 +15568,7 @@ }, "type": { "type": "string", - "enum": [ - "session.tool.input.ended" - ] + "enum": ["session.tool.input.ended"] }, "durable": { "type": "object", @@ -15221,16 +15586,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15262,22 +15621,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "assistantMessageID", - "callID", - "text" - ], + "required": ["sessionID", "assistantMessageID", "callID", "text"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "Session.Message.ProviderState5": { @@ -15302,9 +15650,7 @@ }, "type": { "type": "string", - "enum": [ - "session.tool.called" - ] + "enum": ["session.tool.called"] }, "durable": { "type": "object", @@ -15322,16 +15668,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15369,23 +15709,11 @@ "$ref": "#/components/schemas/Session.Message.ProviderState5" } }, - "required": [ - "sessionID", - "assistantMessageID", - "callID", - "input", - "executed" - ], + "required": ["sessionID", "assistantMessageID", "callID", "input", "executed"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.tool.progress": { @@ -15407,9 +15735,7 @@ }, "type": { "type": "string", - "enum": [ - "session.tool.progress" - ] + "enum": ["session.tool.progress"] }, "durable": { "type": "object", @@ -15427,16 +15753,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15474,23 +15794,11 @@ } } }, - "required": [ - "sessionID", - "assistantMessageID", - "callID", - "structured", - "content" - ], + "required": ["sessionID", "assistantMessageID", "callID", "structured", "content"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "Session.Message.ProviderState6": { @@ -15515,9 +15823,7 @@ }, "type": { "type": "string", - "enum": [ - "session.tool.success" - ] + "enum": ["session.tool.success"] }, "durable": { "type": "object", @@ -15535,16 +15841,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15589,24 +15889,11 @@ "$ref": "#/components/schemas/Session.Message.ProviderState6" } }, - "required": [ - "sessionID", - "assistantMessageID", - "callID", - "structured", - "content", - "executed" - ], + "required": ["sessionID", "assistantMessageID", "callID", "structured", "content", "executed"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "Session.Message.ProviderState7": { @@ -15631,9 +15918,7 @@ }, "type": { "type": "string", - "enum": [ - "session.tool.failed" - ] + "enum": ["session.tool.failed"] }, "durable": { "type": "object", @@ -15651,16 +15936,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15699,23 +15978,11 @@ "$ref": "#/components/schemas/Session.Message.ProviderState7" } }, - "required": [ - "sessionID", - "assistantMessageID", - "callID", - "error", - "executed" - ], + "required": ["sessionID", "assistantMessageID", "callID", "error", "executed"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.retry.scheduled": { @@ -15737,9 +16004,7 @@ }, "type": { "type": "string", - "enum": [ - "session.retry.scheduled" - ] + "enum": ["session.retry.scheduled"] }, "durable": { "type": "object", @@ -15757,16 +16022,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15811,23 +16070,11 @@ "$ref": "#/components/schemas/Session.StructuredError" } }, - "required": [ - "sessionID", - "assistantMessageID", - "attempt", - "at", - "error" - ], + "required": ["sessionID", "assistantMessageID", "attempt", "at", "error"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.compaction.admitted": { @@ -15849,9 +16096,7 @@ }, "type": { "type": "string", - "enum": [ - "session.compaction.admitted" - ] + "enum": ["session.compaction.admitted"] }, "durable": { "type": "object", @@ -15869,16 +16114,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15904,20 +16143,11 @@ ] } }, - "required": [ - "sessionID", - "inputID" - ], + "required": ["sessionID", "inputID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.compaction.started": { @@ -15939,9 +16169,7 @@ }, "type": { "type": "string", - "enum": [ - "session.compaction.started" - ] + "enum": ["session.compaction.started"] }, "durable": { "type": "object", @@ -15959,16 +16187,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -15987,10 +16209,7 @@ }, "reason": { "type": "string", - "enum": [ - "auto", - "manual" - ] + "enum": ["auto", "manual"] }, "recent": { "type": "string" @@ -16004,21 +16223,11 @@ ] } }, - "required": [ - "sessionID", - "reason", - "recent" - ], + "required": ["sessionID", "reason", "recent"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.compaction.ended": { @@ -16040,9 +16249,7 @@ }, "type": { "type": "string", - "enum": [ - "session.compaction.ended" - ] + "enum": ["session.compaction.ended"] }, "durable": { "type": "object", @@ -16060,16 +16267,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -16088,10 +16289,7 @@ }, "reason": { "type": "string", - "enum": [ - "auto", - "manual" - ] + "enum": ["auto", "manual"] }, "text": { "type": "string" @@ -16100,22 +16298,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "reason", - "text", - "recent" - ], + "required": ["sessionID", "reason", "text", "recent"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.compaction.failed": { @@ -16137,9 +16324,7 @@ }, "type": { "type": "string", - "enum": [ - "session.compaction.failed" - ] + "enum": ["session.compaction.failed"] }, "durable": { "type": "object", @@ -16157,16 +16342,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -16185,10 +16364,7 @@ }, "reason": { "type": "string", - "enum": [ - "auto", - "manual" - ] + "enum": ["auto", "manual"] }, "error": { "$ref": "#/components/schemas/Session.StructuredError" @@ -16202,21 +16378,11 @@ ] } }, - "required": [ - "sessionID", - "reason", - "error" - ], + "required": ["sessionID", "reason", "error"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.revert.staged": { @@ -16238,9 +16404,7 @@ }, "type": { "type": "string", - "enum": [ - "session.revert.staged" - ] + "enum": ["session.revert.staged"] }, "durable": { "type": "object", @@ -16258,16 +16422,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -16288,20 +16446,11 @@ "$ref": "#/components/schemas/Session.Revert" } }, - "required": [ - "sessionID", - "revert" - ], + "required": ["sessionID", "revert"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.revert.cleared": { @@ -16323,9 +16472,7 @@ }, "type": { "type": "string", - "enum": [ - "session.revert.cleared" - ] + "enum": ["session.revert.cleared"] }, "durable": { "type": "object", @@ -16343,16 +16490,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -16370,19 +16511,11 @@ ] } }, - "required": [ - "sessionID" - ], + "required": ["sessionID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.revert.committed": { @@ -16404,9 +16537,7 @@ }, "type": { "type": "string", - "enum": [ - "session.revert.committed" - ] + "enum": ["session.revert.committed"] }, "durable": { "type": "object", @@ -16424,16 +16555,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -16459,20 +16584,11 @@ ] } }, - "required": [ - "sessionID", - "to" - ], + "required": ["sessionID", "to"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "Session.Event.Durable": { @@ -16496,10 +16612,10 @@ "$ref": "#/components/schemas/session.forked" }, { - "$ref": "#/components/schemas/session.prompt.promoted" + "$ref": "#/components/schemas/session.input.promoted" }, { - "$ref": "#/components/schemas/session.prompt.admitted" + "$ref": "#/components/schemas/session.input.admitted" }, { "$ref": "#/components/schemas/session.execution.started" @@ -16598,9 +16714,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "log.synced" - ] + "enum": ["log.synced"] }, "aggregateID": { "type": "string" @@ -16614,10 +16728,7 @@ ] } }, - "required": [ - "type", - "aggregateID" - ], + "required": ["type", "aggregateID"], "additionalProperties": false, "description": "Marker emitted once when a log read reaches its captured watermark. The reader holds every event committed at or below seq." }, @@ -16674,10 +16785,7 @@ "additionalProperties": false } }, - "required": [ - "data", - "cursor" - ], + "required": ["data", "cursor"], "additionalProperties": false }, "Model.Capabilities": { @@ -16699,11 +16807,7 @@ } } }, - "required": [ - "tools", - "input", - "output" - ], + "required": ["tools", "input", "output"], "additionalProperties": false }, "Model.Variant": { @@ -16725,9 +16829,7 @@ "type": "object" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false }, "Money.USDPerMillionTokens": { @@ -16741,18 +16843,13 @@ "properties": { "type": { "type": "string", - "enum": [ - "context" - ] + "enum": ["context"] }, "size": { "type": "integer" } }, - "required": [ - "type", - "size" - ], + "required": ["type", "size"], "additionalProperties": false }, "input": { @@ -16771,18 +16868,11 @@ "$ref": "#/components/schemas/Money.USDPerMillionTokens" } }, - "required": [ - "read", - "write" - ], + "required": ["read", "write"], "additionalProperties": false } }, - "required": [ - "input", - "output", - "cache" - ], + "required": ["input", "output", "cache"], "additionalProperties": false }, "Model.Info": { @@ -16834,9 +16924,7 @@ "type": "number" } }, - "required": [ - "released" - ], + "required": ["released"], "additionalProperties": false }, "cost": { @@ -16847,12 +16935,7 @@ }, "status": { "type": "string", - "enum": [ - "alpha", - "beta", - "deprecated", - "active" - ] + "enum": ["alpha", "beta", "deprecated", "active"] }, "enabled": { "type": "boolean" @@ -16870,10 +16953,7 @@ "type": "integer" } }, - "required": [ - "context", - "output" - ], + "required": ["context", "output"], "additionalProperties": false } }, @@ -16902,15 +16982,11 @@ "type": "string" } }, - "required": [ - "text" - ], + "required": ["text"], "additionalProperties": false } }, - "required": [ - "data" - ], + "required": ["data"], "additionalProperties": false }, "ProviderV2.Info": { @@ -16944,11 +17020,7 @@ "type": "object" } }, - "required": [ - "id", - "name", - "package" - ], + "required": ["id", "name", "package"], "additionalProperties": false }, "ProviderNotFoundError": { @@ -16956,9 +17028,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "ProviderNotFoundError" - ] + "enum": ["ProviderNotFoundError"] }, "providerID": { "type": "string" @@ -16967,11 +17037,7 @@ "type": "string" } }, - "required": [ - "_tag", - "providerID", - "message" - ], + "required": ["_tag", "providerID", "message"], "additionalProperties": false }, "Integration.When": { @@ -16982,20 +17048,13 @@ }, "op": { "type": "string", - "enum": [ - "eq", - "neq" - ] + "enum": ["eq", "neq"] }, "value": { "type": "string" } }, - "required": [ - "key", - "op", - "value" - ], + "required": ["key", "op", "value"], "additionalProperties": false }, "Integration.TextPrompt": { @@ -17003,9 +17062,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "text" - ] + "enum": ["text"] }, "key": { "type": "string" @@ -17020,11 +17077,7 @@ "$ref": "#/components/schemas/Integration.When" } }, - "required": [ - "type", - "key", - "message" - ], + "required": ["type", "key", "message"], "additionalProperties": false }, "Integration.SelectPrompt": { @@ -17032,9 +17085,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "select" - ] + "enum": ["select"] }, "key": { "type": "string" @@ -17057,10 +17108,7 @@ "type": "string" } }, - "required": [ - "label", - "value" - ], + "required": ["label", "value"], "additionalProperties": false } }, @@ -17068,12 +17116,7 @@ "$ref": "#/components/schemas/Integration.When" } }, - "required": [ - "type", - "key", - "message", - "options" - ], + "required": ["type", "key", "message", "options"], "additionalProperties": false }, "Integration.OAuthMethod": { @@ -17084,9 +17127,7 @@ }, "type": { "type": "string", - "enum": [ - "oauth" - ] + "enum": ["oauth"] }, "label": { "type": "string" @@ -17105,11 +17146,7 @@ } } }, - "required": [ - "id", - "type", - "label" - ], + "required": ["id", "type", "label"], "additionalProperties": false }, "Integration.KeyMethod": { @@ -17117,17 +17154,13 @@ "properties": { "type": { "type": "string", - "enum": [ - "key" - ] + "enum": ["key"] }, "label": { "type": "string" } }, - "required": [ - "type" - ], + "required": ["type"], "additionalProperties": false }, "Integration.EnvMethod": { @@ -17135,9 +17168,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "env" - ] + "enum": ["env"] }, "names": { "type": "array", @@ -17146,10 +17177,7 @@ } } }, - "required": [ - "type", - "names" - ], + "required": ["type", "names"], "additionalProperties": false }, "Integration.Method": { @@ -17170,9 +17198,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "credential" - ] + "enum": ["credential"] }, "id": { "type": "string" @@ -17181,11 +17207,7 @@ "type": "string" } }, - "required": [ - "type", - "id", - "label" - ], + "required": ["type", "id", "label"], "additionalProperties": false }, "Connection.EnvInfo": { @@ -17193,18 +17215,13 @@ "properties": { "type": { "type": "string", - "enum": [ - "env" - ] + "enum": ["env"] }, "name": { "type": "string" } }, - "required": [ - "type", - "name" - ], + "required": ["type", "name"], "additionalProperties": false }, "Connection.Info": { @@ -17239,12 +17256,7 @@ } } }, - "required": [ - "id", - "name", - "methods", - "connections" - ], + "required": ["id", "name", "methods", "connections"], "additionalProperties": false }, "Integration.Attempt": { @@ -17261,10 +17273,7 @@ }, "mode": { "type": "string", - "enum": [ - "auto", - "code" - ] + "enum": ["auto", "code"] }, "time": { "type": "object", @@ -17278,31 +17287,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -17315,49 +17314,30 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] } }, - "required": [ - "created", - "expires" - ], + "required": ["created", "expires"], "additionalProperties": false } }, - "required": [ - "attemptID", - "url", - "instructions", - "mode", - "time" - ], + "required": ["attemptID", "url", "instructions", "mode", "time"], "additionalProperties": false }, "Integration.AttemptStatus": { @@ -17367,9 +17347,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "pending" - ] + "enum": ["pending"] }, "time": { "type": "object", @@ -17383,31 +17361,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -17420,46 +17388,30 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] } }, - "required": [ - "created", - "expires" - ], + "required": ["created", "expires"], "additionalProperties": false } }, - "required": [ - "status", - "time" - ], + "required": ["status", "time"], "additionalProperties": false }, { @@ -17467,9 +17419,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "complete" - ] + "enum": ["complete"] }, "time": { "type": "object", @@ -17483,31 +17433,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -17520,46 +17460,30 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] } }, - "required": [ - "created", - "expires" - ], + "required": ["created", "expires"], "additionalProperties": false } }, - "required": [ - "status", - "time" - ], + "required": ["status", "time"], "additionalProperties": false }, { @@ -17567,9 +17491,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "failed" - ] + "enum": ["failed"] }, "message": { "type": "string" @@ -17586,31 +17508,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -17623,47 +17535,30 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] } }, - "required": [ - "created", - "expires" - ], + "required": ["created", "expires"], "additionalProperties": false } }, - "required": [ - "status", - "message", - "time" - ], + "required": ["status", "message", "time"], "additionalProperties": false }, { @@ -17671,9 +17566,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "expired" - ] + "enum": ["expired"] }, "time": { "type": "object", @@ -17687,31 +17580,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -17724,46 +17607,30 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] } }, - "required": [ - "created", - "expires" - ], + "required": ["created", "expires"], "additionalProperties": false } }, - "required": [ - "status", - "time" - ], + "required": ["status", "time"], "additionalProperties": false } ] @@ -17773,14 +17640,10 @@ "properties": { "status": { "type": "string", - "enum": [ - "connected" - ] + "enum": ["connected"] } }, - "required": [ - "status" - ], + "required": ["status"], "additionalProperties": false }, "Mcp.Status.Pending": { @@ -17788,14 +17651,10 @@ "properties": { "status": { "type": "string", - "enum": [ - "pending" - ] + "enum": ["pending"] } }, - "required": [ - "status" - ], + "required": ["status"], "additionalProperties": false }, "Mcp.Status.Disabled": { @@ -17803,14 +17662,10 @@ "properties": { "status": { "type": "string", - "enum": [ - "disabled" - ] + "enum": ["disabled"] } }, - "required": [ - "status" - ], + "required": ["status"], "additionalProperties": false }, "Mcp.Status.Failed": { @@ -17818,18 +17673,13 @@ "properties": { "status": { "type": "string", - "enum": [ - "failed" - ] + "enum": ["failed"] }, "error": { "type": "string" } }, - "required": [ - "status", - "error" - ], + "required": ["status", "error"], "additionalProperties": false }, "Mcp.Status.NeedsAuth": { @@ -17837,14 +17687,10 @@ "properties": { "status": { "type": "string", - "enum": [ - "needs_auth" - ] + "enum": ["needs_auth"] } }, - "required": [ - "status" - ], + "required": ["status"], "additionalProperties": false }, "Mcp.Status.NeedsClientRegistration": { @@ -17852,18 +17698,13 @@ "properties": { "status": { "type": "string", - "enum": [ - "needs_client_registration" - ] + "enum": ["needs_client_registration"] }, "error": { "type": "string" } }, - "required": [ - "status", - "error" - ], + "required": ["status", "error"], "additionalProperties": false }, "Mcp.Server": { @@ -17898,10 +17739,7 @@ "type": "string" } }, - "required": [ - "name", - "status" - ], + "required": ["name", "status"], "additionalProperties": false }, "Mcp.Resource": { @@ -17923,11 +17761,7 @@ "type": "string" } }, - "required": [ - "server", - "name", - "uri" - ], + "required": ["server", "name", "uri"], "additionalProperties": false }, "Mcp.ResourceTemplate": { @@ -17949,11 +17783,7 @@ "type": "string" } }, - "required": [ - "server", - "name", - "uriTemplate" - ], + "required": ["server", "name", "uriTemplate"], "additionalProperties": false }, "Mcp.ResourceCatalog": { @@ -17972,18 +17802,12 @@ } } }, - "required": [ - "resources", - "templates" - ], + "required": ["resources", "templates"], "additionalProperties": false }, "Project.Vcs": { "type": "string", - "enum": [ - "git", - "hg" - ] + "enum": ["git", "hg"] }, "Project.Icon": { "type": "object", @@ -18038,10 +17862,7 @@ ] } }, - "required": [ - "created", - "updated" - ], + "required": ["created", "updated"], "additionalProperties": false }, "Project": { @@ -18075,12 +17896,7 @@ } } }, - "required": [ - "id", - "worktree", - "time", - "sandboxes" - ], + "required": ["id", "worktree", "time", "sandboxes"], "additionalProperties": false }, "Project.Current": { @@ -18093,10 +17909,7 @@ "type": "string" } }, - "required": [ - "id", - "directory" - ], + "required": ["id", "directory"], "additionalProperties": false }, "Project.Directory": { @@ -18109,9 +17922,7 @@ "type": "string" } }, - "required": [ - "directory" - ], + "required": ["directory"], "additionalProperties": false }, "Project.Directories": { @@ -18131,10 +17942,7 @@ }, "op": { "type": "string", - "enum": [ - "eq", - "neq" - ] + "enum": ["eq", "neq"] }, "value": { "anyOf": [ @@ -18150,31 +17958,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -18184,11 +17982,7 @@ ] } }, - "required": [ - "key", - "op", - "value" - ], + "required": ["key", "op", "value"], "additionalProperties": false }, "Form.Option": { @@ -18204,10 +17998,7 @@ "type": "string" } }, - "required": [ - "value", - "label" - ], + "required": ["value", "label"], "additionalProperties": false }, "Form.StringField": { @@ -18233,18 +18024,11 @@ }, "type": { "type": "string", - "enum": [ - "string" - ] + "enum": ["string"] }, "format": { "type": "string", - "enum": [ - "email", - "uri", - "date", - "date-time" - ] + "enum": ["email", "uri", "date", "date-time"] }, "minLength": { "type": "integer", @@ -18281,10 +18065,7 @@ "type": "boolean" } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.NumberField": { @@ -18310,9 +18091,7 @@ }, "type": { "type": "string", - "enum": [ - "number" - ] + "enum": ["number"] }, "minimum": { "anyOf": [ @@ -18323,31 +18102,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -18360,31 +18129,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -18397,39 +18156,26 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.IntegerField": { @@ -18455,9 +18201,7 @@ }, "type": { "type": "string", - "enum": [ - "integer" - ] + "enum": ["integer"] }, "minimum": { "anyOf": [ @@ -18468,31 +18212,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -18505,31 +18239,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -18542,39 +18266,26 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.BooleanField": { @@ -18600,18 +18311,13 @@ }, "type": { "type": "string", - "enum": [ - "boolean" - ] + "enum": ["boolean"] }, "default": { "type": "boolean" } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.MultiselectField": { @@ -18637,9 +18343,7 @@ }, "type": { "type": "string", - "enum": [ - "multiselect" - ] + "enum": ["multiselect"] }, "options": { "type": "array", @@ -18673,72 +18377,67 @@ } } }, - "required": [ - "key", - "type", - "options" - ], + "required": ["key", "type", "options"], "additionalProperties": false }, - "Form.FormInfo": { + "Form.ExternalField": { "type": "object", "properties": { - "id": { + "key": { + "type": "string" + }, + "type": { "type": "string", - "allOf": [ - { - "pattern": "^frm_" - } - ] + "enum": ["external"] }, - "sessionID": { + "url": { "type": "string" }, "title": { "type": "string" }, - "metadata": { - "$ref": "#/components/schemas/Form.Metadata" + "description": { + "type": "string" + } + }, + "required": ["key", "type", "url"], + "additionalProperties": false + }, + "Form.Field": { + "anyOf": [ + { + "$ref": "#/components/schemas/Form.StringField" }, - "mode": { - "type": "string", - "enum": [ - "form" - ] + { + "$ref": "#/components/schemas/Form.NumberField" }, - "fields": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.StringField" - }, - { - "$ref": "#/components/schemas/Form.NumberField" - }, - { - "$ref": "#/components/schemas/Form.IntegerField" - }, - { - "$ref": "#/components/schemas/Form.BooleanField" - }, - { - "$ref": "#/components/schemas/Form.MultiselectField" - } - ] - } + { + "$ref": "#/components/schemas/Form.IntegerField" + }, + { + "$ref": "#/components/schemas/Form.BooleanField" + }, + { + "$ref": "#/components/schemas/Form.MultiselectField" + }, + { + "$ref": "#/components/schemas/Form.ExternalField" + } + ] + }, + "Form.Fields": { + "type": "array", + "prefixItems": [ + { + "$ref": "#/components/schemas/Form.Field" } - }, - "required": [ - "id", - "sessionID", - "title", - "mode", - "fields" ], - "additionalProperties": false + "minItems": 1, + "items": { + "$ref": "#/components/schemas/Form.Field" + } }, - "Form.UrlInfo": { + "Form.Info": { "type": "object", "properties": { "id": { @@ -18758,23 +18457,11 @@ "metadata": { "$ref": "#/components/schemas/Form.Metadata" }, - "mode": { - "type": "string", - "enum": [ - "url" - ] - }, - "url": { - "type": "string" + "fields": { + "$ref": "#/components/schemas/Form.Fields" } }, - "required": [ - "id", - "sessionID", - "title", - "mode", - "url" - ], + "required": ["id", "sessionID", "title", "fields"], "additionalProperties": false }, "Form.CreatePayload": { @@ -18801,57 +18488,11 @@ "metadata": { "$ref": "#/components/schemas/Form.Metadata" }, - "mode": { - "type": "string", - "enum": [ - "form", - "url" - ] - }, "fields": { - "anyOf": [ - { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.StringField" - }, - { - "$ref": "#/components/schemas/Form.NumberField" - }, - { - "$ref": "#/components/schemas/Form.IntegerField" - }, - { - "$ref": "#/components/schemas/Form.BooleanField" - }, - { - "$ref": "#/components/schemas/Form.MultiselectField" - } - ] - } - }, - { - "type": "null" - } - ] - }, - "url": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "null" - } - ] + "$ref": "#/components/schemas/Form.Fields" } }, - "required": [ - "title", - "mode" - ], + "required": ["title", "fields"], "additionalProperties": false }, "FormNotFoundError": { @@ -18859,9 +18500,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "FormNotFoundError" - ] + "enum": ["FormNotFoundError"] }, "id": { "type": "string" @@ -18870,11 +18509,7 @@ "type": "string" } }, - "required": [ - "_tag", - "id", - "message" - ], + "required": ["_tag", "id", "message"], "additionalProperties": false }, "Form.Value": { @@ -18891,31 +18526,21 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, { "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] + "enum": ["Infinity", "-Infinity", "NaN"] } ] }, @@ -18943,14 +18568,10 @@ "properties": { "status": { "type": "string", - "enum": [ - "pending" - ] + "enum": ["pending"] } }, - "required": [ - "status" - ], + "required": ["status"], "additionalProperties": false }, { @@ -18958,18 +18579,13 @@ "properties": { "status": { "type": "string", - "enum": [ - "answered" - ] + "enum": ["answered"] }, "answer": { "$ref": "#/components/schemas/Form.Answer" } }, - "required": [ - "status", - "answer" - ], + "required": ["status", "answer"], "additionalProperties": false }, { @@ -18977,14 +18593,10 @@ "properties": { "status": { "type": "string", - "enum": [ - "cancelled" - ] + "enum": ["cancelled"] } }, - "required": [ - "status" - ], + "required": ["status"], "additionalProperties": false } ] @@ -18996,9 +18608,7 @@ "$ref": "#/components/schemas/Form.Answer" } }, - "required": [ - "answer" - ], + "required": ["answer"], "additionalProperties": false }, "FormAlreadySettledError": { @@ -19006,9 +18616,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "FormAlreadySettledError" - ] + "enum": ["FormAlreadySettledError"] }, "id": { "type": "string" @@ -19017,11 +18625,7 @@ "type": "string" } }, - "required": [ - "_tag", - "id", - "message" - ], + "required": ["_tag", "id", "message"], "additionalProperties": false }, "FormInvalidAnswerError": { @@ -19029,9 +18633,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "FormInvalidAnswerError" - ] + "enum": ["FormInvalidAnswerError"] }, "id": { "type": "string" @@ -19040,11 +18642,7 @@ "type": "string" } }, - "required": [ - "_tag", - "id", - "message" - ], + "required": ["_tag", "id", "message"], "additionalProperties": false }, "PermissionV2.Source": { @@ -19054,9 +18652,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "tool" - ] + "enum": ["tool"] }, "messageID": { "type": "string" @@ -19065,11 +18661,7 @@ "type": "string" } }, - "required": [ - "type", - "messageID", - "callID" - ], + "required": ["type", "messageID", "callID"], "additionalProperties": false } ] @@ -19115,12 +18707,7 @@ "$ref": "#/components/schemas/PermissionV2.Source" } }, - "required": [ - "id", - "sessionID", - "action", - "resources" - ], + "required": ["id", "sessionID", "action", "resources"], "additionalProperties": false }, "PermissionSaved.Info": { @@ -19139,12 +18726,7 @@ "type": "string" } }, - "required": [ - "id", - "projectID", - "action", - "resource" - ], + "required": ["id", "projectID", "action", "resource"], "additionalProperties": false }, "PermissionNotFoundError": { @@ -19152,9 +18734,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "PermissionNotFoundError" - ] + "enum": ["PermissionNotFoundError"] }, "requestID": { "type": "string" @@ -19163,20 +18743,12 @@ "type": "string" } }, - "required": [ - "_tag", - "requestID", - "message" - ], + "required": ["_tag", "requestID", "message"], "additionalProperties": false }, "PermissionV2.Reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] }, "FileSystem.Entry": { "type": "object", @@ -19186,16 +18758,10 @@ }, "type": { "type": "string", - "enum": [ - "file", - "directory" - ] + "enum": ["file", "directory"] } }, - "required": [ - "path", - "type" - ], + "required": ["path", "type"], "additionalProperties": false }, "Command.Info": { @@ -19220,10 +18786,7 @@ "type": "boolean" } }, - "required": [ - "name", - "template" - ], + "required": ["name", "template"], "additionalProperties": false }, "Skill.Info": { @@ -19251,12 +18814,7 @@ "type": "string" } }, - "required": [ - "id", - "name", - "location", - "content" - ], + "required": ["id", "name", "location", "content"], "additionalProperties": false }, "models-dev.refreshed": { @@ -19278,9 +18836,7 @@ }, "type": { "type": "string", - "enum": [ - "models-dev.refreshed" - ] + "enum": ["models-dev.refreshed"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -19296,12 +18852,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "integration.updated": { @@ -19323,9 +18874,7 @@ }, "type": { "type": "string", - "enum": [ - "integration.updated" - ] + "enum": ["integration.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -19341,12 +18890,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "integration.connection.updated": { @@ -19368,9 +18912,7 @@ }, "type": { "type": "string", - "enum": [ - "integration.connection.updated" - ] + "enum": ["integration.connection.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -19382,18 +18924,11 @@ "type": "string" } }, - "required": [ - "integrationID" - ], + "required": ["integrationID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "catalog.updated": { @@ -19415,9 +18950,7 @@ }, "type": { "type": "string", - "enum": [ - "catalog.updated" - ] + "enum": ["catalog.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -19433,12 +18966,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "agent.updated": { @@ -19460,9 +18988,7 @@ }, "type": { "type": "string", - "enum": [ - "agent.updated" - ] + "enum": ["agent.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -19478,12 +19004,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "FileDiff.LegacyInfo": { @@ -19503,26 +19024,15 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "additions", - "deletions" - ], + "required": ["additions", "deletions"], "additionalProperties": false }, "PermissionAction": { "type": "string", - "enum": [ - "allow", - "deny", - "ask" - ] + "enum": ["allow", "deny", "ask"] }, "PermissionRule": { "type": "object", @@ -19537,11 +19047,7 @@ "$ref": "#/components/schemas/PermissionAction" } }, - "required": [ - "permission", - "pattern", - "action" - ], + "required": ["permission", "pattern", "action"], "additionalProperties": false }, "PermissionRuleset": { @@ -19608,11 +19114,7 @@ } } }, - "required": [ - "additions", - "deletions", - "files" - ], + "required": ["additions", "deletions", "files"], "additionalProperties": false }, "cost": { @@ -19640,19 +19142,11 @@ "type": "number" } }, - "required": [ - "read", - "write" - ], + "required": ["read", "write"], "additionalProperties": false } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ], + "required": ["input", "output", "reasoning", "cache"], "additionalProperties": false }, "share": { @@ -19662,9 +19156,7 @@ "type": "string" } }, - "required": [ - "url" - ], + "required": ["url"], "additionalProperties": false }, "title": { @@ -19686,10 +19178,7 @@ "type": "string" } }, - "required": [ - "id", - "providerID" - ], + "required": ["id", "providerID"], "additionalProperties": false }, "version": { @@ -19729,10 +19218,7 @@ "type": "number" } }, - "required": [ - "created", - "updated" - ], + "required": ["created", "updated"], "additionalProperties": false }, "permission": { @@ -19764,21 +19250,11 @@ "type": "string" } }, - "required": [ - "messageID" - ], + "required": ["messageID"], "additionalProperties": false } }, - "required": [ - "id", - "slug", - "projectID", - "directory", - "title", - "version", - "time" - ], + "required": ["id", "slug", "projectID", "directory", "title", "version", "time"], "additionalProperties": false }, "session.created": { @@ -19800,9 +19276,7 @@ }, "type": { "type": "string", - "enum": [ - "session.created" - ] + "enum": ["session.created"] }, "durable": { "type": "object", @@ -19820,16 +19294,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -19850,20 +19318,11 @@ "$ref": "#/components/schemas/SessionV1.Info" } }, - "required": [ - "sessionID", - "info" - ], + "required": ["sessionID", "info"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.updated": { @@ -19885,9 +19344,7 @@ }, "type": { "type": "string", - "enum": [ - "session.updated" - ] + "enum": ["session.updated"] }, "durable": { "type": "object", @@ -19905,16 +19362,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -19935,20 +19386,11 @@ "$ref": "#/components/schemas/SessionV1.Info" } }, - "required": [ - "sessionID", - "info" - ], + "required": ["sessionID", "info"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.deleted1": { @@ -19970,9 +19412,7 @@ }, "type": { "type": "string", - "enum": [ - "session.deleted" - ] + "enum": ["session.deleted"] }, "durable": { "type": "object", @@ -19990,16 +19430,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -20020,20 +19454,11 @@ "$ref": "#/components/schemas/SessionV1.Info" } }, - "required": [ - "sessionID", - "info" - ], + "required": ["sessionID", "info"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "JSONSchema": { @@ -20046,14 +19471,10 @@ "properties": { "type": { "type": "string", - "enum": [ - "text" - ] + "enum": ["text"] } }, - "required": [ - "type" - ], + "required": ["type"], "additionalProperties": false }, { @@ -20061,9 +19482,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "json_schema" - ] + "enum": ["json_schema"] }, "schema": { "$ref": "#/components/schemas/JSONSchema" @@ -20091,10 +19510,7 @@ ] } }, - "required": [ - "type", - "schema" - ], + "required": ["type", "schema"], "additionalProperties": false } ] @@ -20120,9 +19536,7 @@ }, "role": { "type": "string", - "enum": [ - "user" - ] + "enum": ["user"] }, "time": { "type": "object", @@ -20136,9 +19550,7 @@ ] } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "format": { @@ -20183,9 +19595,7 @@ } } }, - "required": [ - "diffs" - ], + "required": ["diffs"], "additionalProperties": false }, { @@ -20216,10 +19626,7 @@ ] } }, - "required": [ - "providerID", - "modelID" - ], + "required": ["providerID", "modelID"], "additionalProperties": false }, "system": { @@ -20246,14 +19653,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "role", - "time", - "agent", - "model" - ], + "required": ["id", "sessionID", "role", "time", "agent", "model"], "additionalProperties": false }, "ProviderAuthError": { @@ -20261,9 +19661,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "ProviderAuthError" - ] + "enum": ["ProviderAuthError"] }, "data": { "type": "object", @@ -20275,17 +19673,11 @@ "type": "string" } }, - "required": [ - "providerID", - "message" - ], + "required": ["providerID", "message"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "UnknownError1": { @@ -20293,9 +19685,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "UnknownError" - ] + "enum": ["UnknownError"] }, "data": { "type": "object", @@ -20314,16 +19704,11 @@ ] } }, - "required": [ - "message" - ], + "required": ["message"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "MessageOutputLengthError": { @@ -20331,9 +19716,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "MessageOutputLengthError" - ] + "enum": ["MessageOutputLengthError"] }, "data": { "anyOf": [ @@ -20346,10 +19729,7 @@ ] } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "MessageAbortedError": { @@ -20357,9 +19737,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "MessageAbortedError" - ] + "enum": ["MessageAbortedError"] }, "data": { "type": "object", @@ -20368,16 +19746,11 @@ "type": "string" } }, - "required": [ - "message" - ], + "required": ["message"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "StructuredOutputError": { @@ -20385,9 +19758,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "StructuredOutputError" - ] + "enum": ["StructuredOutputError"] }, "data": { "type": "object", @@ -20404,17 +19775,11 @@ ] } }, - "required": [ - "message", - "retries" - ], + "required": ["message", "retries"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "ContextOverflowError": { @@ -20422,9 +19787,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "ContextOverflowError" - ] + "enum": ["ContextOverflowError"] }, "data": { "type": "object", @@ -20443,16 +19806,11 @@ ] } }, - "required": [ - "message" - ], + "required": ["message"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "ContentFilterError": { @@ -20460,9 +19818,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "ContentFilterError" - ] + "enum": ["ContentFilterError"] }, "data": { "type": "object", @@ -20471,16 +19827,11 @@ "type": "string" } }, - "required": [ - "message" - ], + "required": ["message"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "APIError": { @@ -20488,9 +19839,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "APIError" - ] + "enum": ["APIError"] }, "data": { "type": "object", @@ -20553,17 +19902,11 @@ ] } }, - "required": [ - "message", - "isRetryable" - ], + "required": ["message", "isRetryable"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "AssistantMessage": { @@ -20587,9 +19930,7 @@ }, "role": { "type": "string", - "enum": [ - "assistant" - ] + "enum": ["assistant"] }, "time": { "type": "object", @@ -20618,9 +19959,7 @@ ] } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false }, "error": { @@ -20688,10 +20027,7 @@ "type": "string" } }, - "required": [ - "cwd", - "root" - ], + "required": ["cwd", "root"], "additionalProperties": false }, "summary": { @@ -20739,19 +20075,11 @@ "type": "number" } }, - "required": [ - "read", - "write" - ], + "required": ["read", "write"], "additionalProperties": false } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ], + "required": ["input", "output", "reasoning", "cache"], "additionalProperties": false }, "structured": { @@ -20828,9 +20156,7 @@ }, "type": { "type": "string", - "enum": [ - "message.updated" - ] + "enum": ["message.updated"] }, "durable": { "type": "object", @@ -20848,16 +20174,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -20878,20 +20198,11 @@ "$ref": "#/components/schemas/Message" } }, - "required": [ - "sessionID", - "info" - ], + "required": ["sessionID", "info"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "message.removed": { @@ -20913,9 +20224,7 @@ }, "type": { "type": "string", - "enum": [ - "message.removed" - ] + "enum": ["message.removed"] }, "durable": { "type": "object", @@ -20933,16 +20242,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -20968,20 +20271,11 @@ ] } }, - "required": [ - "sessionID", - "messageID" - ], + "required": ["sessionID", "messageID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "TextPart": { @@ -21013,9 +20307,7 @@ }, "type": { "type": "string", - "enum": [ - "text" - ] + "enum": ["text"] }, "text": { "type": "string" @@ -21069,9 +20361,7 @@ ] } }, - "required": [ - "start" - ], + "required": ["start"], "additionalProperties": false }, { @@ -21090,13 +20380,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text" - ], + "required": ["id", "sessionID", "messageID", "type", "text"], "additionalProperties": false }, "SubtaskPart": { @@ -21128,9 +20412,7 @@ }, "type": { "type": "string", - "enum": [ - "subtask" - ] + "enum": ["subtask"] }, "prompt": { "type": "string" @@ -21153,10 +20435,7 @@ "type": "string" } }, - "required": [ - "providerID", - "modelID" - ], + "required": ["providerID", "modelID"], "additionalProperties": false }, { @@ -21175,15 +20454,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "prompt", - "description", - "agent" - ], + "required": ["id", "sessionID", "messageID", "type", "prompt", "description", "agent"], "additionalProperties": false }, "ReasoningPart": { @@ -21215,9 +20486,7 @@ }, "type": { "type": "string", - "enum": [ - "reasoning" - ] + "enum": ["reasoning"] }, "text": { "type": "string" @@ -21259,20 +20528,11 @@ ] } }, - "required": [ - "start" - ], + "required": ["start"], "additionalProperties": false } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "text", - "time" - ], + "required": ["id", "sessionID", "messageID", "type", "text", "time"], "additionalProperties": false }, "FilePartSourceText": { @@ -21288,11 +20548,7 @@ "type": "number" } }, - "required": [ - "value", - "start", - "end" - ], + "required": ["value", "start", "end"], "additionalProperties": false }, "FileSource": { @@ -21303,19 +20559,13 @@ }, "type": { "type": "string", - "enum": [ - "file" - ] + "enum": ["file"] }, "path": { "type": "string" } }, - "required": [ - "text", - "type", - "path" - ], + "required": ["text", "type", "path"], "additionalProperties": false }, "Range": { @@ -21341,10 +20591,7 @@ ] } }, - "required": [ - "line", - "character" - ], + "required": ["line", "character"], "additionalProperties": false }, "end": { @@ -21367,17 +20614,11 @@ ] } }, - "required": [ - "line", - "character" - ], + "required": ["line", "character"], "additionalProperties": false } }, - "required": [ - "start", - "end" - ], + "required": ["start", "end"], "additionalProperties": false }, "SymbolSource": { @@ -21388,9 +20629,7 @@ }, "type": { "type": "string", - "enum": [ - "symbol" - ] + "enum": ["symbol"] }, "path": { "type": "string" @@ -21410,14 +20649,7 @@ ] } }, - "required": [ - "text", - "type", - "path", - "range", - "name", - "kind" - ], + "required": ["text", "type", "path", "range", "name", "kind"], "additionalProperties": false }, "ResourceSource": { @@ -21428,9 +20660,7 @@ }, "type": { "type": "string", - "enum": [ - "resource" - ] + "enum": ["resource"] }, "clientName": { "type": "string" @@ -21439,12 +20669,7 @@ "type": "string" } }, - "required": [ - "text", - "type", - "clientName", - "uri" - ], + "required": ["text", "type", "clientName", "uri"], "additionalProperties": false }, "FilePartSource": { @@ -21489,9 +20714,7 @@ }, "type": { "type": "string", - "enum": [ - "file" - ] + "enum": ["file"] }, "mime": { "type": "string" @@ -21520,14 +20743,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "mime", - "url" - ], + "required": ["id", "sessionID", "messageID", "type", "mime", "url"], "additionalProperties": false }, "ToolStatePending": { @@ -21535,9 +20751,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "pending" - ] + "enum": ["pending"] }, "input": { "type": "object" @@ -21546,11 +20760,7 @@ "type": "string" } }, - "required": [ - "status", - "input", - "raw" - ], + "required": ["status", "input", "raw"], "additionalProperties": false }, "ToolStateRunning": { @@ -21558,9 +20768,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "running" - ] + "enum": ["running"] }, "input": { "type": "object" @@ -21597,17 +20805,11 @@ ] } }, - "required": [ - "start" - ], + "required": ["start"], "additionalProperties": false } }, - "required": [ - "status", - "input", - "time" - ], + "required": ["status", "input", "time"], "additionalProperties": false }, "ToolStateCompleted": { @@ -21615,9 +20817,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "completed" - ] + "enum": ["completed"] }, "input": { "type": "object" @@ -21666,10 +20866,7 @@ ] } }, - "required": [ - "start", - "end" - ], + "required": ["start", "end"], "additionalProperties": false }, "attachments": { @@ -21686,14 +20883,7 @@ ] } }, - "required": [ - "status", - "input", - "output", - "title", - "metadata", - "time" - ], + "required": ["status", "input", "output", "title", "metadata", "time"], "additionalProperties": false }, "ToolStateError": { @@ -21701,9 +20891,7 @@ "properties": { "status": { "type": "string", - "enum": [ - "error" - ] + "enum": ["error"] }, "input": { "type": "object" @@ -21741,19 +20929,11 @@ ] } }, - "required": [ - "start", - "end" - ], + "required": ["start", "end"], "additionalProperties": false } }, - "required": [ - "status", - "input", - "error", - "time" - ], + "required": ["status", "input", "error", "time"], "additionalProperties": false }, "ToolState": { @@ -21801,9 +20981,7 @@ }, "type": { "type": "string", - "enum": [ - "tool" - ] + "enum": ["tool"] }, "callID": { "type": "string" @@ -21825,15 +21003,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "callID", - "tool", - "state" - ], + "required": ["id", "sessionID", "messageID", "type", "callID", "tool", "state"], "additionalProperties": false }, "StepStartPart": { @@ -21865,9 +21035,7 @@ }, "type": { "type": "string", - "enum": [ - "step-start" - ] + "enum": ["step-start"] }, "snapshot": { "anyOf": [ @@ -21880,12 +21048,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type" - ], + "required": ["id", "sessionID", "messageID", "type"], "additionalProperties": false }, "StepFinishPart": { @@ -21917,9 +21080,7 @@ }, "type": { "type": "string", - "enum": [ - "step-finish" - ] + "enum": ["step-finish"] }, "reason": { "type": "string" @@ -21969,31 +21130,15 @@ "type": "number" } }, - "required": [ - "read", - "write" - ], + "required": ["read", "write"], "additionalProperties": false } }, - "required": [ - "input", - "output", - "reasoning", - "cache" - ], + "required": ["input", "output", "reasoning", "cache"], "additionalProperties": false } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "reason", - "cost", - "tokens" - ], + "required": ["id", "sessionID", "messageID", "type", "reason", "cost", "tokens"], "additionalProperties": false }, "SnapshotPart": { @@ -22025,21 +21170,13 @@ }, "type": { "type": "string", - "enum": [ - "snapshot" - ] + "enum": ["snapshot"] }, "snapshot": { "type": "string" } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "snapshot" - ], + "required": ["id", "sessionID", "messageID", "type", "snapshot"], "additionalProperties": false }, "PatchPart": { @@ -22071,9 +21208,7 @@ }, "type": { "type": "string", - "enum": [ - "patch" - ] + "enum": ["patch"] }, "hash": { "type": "string" @@ -22085,14 +21220,7 @@ } } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "hash", - "files" - ], + "required": ["id", "sessionID", "messageID", "type", "hash", "files"], "additionalProperties": false }, "AgentPart": { @@ -22124,9 +21252,7 @@ }, "type": { "type": "string", - "enum": [ - "agent" - ] + "enum": ["agent"] }, "name": { "type": "string" @@ -22156,11 +21282,7 @@ ] } }, - "required": [ - "value", - "start", - "end" - ], + "required": ["value", "start", "end"], "additionalProperties": false }, { @@ -22169,13 +21291,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "name" - ], + "required": ["id", "sessionID", "messageID", "type", "name"], "additionalProperties": false }, "RetryPart": { @@ -22207,9 +21323,7 @@ }, "type": { "type": "string", - "enum": [ - "retry" - ] + "enum": ["retry"] }, "attempt": { "type": "integer", @@ -22234,21 +21348,11 @@ ] } }, - "required": [ - "created" - ], + "required": ["created"], "additionalProperties": false } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "attempt", - "error", - "time" - ], + "required": ["id", "sessionID", "messageID", "type", "attempt", "error", "time"], "additionalProperties": false }, "CompactionPart": { @@ -22280,9 +21384,7 @@ }, "type": { "type": "string", - "enum": [ - "compaction" - ] + "enum": ["compaction"] }, "auto": { "type": "boolean" @@ -22313,13 +21415,7 @@ ] } }, - "required": [ - "id", - "sessionID", - "messageID", - "type", - "auto" - ], + "required": ["id", "sessionID", "messageID", "type", "auto"], "additionalProperties": false }, "Part": { @@ -22381,9 +21477,7 @@ }, "type": { "type": "string", - "enum": [ - "message.part.updated" - ] + "enum": ["message.part.updated"] }, "durable": { "type": "object", @@ -22401,16 +21495,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -22434,21 +21522,11 @@ "type": "number" } }, - "required": [ - "sessionID", - "part", - "time" - ], + "required": ["sessionID", "part", "time"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "message.part.removed": { @@ -22470,9 +21548,7 @@ }, "type": { "type": "string", - "enum": [ - "message.part.removed" - ] + "enum": ["message.part.removed"] }, "durable": { "type": "object", @@ -22490,16 +21566,10 @@ }, "version": { "type": "number", - "enum": [ - 1 - ] + "enum": [1] } }, - "required": [ - "aggregateID", - "seq", - "version" - ], + "required": ["aggregateID", "seq", "version"], "additionalProperties": false }, "location": { @@ -22533,21 +21603,11 @@ ] } }, - "required": [ - "sessionID", - "messageID", - "partID" - ], + "required": ["sessionID", "messageID", "partID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "durable", - "data" - ], + "required": ["id", "created", "type", "durable", "data"], "additionalProperties": false }, "session.usage.updated": { @@ -22569,9 +21629,7 @@ }, "type": { "type": "string", - "enum": [ - "session.usage.updated" - ] + "enum": ["session.usage.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -22594,20 +21652,11 @@ "$ref": "#/components/schemas/TokenUsage.Info" } }, - "required": [ - "sessionID", - "cost", - "tokens" - ], + "required": ["sessionID", "cost", "tokens"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "session.text.delta": { @@ -22629,9 +21678,7 @@ }, "type": { "type": "string", - "enum": [ - "session.text.delta" - ] + "enum": ["session.text.delta"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -22667,21 +21714,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "assistantMessageID", - "ordinal", - "delta" - ], + "required": ["sessionID", "assistantMessageID", "ordinal", "delta"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "session.reasoning.delta": { @@ -22703,9 +21740,7 @@ }, "type": { "type": "string", - "enum": [ - "session.reasoning.delta" - ] + "enum": ["session.reasoning.delta"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -22741,21 +21776,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "assistantMessageID", - "ordinal", - "delta" - ], + "required": ["sessionID", "assistantMessageID", "ordinal", "delta"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "session.tool.input.delta": { @@ -22777,9 +21802,7 @@ }, "type": { "type": "string", - "enum": [ - "session.tool.input.delta" - ] + "enum": ["session.tool.input.delta"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -22810,21 +21833,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "assistantMessageID", - "callID", - "delta" - ], + "required": ["sessionID", "assistantMessageID", "callID", "delta"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "session.compaction.delta": { @@ -22846,9 +21859,7 @@ }, "type": { "type": "string", - "enum": [ - "session.compaction.delta" - ] + "enum": ["session.compaction.delta"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -22868,19 +21879,11 @@ "type": "string" } }, - "required": [ - "sessionID", - "text" - ], + "required": ["sessionID", "text"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "filesystem.changed": { @@ -22902,9 +21905,7 @@ }, "type": { "type": "string", - "enum": [ - "filesystem.changed" - ] + "enum": ["filesystem.changed"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -22917,26 +21918,14 @@ }, "event": { "type": "string", - "enum": [ - "add", - "change", - "unlink" - ] + "enum": ["add", "change", "unlink"] } }, - "required": [ - "file", - "event" - ], + "required": ["file", "event"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "reference.updated": { @@ -22958,9 +21947,7 @@ }, "type": { "type": "string", - "enum": [ - "reference.updated" - ] + "enum": ["reference.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -22976,12 +21963,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "permission.v2.asked": { @@ -23003,9 +21985,7 @@ }, "type": { "type": "string", - "enum": [ - "permission.v2.asked" - ] + "enum": ["permission.v2.asked"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23051,21 +22031,11 @@ "$ref": "#/components/schemas/PermissionV2.Source" } }, - "required": [ - "id", - "sessionID", - "action", - "resources" - ], + "required": ["id", "sessionID", "action", "resources"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "permission.v2.replied": { @@ -23087,9 +22057,7 @@ }, "type": { "type": "string", - "enum": [ - "permission.v2.replied" - ] + "enum": ["permission.v2.replied"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23117,20 +22085,11 @@ "$ref": "#/components/schemas/PermissionV2.Reply" } }, - "required": [ - "sessionID", - "requestID", - "reply" - ], + "required": ["sessionID", "requestID", "reply"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "plugin.added": { @@ -23152,9 +22111,7 @@ }, "type": { "type": "string", - "enum": [ - "plugin.added" - ] + "enum": ["plugin.added"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23166,18 +22123,11 @@ "type": "string" } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "plugin.updated": { @@ -23199,9 +22149,7 @@ }, "type": { "type": "string", - "enum": [ - "plugin.updated" - ] + "enum": ["plugin.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23217,12 +22165,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "project.directories.updated": { @@ -23244,9 +22187,7 @@ }, "type": { "type": "string", - "enum": [ - "project.directories.updated" - ] + "enum": ["project.directories.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23258,18 +22199,11 @@ "type": "string" } }, - "required": [ - "projectID" - ], + "required": ["projectID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "command.updated": { @@ -23291,9 +22225,7 @@ }, "type": { "type": "string", - "enum": [ - "command.updated" - ] + "enum": ["command.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23309,12 +22241,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "config.updated": { @@ -23336,9 +22263,7 @@ }, "type": { "type": "string", - "enum": [ - "config.updated" - ] + "enum": ["config.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23354,12 +22279,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "skill.updated": { @@ -23381,9 +22301,7 @@ }, "type": { "type": "string", - "enum": [ - "skill.updated" - ] + "enum": ["skill.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23399,12 +22317,7 @@ ] } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "Pty": { @@ -23435,10 +22348,7 @@ }, "status": { "type": "string", - "enum": [ - "running", - "exited" - ] + "enum": ["running", "exited"] }, "pid": { "type": "integer", @@ -23457,15 +22367,7 @@ ] } }, - "required": [ - "id", - "title", - "command", - "args", - "cwd", - "status", - "pid" - ], + "required": ["id", "title", "command", "args", "cwd", "status", "pid"], "additionalProperties": false }, "pty.created": { @@ -23487,9 +22389,7 @@ }, "type": { "type": "string", - "enum": [ - "pty.created" - ] + "enum": ["pty.created"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23501,18 +22401,11 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ], + "required": ["info"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "pty.updated": { @@ -23534,9 +22427,7 @@ }, "type": { "type": "string", - "enum": [ - "pty.updated" - ] + "enum": ["pty.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23548,18 +22439,11 @@ "$ref": "#/components/schemas/Pty" } }, - "required": [ - "info" - ], + "required": ["info"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "pty.exited": { @@ -23581,9 +22465,7 @@ }, "type": { "type": "string", - "enum": [ - "pty.exited" - ] + "enum": ["pty.exited"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23608,19 +22490,11 @@ ] } }, - "required": [ - "id", - "exitCode" - ], + "required": ["id", "exitCode"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "pty.deleted": { @@ -23642,9 +22516,7 @@ }, "type": { "type": "string", - "enum": [ - "pty.deleted" - ] + "enum": ["pty.deleted"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23661,18 +22533,11 @@ ] } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "shell.created": { @@ -23694,9 +22559,7 @@ }, "type": { "type": "string", - "enum": [ - "shell.created" - ] + "enum": ["shell.created"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23705,21 +22568,14 @@ "type": "object", "properties": { "info": { - "$ref": "#/components/schemas/Shell" + "$ref": "#/components/schemas/Shell.Info" } }, - "required": [ - "info" - ], + "required": ["info"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "shell.exited": { @@ -23741,9 +22597,7 @@ }, "type": { "type": "string", - "enum": [ - "shell.exited" - ] + "enum": ["shell.exited"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23760,53 +22614,18 @@ ] }, "exit": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] + "type": "number" }, "status": { "type": "string", - "enum": [ - "running", - "exited", - "timeout", - "killed" - ] + "enum": ["running", "exited", "timeout", "killed"] } }, - "required": [ - "id", - "status" - ], + "required": ["id", "status"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "shell.deleted": { @@ -23828,9 +22647,7 @@ }, "type": { "type": "string", - "enum": [ - "shell.deleted" - ] + "enum": ["shell.deleted"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23847,18 +22664,11 @@ ] } }, - "required": [ - "id" - ], + "required": ["id"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "QuestionV2.Option": { @@ -23873,10 +22683,7 @@ "description": "Explanation of choice" } }, - "required": [ - "label", - "description" - ], + "required": ["label", "description"], "additionalProperties": false }, "QuestionV2.Info": { @@ -23904,11 +22711,7 @@ "type": "boolean" } }, - "required": [ - "question", - "header", - "options" - ], + "required": ["question", "header", "options"], "additionalProperties": false }, "QuestionV2.Tool": { @@ -23921,10 +22724,7 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ], + "required": ["messageID", "callID"], "additionalProperties": false }, "question.v2.asked": { @@ -23946,9 +22746,7 @@ }, "type": { "type": "string", - "enum": [ - "question.v2.asked" - ] + "enum": ["question.v2.asked"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -23983,20 +22781,11 @@ "$ref": "#/components/schemas/QuestionV2.Tool" } }, - "required": [ - "id", - "sessionID", - "questions" - ], + "required": ["id", "sessionID", "questions"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "QuestionV2.Answer": { @@ -24024,9 +22813,7 @@ }, "type": { "type": "string", - "enum": [ - "question.v2.replied" - ] + "enum": ["question.v2.replied"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -24057,20 +22844,11 @@ } } }, - "required": [ - "sessionID", - "requestID", - "answers" - ], + "required": ["sessionID", "requestID", "answers"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "question.v2.rejected": { @@ -24092,9 +22870,7 @@ }, "type": { "type": "string", - "enum": [ - "question.v2.rejected" - ] + "enum": ["question.v2.rejected"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -24119,19 +22895,11 @@ ] } }, - "required": [ - "sessionID", - "requestID" - ], + "required": ["sessionID", "requestID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "Form.Metadata1": { @@ -24145,10 +22913,7 @@ }, "op": { "type": "string", - "enum": [ - "eq", - "neq" - ] + "enum": ["eq", "neq"] }, "value": { "anyOf": [ @@ -24162,21 +22927,15 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, @@ -24186,11 +22945,7 @@ ] } }, - "required": [ - "key", - "op", - "value" - ], + "required": ["key", "op", "value"], "additionalProperties": false }, "Form.StringField1": { @@ -24216,18 +22971,11 @@ }, "type": { "type": "string", - "enum": [ - "string" - ] + "enum": ["string"] }, "format": { "type": "string", - "enum": [ - "email", - "uri", - "date", - "date-time" - ] + "enum": ["email", "uri", "date", "date-time"] }, "minLength": { "type": "integer", @@ -24264,10 +23012,7 @@ "type": "boolean" } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.NumberField1": { @@ -24293,9 +23038,7 @@ }, "type": { "type": "string", - "enum": [ - "number" - ] + "enum": ["number"] }, "minimum": { "anyOf": [ @@ -24304,21 +23047,15 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, @@ -24329,21 +23066,15 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, @@ -24354,29 +23085,20 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.IntegerField1": { @@ -24402,9 +23124,7 @@ }, "type": { "type": "string", - "enum": [ - "integer" - ] + "enum": ["integer"] }, "minimum": { "anyOf": [ @@ -24413,21 +23133,15 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, @@ -24438,21 +23152,15 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, @@ -24463,29 +23171,20 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.BooleanField1": { @@ -24511,18 +23210,13 @@ }, "type": { "type": "string", - "enum": [ - "boolean" - ] + "enum": ["boolean"] }, "default": { "type": "boolean" } }, - "required": [ - "key", - "type" - ], + "required": ["key", "type"], "additionalProperties": false }, "Form.MultiselectField1": { @@ -24548,9 +23242,7 @@ }, "type": { "type": "string", - "enum": [ - "multiselect" - ] + "enum": ["multiselect"] }, "options": { "type": "array", @@ -24584,72 +23276,44 @@ } } }, - "required": [ - "key", - "type", - "options" - ], + "required": ["key", "type", "options"], "additionalProperties": false }, - "Form.FormInfo1": { - "type": "object", - "properties": { - "id": { - "type": "string", - "allOf": [ - { - "pattern": "^frm_" - } - ] + "Form.Field1": { + "anyOf": [ + { + "$ref": "#/components/schemas/Form.StringField1" }, - "sessionID": { - "type": "string" + { + "$ref": "#/components/schemas/Form.NumberField1" }, - "title": { - "type": "string" + { + "$ref": "#/components/schemas/Form.IntegerField1" }, - "metadata": { - "$ref": "#/components/schemas/Form.Metadata1" + { + "$ref": "#/components/schemas/Form.BooleanField1" }, - "mode": { - "type": "string", - "enum": [ - "form" - ] + { + "$ref": "#/components/schemas/Form.MultiselectField1" }, - "fields": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.StringField1" - }, - { - "$ref": "#/components/schemas/Form.NumberField1" - }, - { - "$ref": "#/components/schemas/Form.IntegerField1" - }, - { - "$ref": "#/components/schemas/Form.BooleanField1" - }, - { - "$ref": "#/components/schemas/Form.MultiselectField1" - } - ] - } + { + "$ref": "#/components/schemas/Form.ExternalField" + } + ] + }, + "Form.Fields1": { + "type": "array", + "prefixItems": [ + { + "$ref": "#/components/schemas/Form.Field1" } - }, - "required": [ - "id", - "sessionID", - "title", - "mode", - "fields" ], - "additionalProperties": false + "minItems": 1, + "items": { + "$ref": "#/components/schemas/Form.Field1" + } }, - "Form.UrlInfo1": { + "Form.Info1": { "type": "object", "properties": { "id": { @@ -24669,23 +23333,11 @@ "metadata": { "$ref": "#/components/schemas/Form.Metadata1" }, - "mode": { - "type": "string", - "enum": [ - "url" - ] - }, - "url": { - "type": "string" + "fields": { + "$ref": "#/components/schemas/Form.Fields1" } }, - "required": [ - "id", - "sessionID", - "title", - "mode", - "url" - ], + "required": ["id", "sessionID", "title", "fields"], "additionalProperties": false }, "form.created": { @@ -24707,9 +23359,7 @@ }, "type": { "type": "string", - "enum": [ - "form.created" - ] + "enum": ["form.created"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -24718,28 +23368,14 @@ "type": "object", "properties": { "form": { - "anyOf": [ - { - "$ref": "#/components/schemas/Form.FormInfo1" - }, - { - "$ref": "#/components/schemas/Form.UrlInfo1" - } - ] + "$ref": "#/components/schemas/Form.Info1" } }, - "required": [ - "form" - ], + "required": ["form"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "Form.Value1": { @@ -24754,21 +23390,15 @@ }, { "type": "string", - "enum": [ - "NaN" - ] + "enum": ["NaN"] }, { "type": "string", - "enum": [ - "Infinity" - ] + "enum": ["Infinity"] }, { "type": "string", - "enum": [ - "-Infinity" - ] + "enum": ["-Infinity"] } ] }, @@ -24808,9 +23438,7 @@ }, "type": { "type": "string", - "enum": [ - "form.replied" - ] + "enum": ["form.replied"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -24833,20 +23461,11 @@ "$ref": "#/components/schemas/Form.Answer1" } }, - "required": [ - "id", - "sessionID", - "answer" - ], + "required": ["id", "sessionID", "answer"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "form.cancelled": { @@ -24868,9 +23487,7 @@ }, "type": { "type": "string", - "enum": [ - "form.cancelled" - ] + "enum": ["form.cancelled"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -24890,19 +23507,11 @@ "type": "string" } }, - "required": [ - "id", - "sessionID" - ], + "required": ["id", "sessionID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "SessionStatus": { @@ -24912,14 +23521,10 @@ "properties": { "type": { "type": "string", - "enum": [ - "idle" - ] + "enum": ["idle"] } }, - "required": [ - "type" - ], + "required": ["type"], "additionalProperties": false }, { @@ -24927,9 +23532,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "retry" - ] + "enum": ["retry"] }, "attempt": { "type": "integer", @@ -24964,13 +23567,7 @@ "type": "string" } }, - "required": [ - "reason", - "provider", - "title", - "message", - "label" - ], + "required": ["reason", "provider", "title", "message", "label"], "additionalProperties": false }, "next": { @@ -24982,12 +23579,7 @@ ] } }, - "required": [ - "type", - "attempt", - "message", - "next" - ], + "required": ["type", "attempt", "message", "next"], "additionalProperties": false }, { @@ -24995,14 +23587,10 @@ "properties": { "type": { "type": "string", - "enum": [ - "busy" - ] + "enum": ["busy"] } }, - "required": [ - "type" - ], + "required": ["type"], "additionalProperties": false } ] @@ -25026,9 +23614,7 @@ }, "type": { "type": "string", - "enum": [ - "session.status" - ] + "enum": ["session.status"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25048,19 +23634,11 @@ "$ref": "#/components/schemas/SessionStatus" } }, - "required": [ - "sessionID", - "status" - ], + "required": ["sessionID", "status"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "session.idle": { @@ -25082,9 +23660,7 @@ }, "type": { "type": "string", - "enum": [ - "session.idle" - ] + "enum": ["session.idle"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25101,18 +23677,11 @@ ] } }, - "required": [ - "sessionID" - ], + "required": ["sessionID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "tui.prompt.append": { @@ -25134,9 +23703,7 @@ }, "type": { "type": "string", - "enum": [ - "tui.prompt.append" - ] + "enum": ["tui.prompt.append"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25148,18 +23715,11 @@ "type": "string" } }, - "required": [ - "text" - ], + "required": ["text"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "tui.command.execute": { @@ -25181,9 +23741,7 @@ }, "type": { "type": "string", - "enum": [ - "tui.command.execute" - ] + "enum": ["tui.command.execute"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25221,18 +23779,11 @@ ] } }, - "required": [ - "command" - ], + "required": ["command"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "tui.toast.show": { @@ -25254,9 +23805,7 @@ }, "type": { "type": "string", - "enum": [ - "tui.toast.show" - ] + "enum": ["tui.toast.show"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25272,12 +23821,7 @@ }, "variant": { "type": "string", - "enum": [ - "info", - "success", - "warning", - "error" - ] + "enum": ["info", "success", "warning", "error"] }, "duration": { "anyOf": [ @@ -25295,19 +23839,11 @@ ] } }, - "required": [ - "message", - "variant" - ], + "required": ["message", "variant"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "tui.session.select": { @@ -25329,9 +23865,7 @@ }, "type": { "type": "string", - "enum": [ - "tui.session.select" - ] + "enum": ["tui.session.select"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25349,18 +23883,11 @@ ] } }, - "required": [ - "sessionID" - ], + "required": ["sessionID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "installation.updated": { @@ -25382,9 +23909,7 @@ }, "type": { "type": "string", - "enum": [ - "installation.updated" - ] + "enum": ["installation.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25396,18 +23921,11 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "installation.update-available": { @@ -25429,9 +23947,7 @@ }, "type": { "type": "string", - "enum": [ - "installation.update-available" - ] + "enum": ["installation.update-available"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25443,18 +23959,11 @@ "type": "string" } }, - "required": [ - "version" - ], + "required": ["version"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "vcs.branch.updated": { @@ -25476,9 +23985,7 @@ }, "type": { "type": "string", - "enum": [ - "vcs.branch.updated" - ] + "enum": ["vcs.branch.updated"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25493,12 +24000,7 @@ "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "mcp.status.changed": { @@ -25520,9 +24022,7 @@ }, "type": { "type": "string", - "enum": [ - "mcp.status.changed" - ] + "enum": ["mcp.status.changed"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25534,18 +24034,11 @@ "type": "string" } }, - "required": [ - "server" - ], + "required": ["server"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "mcp.resources.changed": { @@ -25567,9 +24060,7 @@ }, "type": { "type": "string", - "enum": [ - "mcp.resources.changed" - ] + "enum": ["mcp.resources.changed"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25581,18 +24072,11 @@ "type": "string" } }, - "required": [ - "server" - ], + "required": ["server"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "permission.asked": { @@ -25614,9 +24098,7 @@ }, "type": { "type": "string", - "enum": [ - "permission.asked" - ] + "enum": ["permission.asked"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25670,10 +24152,7 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ], + "required": ["messageID", "callID"], "additionalProperties": false }, { @@ -25682,23 +24161,11 @@ ] } }, - "required": [ - "id", - "sessionID", - "permission", - "patterns", - "metadata", - "always" - ], + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "permission.replied": { @@ -25720,9 +24187,7 @@ }, "type": { "type": "string", - "enum": [ - "permission.replied" - ] + "enum": ["permission.replied"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25748,27 +24213,14 @@ }, "reply": { "type": "string", - "enum": [ - "once", - "always", - "reject" - ] + "enum": ["once", "always", "reject"] } }, - "required": [ - "sessionID", - "requestID", - "reply" - ], + "required": ["sessionID", "requestID", "reply"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "QuestionOption": { @@ -25783,10 +24235,7 @@ "description": "Explanation of choice" } }, - "required": [ - "label", - "description" - ], + "required": ["label", "description"], "additionalProperties": false }, "QuestionInfo": { @@ -25830,11 +24279,7 @@ "description": "Allow typing a custom answer (default: true)" } }, - "required": [ - "question", - "header", - "options" - ], + "required": ["question", "header", "options"], "additionalProperties": false }, "QuestionTool": { @@ -25852,10 +24297,7 @@ "type": "string" } }, - "required": [ - "messageID", - "callID" - ], + "required": ["messageID", "callID"], "additionalProperties": false }, "question.asked": { @@ -25877,9 +24319,7 @@ }, "type": { "type": "string", - "enum": [ - "question.asked" - ] + "enum": ["question.asked"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25921,20 +24361,11 @@ ] } }, - "required": [ - "id", - "sessionID", - "questions" - ], + "required": ["id", "sessionID", "questions"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "QuestionAnswer": { @@ -25962,9 +24393,7 @@ }, "type": { "type": "string", - "enum": [ - "question.replied" - ] + "enum": ["question.replied"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -25995,20 +24424,11 @@ } } }, - "required": [ - "sessionID", - "requestID", - "answers" - ], + "required": ["sessionID", "requestID", "answers"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "question.rejected": { @@ -26030,9 +24450,7 @@ }, "type": { "type": "string", - "enum": [ - "question.rejected" - ] + "enum": ["question.rejected"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -26057,19 +24475,11 @@ ] } }, - "required": [ - "sessionID", - "requestID" - ], + "required": ["sessionID", "requestID"], "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "session.error": { @@ -26091,9 +24501,7 @@ }, "type": { "type": "string", - "enum": [ - "session.error" - ] + "enum": ["session.error"] }, "location": { "$ref": "#/components/schemas/Location.Ref" @@ -26155,12 +24563,7 @@ "additionalProperties": false } }, - "required": [ - "id", - "created", - "type", - "data" - ], + "required": ["id", "created", "type", "data"], "additionalProperties": false }, "V2Event.server.connected": { @@ -26196,9 +24599,7 @@ }, "type": { "type": "string", - "enum": [ - "server.connected" - ] + "enum": ["server.connected"] }, "data": { "anyOf": [ @@ -26211,11 +24612,7 @@ ] } }, - "required": [ - "id", - "type", - "data" - ], + "required": ["id", "type", "data"], "additionalProperties": false }, "V2Event": { @@ -26278,10 +24675,10 @@ "$ref": "#/components/schemas/session.forked" }, { - "$ref": "#/components/schemas/session.prompt.promoted" + "$ref": "#/components/schemas/session.input.promoted" }, { - "$ref": "#/components/schemas/session.prompt.admitted" + "$ref": "#/components/schemas/session.input.admitted" }, { "$ref": "#/components/schemas/session.execution.started" @@ -26522,9 +24919,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "PtyNotFoundError" - ] + "enum": ["PtyNotFoundError"] }, "ptyID": { "type": "string" @@ -26533,11 +24928,7 @@ "type": "string" } }, - "required": [ - "_tag", - "ptyID", - "message" - ], + "required": ["_tag", "ptyID", "message"], "additionalProperties": false }, "PtyTicket.ConnectToken": { @@ -26555,32 +24946,10 @@ ] } }, - "required": [ - "ticket", - "expires_in" - ], - "additionalProperties": false - }, - "ForbiddenError": { - "type": "object", - "properties": { - "_tag": { - "type": "string", - "enum": [ - "ForbiddenError" - ] - }, - "message": { - "type": "string" - } - }, - "required": [ - "_tag", - "message" - ], + "required": ["ticket", "expires_in"], "additionalProperties": false }, - "Shell1": { + "Shell.Info1": { "type": "object", "properties": { "id": { @@ -26593,12 +24962,7 @@ }, "status": { "type": "string", - "enum": [ - "running", - "exited", - "timeout", - "killed" - ] + "enum": ["running", "exited", "timeout", "killed"] }, "command": { "type": "string" @@ -26621,41 +24985,7 @@ ] }, "exit": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - { - "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] - } - ] + "type": "number" }, "metadata": { "type": "object" @@ -26664,96 +24994,17 @@ "type": "object", "properties": { "started": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - { - "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] - } - ] + "type": "number" }, "completed": { - "anyOf": [ - { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string", - "enum": [ - "NaN" - ] - }, - { - "type": "string", - "enum": [ - "Infinity" - ] - }, - { - "type": "string", - "enum": [ - "-Infinity" - ] - } - ] - }, - { - "type": "string", - "enum": [ - "Infinity", - "-Infinity", - "NaN" - ] - } - ] + "type": "number" } }, - "required": [ - "started" - ], + "required": ["started"], "additionalProperties": false } }, - "required": [ - "id", - "status", - "command", - "cwd", - "shell", - "file", - "metadata", - "time" - ], + "required": ["id", "status", "command", "cwd", "shell", "file", "metadata", "time"], "additionalProperties": false }, "ShellNotFoundError": { @@ -26761,9 +25012,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "ShellNotFoundError" - ] + "enum": ["ShellNotFoundError"] }, "id": { "type": "string" @@ -26772,11 +25021,7 @@ "type": "string" } }, - "required": [ - "_tag", - "id", - "message" - ], + "required": ["_tag", "id", "message"], "additionalProperties": false }, "QuestionV2.Request": { @@ -26809,11 +25054,7 @@ "$ref": "#/components/schemas/QuestionV2.Tool" } }, - "required": [ - "id", - "sessionID", - "questions" - ], + "required": ["id", "sessionID", "questions"], "additionalProperties": false }, "QuestionV2.Reply": { @@ -26827,9 +25068,7 @@ "description": "User answers in order of questions (each answer is an array of selected labels)" } }, - "required": [ - "answers" - ], + "required": ["answers"], "additionalProperties": false }, "QuestionNotFoundError": { @@ -26837,9 +25076,7 @@ "properties": { "_tag": { "type": "string", - "enum": [ - "QuestionNotFoundError" - ] + "enum": ["QuestionNotFoundError"] }, "requestID": { "type": "string" @@ -26848,11 +25085,7 @@ "type": "string" } }, - "required": [ - "_tag", - "requestID", - "message" - ], + "required": ["_tag", "requestID", "message"], "additionalProperties": false }, "Reference.LocalSource": { @@ -26860,9 +25093,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "local" - ] + "enum": ["local"] }, "path": { "type": "string" @@ -26874,10 +25105,7 @@ "type": "boolean" } }, - "required": [ - "type", - "path" - ], + "required": ["type", "path"], "additionalProperties": false }, "Reference.GitSource": { @@ -26885,9 +25113,7 @@ "properties": { "type": { "type": "string", - "enum": [ - "git" - ] + "enum": ["git"] }, "repository": { "type": "string" @@ -26902,10 +25128,7 @@ "type": "boolean" } }, - "required": [ - "type", - "repository" - ], + "required": ["type", "repository"], "additionalProperties": false }, "Reference.Source": { @@ -26937,11 +25160,7 @@ "$ref": "#/components/schemas/Reference.Source" } }, - "required": [ - "name", - "path", - "source" - ], + "required": ["name", "path", "source"], "additionalProperties": false }, "ProjectCopy.Copy": { @@ -26951,9 +25170,7 @@ "type": "string" } }, - "required": [ - "directory" - ], + "required": ["directory"], "additionalProperties": false }, "ProjectCopyError": { @@ -26961,9 +25178,7 @@ "properties": { "name": { "type": "string", - "enum": [ - "ProjectCopyError" - ] + "enum": ["ProjectCopyError"] }, "data": { "type": "object", @@ -26982,16 +25197,11 @@ ] } }, - "required": [ - "message" - ], + "required": ["message"], "additionalProperties": false } }, - "required": [ - "name", - "data" - ], + "required": ["name", "data"], "additionalProperties": false }, "Vcs.FileStatus": { @@ -27018,27 +25228,15 @@ }, "status": { "type": "string", - "enum": [ - "added", - "deleted", - "modified" - ] + "enum": ["added", "deleted", "modified"] } }, - "required": [ - "file", - "additions", - "deletions", - "status" - ], + "required": ["file", "additions", "deletions", "status"], "additionalProperties": false }, "Vcs.Mode": { "type": "string", - "enum": [ - "working", - "branch" - ] + "enum": ["working", "branch"] } }, "securitySchemes": {} @@ -27051,6 +25249,9 @@ { "name": "server" }, + { + "name": "pairing" + }, { "name": "location" }, diff --git a/packages/protocol/src/api.ts b/packages/protocol/src/api.ts index a7a6ef9fd2bd..c4bb964fb9e2 100644 --- a/packages/protocol/src/api.ts +++ b/packages/protocol/src/api.ts @@ -30,6 +30,7 @@ import { CredentialGroup } from "./groups/credential.js" import { ProjectGroup } from "./groups/project.js" import { ProjectCopyGroup } from "./groups/project-copy.js" import { VcsGroup } from "./groups/vcs.js" +import { PairingGroup } from "./groups/pairing.js" type LocationGroups = | HttpApiGroup.AddMiddleware @@ -68,9 +69,7 @@ type MixedMiddlewareGroups< SessionLocationId extends HttpApiMiddleware.AnyId, SessionLocationService, > = - | ReturnType< - typeof makePermissionGroup - > + | ReturnType> | ReturnType> type ApiGroups< @@ -84,6 +83,7 @@ type ApiGroups< > = | typeof HealthGroup | typeof ServerGroup + | typeof PairingGroup | typeof DebugGroup | LocationGroups | FormGroups @@ -146,6 +146,7 @@ const makeApiFromGroup = < HttpApi.make("server") .add(HealthGroup) .add(ServerGroup) + .add(PairingGroup) .add(LocationGroup.middleware(locationMiddleware)) .add(AgentGroup.middleware(locationMiddleware)) .add(PluginGroup.middleware(locationMiddleware)) diff --git a/packages/protocol/src/capabilities.ts b/packages/protocol/src/capabilities.ts new file mode 100644 index 000000000000..33fefb0f9760 --- /dev/null +++ b/packages/protocol/src/capabilities.ts @@ -0,0 +1,90 @@ +export * as Capabilities from "./capabilities.js" + +export type Capability = "administrator" | "mobile" +export type Entry = { readonly method: string; readonly path: string; readonly capability: Capability } + +// These exact method/path pairs are the runtime source of truth for device access. They mirror the +// ShuvKit operations used by OpenShuv; unlisted operations fail closed for device principals. +export const mobile = [ + ["GET", "/api/health"], + ["GET", "/api/server"], + ["GET", "/api/session"], + ["POST", "/api/session"], + ["GET", "/api/session/active"], + ["GET", "/api/session/:sessionID"], + ["DELETE", "/api/session/:sessionID"], + ["GET", "/api/session/:sessionID/message"], + ["GET", "/api/session/:sessionID/message/:messageID"], + ["POST", "/api/session/:sessionID/prompt"], + ["POST", "/api/session/:sessionID/interrupt"], + ["GET", "/api/session/:sessionID/context"], + ["GET", "/api/session/:sessionID/instructions/entries"], + ["POST", "/api/session/:sessionID/fork"], + ["POST", "/api/session/:sessionID/agent"], + ["POST", "/api/session/:sessionID/model"], + ["POST", "/api/session/:sessionID/rename"], + ["POST", "/api/session/:sessionID/command"], + ["POST", "/api/session/:sessionID/skill"], + ["POST", "/api/session/:sessionID/compact"], + ["POST", "/api/session/:sessionID/revert/stage"], + ["POST", "/api/session/:sessionID/revert/clear"], + ["POST", "/api/session/:sessionID/revert/commit"], + ["GET", "/api/experimental/session/:sessionID/log"], + ["GET", "/api/event"], + ["GET", "/api/permission/request"], + ["GET", "/api/permission/saved"], + ["DELETE", "/api/permission/saved/:id"], + ["GET", "/api/session/:sessionID/permission"], + ["GET", "/api/session/:sessionID/permission/:requestID"], + ["POST", "/api/session/:sessionID/permission/:requestID/reply"], + ["GET", "/api/question/request"], + ["GET", "/api/session/:sessionID/question"], + ["POST", "/api/session/:sessionID/question/:requestID/reply"], + ["POST", "/api/session/:sessionID/question/:requestID/reject"], + ["GET", "/api/form/request"], + ["GET", "/api/session/:sessionID/form"], + ["GET", "/api/session/:sessionID/form/:formID/state"], + ["POST", "/api/session/:sessionID/form/:formID/reply"], + ["POST", "/api/session/:sessionID/form/:formID/cancel"], + ["GET", "/api/project"], + ["GET", "/api/project/current"], + ["GET", "/api/project/:projectID/directories"], + ["GET", "/api/agent"], + ["GET", "/api/model"], + ["GET", "/api/model/default"], + ["GET", "/api/command"], + ["GET", "/api/skill"], + ["GET", "/api/vcs/status"], + ["GET", "/api/vcs/diff"], + ["GET", "/api/fs/read/*"], +] as const + +const patterns = mobile.map(([method, path]) => ({ + method, + pattern: new RegExp( + "^" + + path + .split("/") + .map((part) => + part === "*" ? ".*" : part.startsWith(":") ? "[^/]+" : part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), + ) + .join("/") + + "$", + ), +})) + +export function allowsMobile(method: string, pathname: string) { + return patterns.some((entry) => entry.method === method.toUpperCase() && entry.pattern.test(pathname)) +} + +export function isPairingRedemption(method: string, pathname: string) { + return method.toUpperCase() === "POST" && pathname === "/api/pairing/redeem" +} + +export function requiresAdministrator(pathname: string) { + return ( + pathname === "/api/pairing/invitation" || + pathname === "/api/pairing/device" || + pathname.startsWith("/api/pairing/device/") + ) +} diff --git a/packages/protocol/src/client.ts b/packages/protocol/src/client.ts index e89525c1ded0..b8cd5d8fa502 100644 --- a/packages/protocol/src/client.ts +++ b/packages/protocol/src/client.ts @@ -35,6 +35,7 @@ export const ClientApi: ClientApiShape = makeDefaultApi({ export const groupNames = { "server.health": "health", "server.server": "server", + "server.pairing": "pairing", "server.debug": "debug", "server.location": "location", "server.agent": "agent", @@ -45,6 +46,7 @@ export const groupNames = { "server.generate": "generate", "server.provider": "provider", "server.integration": "integration", + "server.mcp": "mcp", "server.credential": "credential", "server.form": "form", "server.permission": "permission", @@ -54,7 +56,6 @@ export const groupNames = { "server.event": "event", "server.pty": "pty", "server.shell": "shell", - "server.mcp": "mcp", "server.question": "question", "server.reference": "reference", "server.project": "project", diff --git a/packages/protocol/src/errors.ts b/packages/protocol/src/errors.ts index d6d1482f3011..6f045349ecba 100644 --- a/packages/protocol/src/errors.ts +++ b/packages/protocol/src/errors.ts @@ -165,6 +165,24 @@ export class ForbiddenError extends Schema.TaggedErrorClass()( { httpApiStatus: 403 }, ) {} +export class PairingConflictError extends Schema.TaggedErrorClass()( + "PairingConflictError", + { message: Schema.String }, + { httpApiStatus: 409 }, +) {} + +export class PairingInvitationUnavailableError extends Schema.TaggedErrorClass()( + "PairingInvitationUnavailableError", + { message: Schema.String }, + { httpApiStatus: 410 }, +) {} + +export class PairingDeviceNotFoundError extends Schema.TaggedErrorClass()( + "PairingDeviceNotFoundError", + { deviceID: Schema.String, message: Schema.String }, + { httpApiStatus: 404 }, +) {} + export class PtyNotFoundError extends Schema.TaggedErrorClass()( "PtyNotFoundError", { diff --git a/packages/protocol/src/groups/pairing.ts b/packages/protocol/src/groups/pairing.ts new file mode 100644 index 000000000000..67a156a45072 --- /dev/null +++ b/packages/protocol/src/groups/pairing.ts @@ -0,0 +1,43 @@ +import { Pairing } from "@opencode-ai/schema/pairing" +import { Schema } from "effect" +import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema, OpenApi } from "effect/unstable/httpapi" +import { + ForbiddenError, + InvalidRequestError, + PairingConflictError, + PairingDeviceNotFoundError, + PairingInvitationUnavailableError, + UnauthorizedError, + ServiceUnavailableError, +} from "../errors.js" + +export const PairingGroup = HttpApiGroup.make("server.pairing") + .add( + HttpApiEndpoint.post("pairing.invitation.create", "/api/pairing/invitation", { + success: Pairing.Invitation, + error: [UnauthorizedError, ForbiddenError, ServiceUnavailableError], + }).annotateMerge( + OpenApi.annotations({ identifier: "v2.pairing.invitation.create", summary: "Create pairing invitation" }), + ), + ) + .add( + HttpApiEndpoint.post("pairing.redeem", "/api/pairing/redeem", { + payload: Pairing.RedeemRequest, + success: Pairing.RedeemResponse, + error: [InvalidRequestError, PairingConflictError, PairingInvitationUnavailableError], + }).annotateMerge(OpenApi.annotations({ identifier: "v2.pairing.redeem", summary: "Redeem pairing invitation" })), + ) + .add( + HttpApiEndpoint.get("pairing.device.list", "/api/pairing/device", { + success: Schema.Array(Pairing.Device), + error: [UnauthorizedError, ForbiddenError], + }).annotateMerge(OpenApi.annotations({ identifier: "v2.pairing.device.list", summary: "List paired devices" })), + ) + .add( + HttpApiEndpoint.delete("pairing.device.revoke", "/api/pairing/device/:deviceID", { + params: { deviceID: Pairing.DeviceID }, + success: HttpApiSchema.NoContent, + error: [UnauthorizedError, ForbiddenError, PairingDeviceNotFoundError], + }).annotateMerge(OpenApi.annotations({ identifier: "v2.pairing.device.revoke", summary: "Revoke paired device" })), + ) + .annotateMerge(OpenApi.annotations({ title: "pairing" })) diff --git a/packages/protocol/src/middleware/authorization.ts b/packages/protocol/src/middleware/authorization.ts index 54d33bc26ee2..91fe073c9ab5 100644 --- a/packages/protocol/src/middleware/authorization.ts +++ b/packages/protocol/src/middleware/authorization.ts @@ -1,6 +1,16 @@ import { HttpApiMiddleware } from "effect/unstable/httpapi" -import { UnauthorizedError } from "../errors.js" +import { Context } from "effect" +import { Pairing } from "@opencode-ai/schema/pairing" +import { ForbiddenError, UnauthorizedError } from "../errors.js" -export class Authorization extends HttpApiMiddleware.Service()("@opencode/HttpApiAuthorization", { - error: UnauthorizedError, -}) {} +export type PrincipalInfo = + | { readonly type: "administrator" } + | { readonly type: "device"; readonly deviceID: Pairing.DeviceID } + | { readonly type: "unauthenticated"; readonly reason: "embedded" | "pairing-redemption" | "pty-ticket" } + +export class Principal extends Context.Service()("@opencode/HttpPrincipal") {} + +export class Authorization extends HttpApiMiddleware.Service()( + "@opencode/HttpApiAuthorization", + { error: [UnauthorizedError, ForbiddenError] }, +) {} diff --git a/packages/protocol/test/capabilities.test.ts b/packages/protocol/test/capabilities.test.ts new file mode 100644 index 000000000000..cac4bee2307c --- /dev/null +++ b/packages/protocol/test/capabilities.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, test } from "bun:test" +import { OpenApi } from "effect/unstable/httpapi" +import { Capabilities } from "../src/capabilities.js" +import { ClientApi } from "../src/client.js" + +describe("mobile capability metadata", () => { + test("every governed method and path exists in the Protocol API", () => { + const paths = OpenApi.fromApi(ClientApi).paths + for (const [method, template] of Capabilities.mobile) { + const path = template.replace(/:([^/]+)/g, "{$1}") + expect(paths[path]?.[method.toLowerCase() as keyof (typeof paths)[string]]).toBeDefined() + } + }) + + test("classification is method-specific and fails closed", () => { + expect(Capabilities.allowsMobile("GET", "/api/server")).toBe(true) + expect(Capabilities.allowsMobile("POST", "/api/server")).toBe(false) + expect(Capabilities.allowsMobile("GET", "/api/debug/location")).toBe(false) + expect(Capabilities.isPairingRedemption("POST", "/api/pairing/redeem")).toBe(true) + expect(Capabilities.isPairingRedemption("GET", "/api/pairing/redeem")).toBe(false) + }) +}) diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 7b09e92426c6..48150fbe715d 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -29,6 +29,7 @@ export { Skill } from "./skill.js" export { TokenUsage } from "./token-usage.js" export { Pty } from "./pty.js" export { PtyTicket } from "./pty-ticket.js" +export { Pairing } from "./pairing.js" export { Question } from "./question.js" export { Workspace } from "./workspace.js" export { Prompt, PromptMention, FileSource, FileAttachment, AgentAttachment } from "./prompt.js" diff --git a/packages/schema/src/pairing.ts b/packages/schema/src/pairing.ts new file mode 100644 index 000000000000..8207e6c62ed4 --- /dev/null +++ b/packages/schema/src/pairing.ts @@ -0,0 +1,75 @@ +export * as Pairing from "./pairing.js" + +import { Schema } from "effect" +import { ascending } from "./identifier.js" +import { statics } from "./schema.js" + +const Base64Url32 = Schema.String.check(Schema.isPattern(/^[A-Za-z0-9_-]{43}$/)) + +export const DeviceID = Schema.String.pipe( + Schema.check(Schema.isStartsWith("device_")), + Schema.brand("Pairing.DeviceID"), + statics((schema) => ({ create: () => schema.make("device_" + ascending()) })), +) +export type DeviceID = typeof DeviceID.Type + +export const InvitationToken = Base64Url32.pipe(Schema.brand("Pairing.InvitationToken")) +export type InvitationToken = typeof InvitationToken.Type + +export const DeviceCredential = Schema.String.check(Schema.isPattern(/^scd_v1_[A-Za-z0-9_-]{43}$/)).pipe( + Schema.brand("Pairing.DeviceCredential"), +) +export type DeviceCredential = typeof DeviceCredential.Type + +export const RequestID = Schema.String.check( + Schema.isPattern(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/), +).pipe(Schema.brand("Pairing.RequestID")) +export type RequestID = typeof RequestID.Type + +export const DeviceName = Schema.Trim.check(Schema.isMinLength(1)).pipe(Schema.brand("Pairing.DeviceName")) +export type DeviceName = typeof DeviceName.Type + +export const Invitation = Schema.Struct({ + v: Schema.Literal(1), + kind: Schema.Literal("shuvcode.pair"), + urls: Schema.Array(Schema.String), + token: InvitationToken, + expiresAt: Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)), +}).annotate({ identifier: "Pairing.Invitation" }) +export type Invitation = typeof Invitation.Type + +export const RedeemRequest = Schema.Struct({ + token: InvitationToken, + requestID: RequestID, + deviceName: DeviceName, + credential: DeviceCredential, +}).annotate({ identifier: "Pairing.RedeemRequest" }) +export type RedeemRequest = typeof RedeemRequest.Type + +export const RedeemResponse = Schema.Struct({ deviceID: DeviceID }).annotate({ + identifier: "Pairing.RedeemResponse", +}) +export type RedeemResponse = typeof RedeemResponse.Type + +export const Device = Schema.Struct({ + deviceID: DeviceID, + name: DeviceName, + createdAt: Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)), + updatedAt: Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/)), + revokedAt: Schema.optional(Schema.String.check(Schema.isPattern(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/))), +}).annotate({ identifier: "Pairing.Device" }) +export type Device = typeof Device.Type + +export function advertisedURLs(values: ReadonlyArray) { + return [...new Set(values.map(advertisedURL))] +} + +function advertisedURL(value: string) { + const url = new URL(value) + if (!["http:", "https:"].includes(url.protocol)) throw new Error("Advertised URLs must use HTTP or HTTPS") + if (url.username || url.password || url.search || url.hash || url.pathname !== "/") + throw new Error("Advertised URLs cannot contain userinfo, a path, query, or fragment") + if (url.protocol === "http:" && !["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)) + throw new Error("Advertised HTTP URLs must be loopback") + return url.toString().replace(/\/$/, "") +} diff --git a/packages/server/package.json b/packages/server/package.json index 17507ea5b49f..3d1dd6ef7d47 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -16,6 +16,7 @@ "@effect/platform-node": "catalog:", "@opencode-ai/core": "workspace:*", "@opencode-ai/protocol": "workspace:*", + "@opencode-ai/schema": "workspace:*", "@opencode-ai/simulation": "workspace:*", "drizzle-orm": "catalog:", "effect": "catalog:" diff --git a/packages/server/src/handlers.ts b/packages/server/src/handlers.ts index 4b0b4acaa225..f6ddd78c26ac 100644 --- a/packages/server/src/handlers.ts +++ b/packages/server/src/handlers.ts @@ -27,10 +27,12 @@ import { ProjectHandler } from "./handlers/project" import { ProjectCopyHandler } from "./handlers/project-copy" import { VcsHandler } from "./handlers/vcs" import { EventFeed } from "./event-feed" +import { PairingHandler } from "./handlers/pairing" export const handlers = Layer.mergeAll( HealthHandler, ServerHandler, + PairingHandler, DebugHandler, LocationHandler, AgentHandler, diff --git a/packages/server/src/handlers/pairing.ts b/packages/server/src/handlers/pairing.ts new file mode 100644 index 000000000000..94d934339072 --- /dev/null +++ b/packages/server/src/handlers/pairing.ts @@ -0,0 +1,42 @@ +import { Pairing } from "@opencode-ai/core/pairing" +import { + InvalidRequestError, + PairingConflictError, + PairingDeviceNotFoundError, + PairingInvitationUnavailableError, + ServiceUnavailableError, +} from "@opencode-ai/protocol/errors" +import { Effect } from "effect" +import { HttpApiBuilder } from "effect/unstable/httpapi" +import { Api } from "../api" +import { ServerInfo } from "../server-info" + +const pairingError = (error: Pairing.Conflict | Pairing.InvitationUnavailable | Pairing.InvalidRequest) => { + if (error._tag === "PairingConflict") return new PairingConflictError({ message: error.message }) + if (error._tag === "PairingInvalidRequest") return new InvalidRequestError({ message: error.message }) + return new PairingInvitationUnavailableError({ message: error.message }) +} + +export const PairingHandler = HttpApiBuilder.group(Api, "server.pairing", (handlers) => + handlers + .handle("pairing.invitation.create", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const server = yield* ServerInfo.Service + return yield* pairing + .issue({ urls: server.urls() }) + .pipe(Effect.mapError((error) => new ServiceUnavailableError({ message: error.message, service: "pairing" }))) + }), + ) + .handle("pairing.redeem", (ctx) => + Pairing.Service.use((pairing) => pairing.redeem(ctx.payload)).pipe(Effect.mapError(pairingError)), + ) + .handle("pairing.device.list", () => Pairing.Service.use((pairing) => pairing.list())) + .handle("pairing.device.revoke", (ctx) => + Pairing.Service.use((pairing) => pairing.revoke(ctx.params.deviceID)).pipe( + Effect.mapError( + (error) => new PairingDeviceNotFoundError({ deviceID: error.deviceID, message: error.message }), + ), + ), + ), +) diff --git a/packages/server/src/middleware/authorization.ts b/packages/server/src/middleware/authorization.ts index cc785ee2472a..09ebe22eec8a 100644 --- a/packages/server/src/middleware/authorization.ts +++ b/packages/server/src/middleware/authorization.ts @@ -1,13 +1,15 @@ import { ServerAuth } from "../auth" -import { UnauthorizedError } from "@opencode-ai/protocol/errors" -import { Authorization } from "@opencode-ai/protocol/middleware/authorization" -export { Authorization } from "@opencode-ai/protocol/middleware/authorization" +import { Pairing } from "@opencode-ai/core/pairing" +import { ForbiddenError, UnauthorizedError } from "@opencode-ai/protocol/errors" +import { Authorization, Principal } from "@opencode-ai/protocol/middleware/authorization" +export { Authorization, Principal } from "@opencode-ai/protocol/middleware/authorization" import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty" +import { Capabilities } from "@opencode-ai/protocol/capabilities" import { Effect, Encoding, Layer, Redacted } from "effect" import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" const AUTH_TOKEN_QUERY = "auth_token" -const WWW_AUTHENTICATE = 'Basic realm="Secure Area"' +const WWW_AUTHENTICATE = 'Basic realm="Secure Area", Bearer realm="Shuvcode Device"' function emptyCredential() { return { username: "", password: Redacted.make("") } @@ -35,19 +37,42 @@ function credentialFromRequest(request: HttpServerRequest.HttpServerRequest) { return Effect.succeed(emptyCredential()) } +function bearerFromRequest(request: HttpServerRequest.HttpServerRequest) { + return /^Bearer\s+(\S+)$/i.exec(request.headers.authorization ?? "")?.[1] +} + export const authorizationLayer = Layer.effect( Authorization, Effect.gen(function* () { const config = yield* ServerAuth.Config - if (!ServerAuth.required(config)) return Authorization.of((effect) => effect) + const pairing = yield* Pairing.Service return Authorization.of((effect) => Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest + const url = new URL(request.url, "http://localhost") // Browsers cannot set headers on WebSocket upgrades, so a ticketed PTY connect skips // credential checks here; the connect handler consumes and validates the ticket. - if (hasPtyConnectTicketURL(new URL(request.url, "http://localhost"))) return yield* effect + if (hasPtyConnectTicketURL(url)) + return yield* effect.pipe(Effect.provideService(Principal, { type: "unauthenticated", reason: "pty-ticket" })) + if (Capabilities.isPairingRedemption(request.method, url.pathname)) + return yield* effect.pipe( + Effect.provideService(Principal, { type: "unauthenticated", reason: "pairing-redemption" }), + ) const credential = yield* credentialFromRequest(request) - if (ServerAuth.authorized(credential, config)) return yield* effect + if (ServerAuth.authorized(credential, config)) + return yield* effect.pipe(Effect.provideService(Principal, { type: "administrator" })) + const bearer = bearerFromRequest(request) + const principal = bearer ? yield* pairing.authenticate(bearer) : undefined + if (principal?.type === "device") { + if ( + Capabilities.requiresAdministrator(url.pathname) || + !Capabilities.allowsMobile(request.method, url.pathname) + ) + return yield* new ForbiddenError({ message: "Administrator access required" }) + return yield* effect.pipe(Effect.provideService(Principal, principal)) + } + if (!ServerAuth.required(config) && !Capabilities.requiresAdministrator(url.pathname)) + return yield* effect.pipe(Effect.provideService(Principal, { type: "unauthenticated", reason: "embedded" })) yield* HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)), ) diff --git a/packages/server/src/process.ts b/packages/server/src/process.ts index d9bef69e10e7..ffea69a18d09 100644 --- a/packages/server/src/process.ts +++ b/packages/server/src/process.ts @@ -16,6 +16,7 @@ export type Options = { readonly port: Option.Option readonly password: string readonly restartContinuity?: boolean + readonly advertisedURLs?: ReadonlyArray } const ReadinessApi = HttpApi.make("readiness").add(HealthGroup) @@ -50,6 +51,8 @@ function bind(options: Options, port: number) { const server = createServer() return Layer.build( createRoutes(options.password, () => { + if (options.advertisedURLs && options.advertisedURLs.length > 0) + return ServerInfo.advertisedURLs(options.advertisedURLs) const address = server.address() if (address === null || typeof address === "string") return [] const host = address.family === "IPv6" ? `[${address.address}]` : address.address diff --git a/packages/server/src/routes.ts b/packages/server/src/routes.ts index 3f44ec3bf186..7f153d536b20 100644 --- a/packages/server/src/routes.ts +++ b/packages/server/src/routes.ts @@ -6,6 +6,7 @@ import { EventV2 } from "@opencode-ai/core/event" import { EventLogger } from "@opencode-ai/core/event-logger" import { Observability } from "@opencode-ai/core/observability" import { Credential } from "@opencode-ai/core/credential" +import { Pairing } from "@opencode-ai/core/pairing" import { PermissionSaved } from "@opencode-ai/core/permission/saved" import { PtyTicket } from "@opencode-ai/core/pty/ticket" import { MoveSession } from "@opencode-ai/core/control-plane/move-session" @@ -23,7 +24,7 @@ import { Context, Effect, Layer, Option } from "effect" import { Api } from "./api" import { ServerAuth } from "./auth" import { handlers } from "./handlers" -import { authorizationLayer } from "./middleware/authorization" +import { authorizationLayer, Principal } from "./middleware/authorization" import { schemaErrorLayer } from "./middleware/schema-error" import { PtyEnvironment } from "./pty-environment" import { layer } from "./location" @@ -46,6 +47,7 @@ const applicationServices = LayerNode.group([ PermissionSaved.node, PtyTicket.node, Credential.node, + Pairing.node, PtyEnvironment.node, LocationServiceMap.node, SessionRestart.node, @@ -91,16 +93,17 @@ function makeRoutes( return serviceLayer.pipe( Layer.flatMap((context) => { const services = Layer.succeedContext(context) - const requestServices = Layer.merge( - Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service)(context)), + const requestServices = Layer.mergeAll( + Layer.succeedContext(Context.pick(PermissionSaved.Service, Project.Service, Pairing.Service)(context)), ServerInfo.layer(serviceURLs), + Layer.succeed(Principal, { type: "unauthenticated", reason: "embedded" }), ) return HttpApiBuilder.layer(Api, { openapiPath: "/openapi.json" }).pipe( Layer.provide(handlers.pipe(Layer.provide(services))), Layer.provide(formLocationLayer), Layer.provide(sessionLocationLayer), Layer.provide(layer), - Layer.provide(authorizationLayer), + Layer.provide(authorizationLayer.pipe(Layer.provide(services))), Layer.provide(schemaErrorLayer), Layer.provide(auth), Layer.provide(Observability.layer), diff --git a/packages/server/src/server-info.ts b/packages/server/src/server-info.ts index 7d1ba4b094f7..a94fcccffdb7 100644 --- a/packages/server/src/server-info.ts +++ b/packages/server/src/server-info.ts @@ -1,4 +1,5 @@ import { Context, Layer } from "effect" +import { Pairing } from "@opencode-ai/schema/pairing" import { networkInterfaces } from "node:os" export class Service extends Context.Service ReadonlyArray }>()( @@ -29,4 +30,8 @@ export function connectionURLs(value: string, requestedHostname?: string) { ] } +export function advertisedURLs(values: ReadonlyArray) { + return Pairing.advertisedURLs(values) +} + export * as ServerInfo from "./server-info" diff --git a/packages/server/test/pairing.test.ts b/packages/server/test/pairing.test.ts new file mode 100644 index 000000000000..dfbefe38411e --- /dev/null +++ b/packages/server/test/pairing.test.ts @@ -0,0 +1,100 @@ +import { afterAll, beforeAll, describe, expect, test } from "bun:test" +import { Layer } from "effect" +import { randomBytes, randomUUID } from "node:crypto" +import { HttpRouter, HttpServer } from "effect/unstable/http" +import { createRoutes } from "../src/routes" + +process.env.OPENCODE_DB = ":memory:" + +const password = "admin-secret" +const basic = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` +const app = HttpRouter.toWebHandler( + createRoutes(password, () => ["https://shuvdev.example"]).pipe(Layer.provide(HttpServer.layerServices)), +) + +beforeAll(async () => { + await app.handler(new Request("http://localhost/api/health", { headers: { authorization: basic } })) +}) + +afterAll(() => app.dispose()) + +describe("pairing HTTP authorization", () => { + test("redeems without server credentials but keeps management administrator-only", async () => { + const invitationResponse = await app.handler( + new Request("http://localhost/api/pairing/invitation", { + method: "POST", + headers: { authorization: basic }, + }), + ) + expect(invitationResponse.status).toBe(200) + const invitation = await invitationResponse.json() + expect(invitation).toMatchObject({ v: 1, kind: "shuvcode.pair", urls: ["https://shuvdev.example"] }) + + const credential = `scd_v1_${randomBytes(32).toString("base64url")}` + const redemptionResponse = await app.handler( + new Request("http://localhost/api/pairing/redeem", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + token: invitation.token, + requestID: randomUUID(), + deviceName: "Test Phone", + credential, + }), + }), + ) + expect(redemptionResponse.status).toBe(200) + + expect( + ( + await app.handler( + new Request("http://localhost/api/server", { headers: { authorization: `Bearer ${credential}` } }), + ) + ).status, + ).toBe(200) + expect( + ( + await app.handler( + new Request("http://localhost/api/pairing/invitation", { + method: "POST", + headers: { authorization: `Bearer ${credential}` }, + }), + ) + ).status, + ).toBe(403) + expect( + ( + await app.handler( + new Request("http://localhost/api/debug/location", { + headers: { authorization: `Bearer ${credential}` }, + }), + ) + ).status, + ).toBe(403) + }) + + test("preserves the exact PTY ticket bypass and rejects unrelated unauthenticated routes", async () => { + const unauthorized = await app.handler(new Request("http://localhost/api/server")) + expect(await unauthorized.clone().text()).toContain("UnauthorizedError") + expect(unauthorized.status).toBe(401) + expect( + (await app.handler(new Request("http://localhost/api/pairing/device", { headers: { authorization: basic } }))) + .status, + ).toBe(200) + expect((await app.handler(new Request("http://localhost/api/pty/pty_missing/connect"))).status).toBe(401) + expect( + (await app.handler(new Request("http://localhost/api/pty/pty_missing/connect?ticket=one-time"))).status, + ).toBe(404) + expect( + ( + await app.handler( + new Request("http://localhost/api/pairing/redeem", { + method: "POST", + headers: { "content-type": "application/json" }, + body: "{}", + }), + ) + ).status, + ).toBe(400) + }) +}) diff --git a/packages/server/test/server-info.test.ts b/packages/server/test/server-info.test.ts new file mode 100644 index 000000000000..3eb4b8b79e6f --- /dev/null +++ b/packages/server/test/server-info.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, test } from "bun:test" +import { ServerInfo } from "../src/server-info" + +describe("ServerInfo.advertisedURLs", () => { + test("accepts HTTPS and loopback HTTP independently from the bind address", () => { + expect(ServerInfo.advertisedURLs(["https://shuvdev.example:10001", "http://127.0.0.1:4096"])).toEqual([ + "https://shuvdev.example:10001", + "http://127.0.0.1:4096", + ]) + }) + + test("rejects unsafe or ambiguous advertised URLs", () => { + for (const value of [ + "http://shuvdev.example:4096", + "https://user@shuvdev.example", + "https://shuvdev.example/path", + "https://shuvdev.example?token=secret", + "ftp://shuvdev.example", + ]) { + expect(() => ServerInfo.advertisedURLs([value])).toThrow() + } + }) +}) diff --git a/packages/tui/src/app.tsx b/packages/tui/src/app.tsx index c6be9754a0c3..743e1598e69f 100644 --- a/packages/tui/src/app.tsx +++ b/packages/tui/src/app.tsx @@ -54,7 +54,7 @@ import { DialogMcp } from "./component/dialog-mcp" import { DialogStatus } from "./component/dialog-status" import { DialogConfig } from "./component/dialog-config" import { DialogDebug } from "./component/dialog-debug" -import { DialogPair, type DialogPairCredentials } from "./component/dialog-pair" +import { DialogPair } from "./component/dialog-pair" import { createOpencodeClient } from "@opencode-ai/sdk/v2/client" import { DialogThemeList } from "./component/dialog-theme-list" import { DialogHelp } from "./ui/dialog-help" @@ -345,35 +345,25 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -411,10 +401,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) { }) }) -function App(props: { - pluginHost: TuiPluginHost - pair?: DialogPairCredentials -}) { +function App(props: { pluginHost: TuiPluginHost }) { const log = useLog({ component: "app" }) const startup = useTuiStartup() const configState = useConfig() @@ -811,7 +798,7 @@ function App(props: { title: "Pair device", slashName: "pair", run: () => { - dialog.replace(() => ) + dialog.replace(() => ) }, category: "System", }, diff --git a/packages/tui/src/component/dialog-pair.tsx b/packages/tui/src/component/dialog-pair.tsx index 807cac0abbca..3035b80e2488 100644 --- a/packages/tui/src/component/dialog-pair.tsx +++ b/packages/tui/src/component/dialog-pair.tsx @@ -7,76 +7,46 @@ import { useTheme } from "../context/theme" import { useDialog } from "../ui/dialog" import { errorMessage } from "../util/error" -export type DialogPairCredentials = { - readonly username: string - readonly password: string -} - -export function DialogPair(props: { credentials?: DialogPairCredentials }) { +export function DialogPair() { const sdk = useSDK() const dialog = useDialog() const dimensions = useTerminalDimensions() const { theme } = useTheme() const [loadError, setLoadError] = createSignal() - const [showPassword, setShowPassword] = createSignal(false) - const [passwordHover, setPasswordHover] = createSignal(false) + const [revoking, setRevoking] = createSignal() dialog.setSize("large") dialog.setCentered(true) - const [server] = createResource(() => - sdk.api.server - .get() - .catch((error) => { - setLoadError(error) - return undefined - }), + const [invitation, invitationActions] = createResource(() => + sdk.api.pairing.invitation.create().catch((error) => { + setLoadError(error) + return undefined + }), ) - const info = createMemo(() => { - const current = server() - if (!current) return - return { - urls: current.urls, - username: props.credentials?.username ?? "opencode", - password: props.credentials?.password ?? "", - } - }) + const [devices, deviceActions] = createResource(() => sdk.api.pairing.device.list()) + const info = createMemo(() => invitation()) const horizontal = createMemo(() => dimensions().width >= 96) const content = () => { const value = info() if (!value) return return ( - + URLs {(url) => {url}} - Username - {value.username} + Expires + {value.expiresAt} - - Password - setPasswordHover(true)} - onMouseOut={() => setPasswordHover(false)} - onMouseUp={() => setShowPassword((current) => !current)} - > - {showPassword() ? value.password : "************"} - - - ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname))} - > + invitationActions.refetch()}> + Regenerate invitation + + ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname))}> - Run `opencode service set hostname 0.0.0.0` to access the service remotely. + Configure `shuvcode service set advertised-urls https://host` for Tailscale Serve or a reverse proxy. @@ -88,6 +58,36 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) { > {renderUnicodeCompact(JSON.stringify(value), { border: 1 })} + + Paired devices + 0} fallback={No paired devices}> + + {(device) => ( + + + {device.name} + {device.revokedAt ? "revoked" : device.deviceID} + + + { + setRevoking(device.deviceID) + sdk.api.pairing.device + .revoke({ deviceID: device.deviceID }) + .then(() => deviceActions.refetch()) + .catch(setLoadError) + .finally(() => setRevoking(undefined)) + }} + > + {revoking() === device.deviceID ? "Revoking..." : "Revoke"} + + + + )} + + + ) } @@ -102,9 +102,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) { esc - - {(error) => {errorMessage(error())}} - + {(error) => {errorMessage(error())}} Loading server information...}> = 36} From 039614799a6f47da4d443cd3ce21c2302ded9f97 Mon Sep 17 00:00:00 2001 From: shuv Date: Tue, 14 Jul 2026 16:29:04 -0700 Subject: [PATCH 2/7] fix(pairing): address review findings --- .../client/src/promise/generated/types.ts | 2 +- packages/core/src/pairing.ts | 45 ++- packages/core/test/pairing.test.ts | 114 ++++-- packages/docs/openapi.json | 26 +- packages/protocol/src/capabilities.ts | 175 ++++---- packages/protocol/test/capabilities.test.ts | 31 +- packages/schema/src/pairing.ts | 9 +- packages/schema/test/pairing.test.ts | 13 + .../server/src/middleware/authorization.ts | 4 +- packages/tui/src/component/dialog-pair.tsx | 47 ++- packages/tui/test/cli/tui/dialog-pair.test.ts | 16 + specs/v2/mobile-server-pairing.md | 382 ++++++++++++++++++ 12 files changed, 708 insertions(+), 156 deletions(-) create mode 100644 packages/schema/test/pairing.test.ts create mode 100644 packages/tui/test/cli/tui/dialog-pair.test.ts create mode 100644 specs/v2/mobile-server-pairing.md diff --git a/packages/client/src/promise/generated/types.ts b/packages/client/src/promise/generated/types.ts index cd0fc3539a7b..5deaa9d8234a 100644 --- a/packages/client/src/promise/generated/types.ts +++ b/packages/client/src/promise/generated/types.ts @@ -9,7 +9,7 @@ export type PairingDevice = { name: string createdAt: string updatedAt: string - revokedAt?: string | null + revokedAt?: string | undefined } export type ModelRef = { id: string; providerID: string; variant?: string } diff --git a/packages/core/src/pairing.ts b/packages/core/src/pairing.ts index 04aa600d6657..9ae060afd135 100644 --- a/packages/core/src/pairing.ts +++ b/packages/core/src/pairing.ts @@ -1,7 +1,7 @@ export * as Pairing from "./pairing" import { asc, eq, or } from "drizzle-orm" -import { Context, Data, Duration, Effect, Layer, Semaphore } from "effect" +import { Context, Data, Duration, Effect, Layer, Schema, Semaphore } from "effect" import { Pairing } from "@opencode-ai/schema/pairing" import { Database } from "./database/database" import { makeGlobalNode } from "./effect/app-node" @@ -52,7 +52,7 @@ export class Service extends Context.Service()("@opencode/Pa export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => Effect.gen(function* () { - const { db } = yield* Database.Service + const database = yield* Database.Service const lock = Semaphore.makeUnsafe(1) const invitations = new Map() @@ -100,15 +100,15 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => ) }), redeem: Effect.fn("Pairing.redeem")(function* (input) { - if (!/^scd_v1_[A-Za-z0-9_-]{43}$/.test(input.credential)) + if (!Schema.is(Pairing.DeviceCredential)(input.credential)) return yield* new InvalidRequest({ message: "Invalid device credential" }) - if (input.deviceName !== input.deviceName.trim() || Array.from(input.deviceName).length > 80) + if (!Schema.is(Pairing.DeviceName)(input.deviceName)) return yield* new InvalidRequest({ message: "Invalid device name" }) const invitationHash = Hash.sha256(input.token) const credentialHash = Hash.sha256(input.credential) return yield* lock.withPermit( Effect.gen(function* () { - const existing = yield* db + const existing = yield* database.db .select() .from(PairingDeviceTable) .where( @@ -135,26 +135,29 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => invitations.delete(invitationHash) return yield* new InvitationUnavailable({ message: "Pairing invitation is unavailable" }) } - invitations.delete(invitationHash) const deviceID = Pairing.DeviceID.create() - yield* db - .insert(PairingDeviceTable) - .values({ - id: deviceID, - request_id: input.requestID, - name: input.deviceName, - credential_hash: credentialHash, - invitation_hash: invitationHash, - }) - .run() + yield* database.db + .transaction((tx) => + tx + .insert(PairingDeviceTable) + .values({ + id: deviceID, + request_id: input.requestID, + name: input.deviceName, + credential_hash: credentialHash, + invitation_hash: invitationHash, + }) + .run(), + ) .pipe(Effect.orDie) + invitations.delete(invitationHash) return { deviceID } }), ) }), authenticate: Effect.fn("Pairing.authenticate")(function* (credential) { - if (!/^scd_v1_[A-Za-z0-9_-]{43}$/.test(credential)) return - const row = yield* db + if (!Schema.is(Pairing.DeviceCredential)(credential)) return + const row = yield* database.db .select({ id: PairingDeviceTable.id, time_revoked: PairingDeviceTable.time_revoked }) .from(PairingDeviceTable) .where(eq(PairingDeviceTable.credential_hash, Hash.sha256(credential))) @@ -164,7 +167,7 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => return { type: "device" as const, deviceID: row.id } }), list: Effect.fn("Pairing.list")(function* () { - return (yield* db + return (yield* database.db .select() .from(PairingDeviceTable) .orderBy(asc(PairingDeviceTable.time_created)) @@ -172,7 +175,7 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => .pipe(Effect.orDie)).map(rowDevice) }), revoke: Effect.fn("Pairing.revoke")(function* (deviceID) { - const row = yield* db + const row = yield* database.db .select({ id: PairingDeviceTable.id, time_revoked: PairingDeviceTable.time_revoked }) .from(PairingDeviceTable) .where(eq(PairingDeviceTable.id, deviceID)) @@ -180,7 +183,7 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => .pipe(Effect.orDie) if (!row) return yield* new DeviceNotFound({ deviceID, message: "Pairing device not found" }) if (row.time_revoked !== null) return - yield* db + yield* database.db .update(PairingDeviceTable) .set({ time_revoked: Date.now() }) .where(eq(PairingDeviceTable.id, deviceID)) diff --git a/packages/core/test/pairing.test.ts b/packages/core/test/pairing.test.ts index 2e8e92990712..bdf34a5d73e6 100644 --- a/packages/core/test/pairing.test.ts +++ b/packages/core/test/pairing.test.ts @@ -4,13 +4,13 @@ import { eq } from "drizzle-orm" import { Pairing } from "@opencode-ai/core/pairing" import { PairingDeviceTable } from "@opencode-ai/core/pairing/sql" import { Database } from "@opencode-ai/core/database/database" -import { Pairing as PairingSchema } from "@opencode-ai/schema/pairing" +import { DeviceCredential, DeviceName, type InvitationToken, RequestID } from "@opencode-ai/schema/pairing" import { LayerNode } from "@opencode-ai/core/effect/layer-node" import { testEffect } from "./lib/effect" const it = testEffect(Layer.merge(LayerNode.compile(Pairing.node), LayerNode.compile(Database.node))) -const credential = PairingSchema.DeviceCredential.make("scd_v1_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") -const requestID = PairingSchema.RequestID.make("1da45bb5-9a85-4a29-955b-c7d6d74f13de") +const credential = DeviceCredential.make("scd_v1_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") +const requestID = RequestID.make("1da45bb5-9a85-4a29-955b-c7d6d74f13de") describe("Pairing.Service", () => { it.effect("enrolls once, reconciles exact retries, and authenticates the device", () => @@ -20,7 +20,7 @@ describe("Pairing.Service", () => { const input = { token: invitation.token, requestID, - deviceName: PairingSchema.DeviceName.make("Shuv's iPhone"), + deviceName: DeviceName.make("Shuv's iPhone"), credential, } @@ -38,16 +38,16 @@ describe("Pairing.Service", () => { yield* pairing.redeem({ token: invitation.token, requestID, - deviceName: PairingSchema.DeviceName.make("Phone"), + deviceName: DeviceName.make("Phone"), credential, }) const changed = yield* pairing .redeem({ token: invitation.token, - requestID: PairingSchema.RequestID.make("6cf0f5d4-d96a-4909-a7f2-69416da670d6"), - deviceName: PairingSchema.DeviceName.make("Phone"), - credential: PairingSchema.DeviceCredential.make("scd_v1_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"), + requestID: RequestID.make("6cf0f5d4-d96a-4909-a7f2-69416da670d6"), + deviceName: DeviceName.make("Phone"), + credential: DeviceCredential.make("scd_v1_BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"), }) .pipe(Effect.flip) expect(changed._tag).toBe("PairingConflict") @@ -61,15 +61,15 @@ describe("Pairing.Service", () => { const firstDevice = yield* pairing.redeem({ token: first.token, requestID, - deviceName: PairingSchema.DeviceName.make("Phone"), + deviceName: DeviceName.make("Phone"), credential, }) - const secondCredential = PairingSchema.DeviceCredential.make("scd_v1_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC") + const secondCredential = DeviceCredential.make("scd_v1_CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC") const second = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) const secondDevice = yield* pairing.redeem({ token: second.token, - requestID: PairingSchema.RequestID.make("0fcfa724-fae1-4610-91bd-7cb78ec36f89"), - deviceName: PairingSchema.DeviceName.make("iPad"), + requestID: RequestID.make("0fcfa724-fae1-4610-91bd-7cb78ec36f89"), + deviceName: DeviceName.make("iPad"), credential: secondCredential, }) @@ -85,9 +85,9 @@ describe("Pairing.Service", () => { const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) const input = { token: invitation.token, - requestID: PairingSchema.RequestID.make("e3b12d77-d8c0-45ee-9942-694ab055b8ad"), - deviceName: PairingSchema.DeviceName.make("Concurrent Phone"), - credential: PairingSchema.DeviceCredential.make("scd_v1_EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"), + requestID: RequestID.make("e3b12d77-d8c0-45ee-9942-694ab055b8ad"), + deviceName: DeviceName.make("Concurrent Phone"), + credential: DeviceCredential.make("scd_v1_EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"), } const results = yield* Effect.all([pairing.redeem(input), pairing.redeem(input)], { concurrency: 2 }) @@ -102,15 +102,15 @@ describe("Pairing.Service", () => { const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) const first = pairing.redeem({ token: invitation.token, - requestID: PairingSchema.RequestID.make("de7bb697-5475-40d0-b56c-900bfcf64e30"), - deviceName: PairingSchema.DeviceName.make("First"), - credential: PairingSchema.DeviceCredential.make("scd_v1_FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), + requestID: RequestID.make("de7bb697-5475-40d0-b56c-900bfcf64e30"), + deviceName: DeviceName.make("First"), + credential: DeviceCredential.make("scd_v1_FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), }) const second = pairing.redeem({ token: invitation.token, - requestID: PairingSchema.RequestID.make("98089dad-ee55-49c7-831e-6e3c07238169"), - deviceName: PairingSchema.DeviceName.make("Second"), - credential: PairingSchema.DeviceCredential.make("scd_v1_GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"), + requestID: RequestID.make("98089dad-ee55-49c7-831e-6e3c07238169"), + deviceName: DeviceName.make("Second"), + credential: DeviceCredential.make("scd_v1_GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG"), }) const exits = yield* Effect.all([Effect.exit(first), Effect.exit(second)], { concurrency: 2 }) @@ -124,12 +124,12 @@ describe("Pairing.Service", () => { Effect.gen(function* () { const pairing = yield* Pairing.Service const first = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) - const firstRequest = PairingSchema.RequestID.make("7707d867-522d-42f5-8438-e2280d4822c4") - const firstCredential = PairingSchema.DeviceCredential.make("scd_v1_HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH") + const firstRequest = RequestID.make("7707d867-522d-42f5-8438-e2280d4822c4") + const firstCredential = DeviceCredential.make("scd_v1_HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH") yield* pairing.redeem({ token: first.token, requestID: firstRequest, - deviceName: PairingSchema.DeviceName.make("Original"), + deviceName: DeviceName.make("Original"), credential: firstCredential, }) const second = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) @@ -138,8 +138,8 @@ describe("Pairing.Service", () => { .redeem({ token: second.token, requestID: firstRequest, - deviceName: PairingSchema.DeviceName.make("Changed request"), - credential: PairingSchema.DeviceCredential.make("scd_v1_IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"), + deviceName: DeviceName.make("Changed request"), + credential: DeviceCredential.make("scd_v1_IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII"), }) .pipe(Effect.flip))._tag, ).toBe("PairingConflict") @@ -147,8 +147,8 @@ describe("Pairing.Service", () => { (yield* pairing .redeem({ token: second.token, - requestID: PairingSchema.RequestID.make("12332213-820a-49f7-8901-23187b2f8ec1"), - deviceName: PairingSchema.DeviceName.make("Changed credential"), + requestID: RequestID.make("12332213-820a-49f7-8901-23187b2f8ec1"), + deviceName: DeviceName.make("Changed credential"), credential: firstCredential, }) .pipe(Effect.flip))._tag, @@ -160,11 +160,11 @@ describe("Pairing.Service", () => { Effect.gen(function* () { const pairing = yield* Pairing.Service const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) - const secret = PairingSchema.DeviceCredential.make("scd_v1_JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ") + const secret = DeviceCredential.make("scd_v1_JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ") const result = yield* pairing.redeem({ token: invitation.token, - requestID: PairingSchema.RequestID.make("5724ec39-cb0b-4cd9-be63-ef51cc2f3468"), - deviceName: PairingSchema.DeviceName.make("Private"), + requestID: RequestID.make("5724ec39-cb0b-4cd9-be63-ef51cc2f3468"), + deviceName: DeviceName.make("Private"), credential: secret, }) const row = yield* (yield* Database.Service).db @@ -180,6 +180,32 @@ describe("Pairing.Service", () => { }), ) + it.effect("retains the invitation when durable enrollment fails", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const database = yield* Database.Service + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const input = { + token: invitation.token, + requestID: RequestID.make("2e7bb697-5475-40d0-b56c-900bfcf64e30"), + deviceName: DeviceName.make("Retry Phone"), + credential: DeviceCredential.make("scd_v1_NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN"), + } + + yield* database.db.run(` + CREATE TRIGGER pairing_device_fail_once + BEFORE INSERT ON pairing_device + BEGIN + SELECT RAISE(ABORT, 'simulated enrollment failure'); + END; + `) + expect(Exit.isFailure(yield* Effect.exit(pairing.redeem(input)))).toBe(true) + yield* database.db.run("DROP TRIGGER pairing_device_fail_once") + + expect(yield* pairing.redeem(input)).toEqual(expect.objectContaining({ deviceID: expect.any(String) })) + }), + ) + it.effect("invalidates outstanding invitations when the service restarts", () => Effect.gen(function* () { const before = yield* Pairing.make() @@ -187,9 +213,9 @@ describe("Pairing.Service", () => { const committed = yield* before.issue({ urls: ["https://shuvdev.example"] }) const committedInput = { token: committed.token, - requestID: PairingSchema.RequestID.make("46a5de7e-716e-4c1e-8d37-685f79de3712"), - deviceName: PairingSchema.DeviceName.make("Committed"), - credential: PairingSchema.DeviceCredential.make("scd_v1_MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"), + requestID: RequestID.make("46a5de7e-716e-4c1e-8d37-685f79de3712"), + deviceName: DeviceName.make("Committed"), + credential: DeviceCredential.make("scd_v1_MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"), } const committedDevice = yield* before.redeem(committedInput) const after = yield* Pairing.make() @@ -197,9 +223,9 @@ describe("Pairing.Service", () => { const error = yield* after .redeem({ token: invitation.token, - requestID: PairingSchema.RequestID.make("72fba88a-4921-418c-88c7-6a917509f77d"), - deviceName: PairingSchema.DeviceName.make("Restarted"), - credential: PairingSchema.DeviceCredential.make("scd_v1_KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"), + requestID: RequestID.make("72fba88a-4921-418c-88c7-6a917509f77d"), + deviceName: DeviceName.make("Restarted"), + credential: DeviceCredential.make("scd_v1_KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK"), }) .pipe(Effect.flip) expect(error._tag).toBe("PairingInvitationUnavailable") @@ -218,19 +244,19 @@ describe("Pairing.Service", () => { (yield* pairing .redeem({ token: invitation.token, - requestID: PairingSchema.RequestID.make("901cc739-b2f7-4df7-aa66-898c9afb1c79"), - deviceName: PairingSchema.DeviceName.make("Expired"), - credential: PairingSchema.DeviceCredential.make("scd_v1_LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"), + requestID: RequestID.make("901cc739-b2f7-4df7-aa66-898c9afb1c79"), + deviceName: DeviceName.make("Expired"), + credential: DeviceCredential.make("scd_v1_LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL"), }) .pipe(Effect.flip))._tag, ).toBe("PairingInvitationUnavailable") expect( (yield* pairing .redeem({ - token: "malformed" as PairingSchema.InvitationToken, - requestID: PairingSchema.RequestID.make("7b00ed9a-e7e8-4667-b88f-8d68ba93d050"), - deviceName: PairingSchema.DeviceName.make("Malformed"), - credential: "bad" as PairingSchema.DeviceCredential, + token: "malformed" as InvitationToken, + requestID: RequestID.make("7b00ed9a-e7e8-4667-b88f-8d68ba93d050"), + deviceName: DeviceName.make("Malformed"), + credential: "bad" as DeviceCredential, }) .pipe(Effect.flip))._tag, ).toBe("PairingInvalidRequest") diff --git a/packages/docs/openapi.json b/packages/docs/openapi.json index 3405e912fc99..5962123e25f1 100644 --- a/packages/docs/openapi.json +++ b/packages/docs/openapi.json @@ -11532,7 +11532,18 @@ ] }, "deviceName": { - "type": "string" + "type": "string", + "allOf": [ + { + "pattern": "^\\S[\\s\\S]*\\S$|^\\S$|^$" + }, + { + "minLength": 1 + }, + { + "maxLength": 80 + } + ] }, "credential": { "type": "string", @@ -11635,7 +11646,18 @@ ] }, "name": { - "type": "string" + "type": "string", + "allOf": [ + { + "pattern": "^\\S[\\s\\S]*\\S$|^\\S$|^$" + }, + { + "minLength": 1 + }, + { + "maxLength": 80 + } + ] }, "createdAt": { "type": "string", diff --git a/packages/protocol/src/capabilities.ts b/packages/protocol/src/capabilities.ts index 33fefb0f9760..18bb1651529f 100644 --- a/packages/protocol/src/capabilities.ts +++ b/packages/protocol/src/capabilities.ts @@ -1,69 +1,106 @@ export * as Capabilities from "./capabilities.js" -export type Capability = "administrator" | "mobile" -export type Entry = { readonly method: string; readonly path: string; readonly capability: Capability } +import { OpenApi } from "effect/unstable/httpapi" +import { ClientApi } from "./client.js" -// These exact method/path pairs are the runtime source of truth for device access. They mirror the -// ShuvKit operations used by OpenShuv; unlisted operations fail closed for device principals. -export const mobile = [ - ["GET", "/api/health"], - ["GET", "/api/server"], - ["GET", "/api/session"], - ["POST", "/api/session"], - ["GET", "/api/session/active"], - ["GET", "/api/session/:sessionID"], - ["DELETE", "/api/session/:sessionID"], - ["GET", "/api/session/:sessionID/message"], - ["GET", "/api/session/:sessionID/message/:messageID"], - ["POST", "/api/session/:sessionID/prompt"], - ["POST", "/api/session/:sessionID/interrupt"], - ["GET", "/api/session/:sessionID/context"], - ["GET", "/api/session/:sessionID/instructions/entries"], - ["POST", "/api/session/:sessionID/fork"], - ["POST", "/api/session/:sessionID/agent"], - ["POST", "/api/session/:sessionID/model"], - ["POST", "/api/session/:sessionID/rename"], - ["POST", "/api/session/:sessionID/command"], - ["POST", "/api/session/:sessionID/skill"], - ["POST", "/api/session/:sessionID/compact"], - ["POST", "/api/session/:sessionID/revert/stage"], - ["POST", "/api/session/:sessionID/revert/clear"], - ["POST", "/api/session/:sessionID/revert/commit"], - ["GET", "/api/experimental/session/:sessionID/log"], - ["GET", "/api/event"], - ["GET", "/api/permission/request"], - ["GET", "/api/permission/saved"], - ["DELETE", "/api/permission/saved/:id"], - ["GET", "/api/session/:sessionID/permission"], - ["GET", "/api/session/:sessionID/permission/:requestID"], - ["POST", "/api/session/:sessionID/permission/:requestID/reply"], - ["GET", "/api/question/request"], - ["GET", "/api/session/:sessionID/question"], - ["POST", "/api/session/:sessionID/question/:requestID/reply"], - ["POST", "/api/session/:sessionID/question/:requestID/reject"], - ["GET", "/api/form/request"], - ["GET", "/api/session/:sessionID/form"], - ["GET", "/api/session/:sessionID/form/:formID/state"], - ["POST", "/api/session/:sessionID/form/:formID/reply"], - ["POST", "/api/session/:sessionID/form/:formID/cancel"], - ["GET", "/api/project"], - ["GET", "/api/project/current"], - ["GET", "/api/project/:projectID/directories"], - ["GET", "/api/agent"], - ["GET", "/api/model"], - ["GET", "/api/model/default"], - ["GET", "/api/command"], - ["GET", "/api/skill"], - ["GET", "/api/vcs/status"], - ["GET", "/api/vcs/diff"], - ["GET", "/api/fs/read/*"], -] as const +export type Capability = "server" | "administrator" | "mobile" | "pairing-redemption" +export type Entry = { + readonly operationID: string + readonly method: string + readonly path: string + readonly capability: Capability +} + +// Capability declarations bind to endpoint-owned OpenAPI identifiers. Method and path are then +// derived from the Protocol contract, so authorization cannot drift from the routes it protects. +export const mobileOperationIDs = new Set([ + "v2.health.get", + "v2.server.get", + "v2.session.list", + "v2.session.create", + "v2.session.active", + "v2.session.get", + "v2.session.remove", + "v2.session.message", + "v2.message.list", + "v2.session.prompt", + "v2.session.interrupt", + "v2.session.context", + "v2.session.instructions.entry.list", + "v2.session.fork", + "v2.session.switchAgent", + "v2.session.switchModel", + "v2.session.rename", + "v2.session.command", + "v2.session.skill", + "v2.session.compact", + "v2.session.revert.stage", + "v2.session.revert.clear", + "v2.session.revert.commit", + "v2.session.log", + "v2.event.subscribe", + "v2.permission.request.list", + "v2.permission.saved.list", + "v2.permission.saved.remove", + "v2.session.permission.list", + "v2.session.permission.get", + "v2.session.permission.reply", + "v2.question.request.list", + "v2.session.question.list", + "v2.session.question.reply", + "v2.session.question.reject", + "v2.form.request.list", + "v2.session.form.list", + "v2.session.form.state", + "v2.session.form.reply", + "v2.session.form.cancel", + "v2.project.list", + "v2.project.current", + "v2.project.directories", + "v2.agent.list", + "v2.model.list", + "v2.model.default", + "v2.command.list", + "v2.skill.list", + "v2.vcs.status", + "v2.vcs.diff", + "v2.fs.read", +]) + +const publicOperationIDs = new Set(["v2.pairing.redeem"]) +const administratorOperationIDs = new Set([ + "v2.pairing.invitation.create", + "v2.pairing.device.list", + "v2.pairing.device.revoke", +]) -const patterns = mobile.map(([method, path]) => ({ - method, +export const routes: ReadonlyArray = Object.entries(OpenApi.fromApi(ClientApi).paths).flatMap( + ([path, operations]) => + Object.entries(operations).flatMap(([method, operation]) => { + if (method === "parameters" || !operation || !("operationId" in operation) || !operation.operationId) return [] + const capability = publicOperationIDs.has(operation.operationId) + ? "pairing-redemption" + : mobileOperationIDs.has(operation.operationId) + ? "mobile" + : administratorOperationIDs.has(operation.operationId) + ? "administrator" + : "server" + return [ + { + operationID: operation.operationId, + method: method.toUpperCase(), + path: path.replace(/\{([^/}]+)\}/g, ":$1"), + capability, + }, + ] + }), +) + +const patterns = routes.map((entry) => ({ + entry, pattern: new RegExp( "^" + - path + entry.path .split("/") .map((part) => part === "*" ? ".*" : part.startsWith(":") ? "[^/]+" : part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), @@ -73,18 +110,20 @@ const patterns = mobile.map(([method, path]) => ({ ), })) +export function classify(method: string, pathname: string) { + return patterns.find( + (candidate) => candidate.entry.method === method.toUpperCase() && candidate.pattern.test(pathname), + )?.entry.capability +} + export function allowsMobile(method: string, pathname: string) { - return patterns.some((entry) => entry.method === method.toUpperCase() && entry.pattern.test(pathname)) + return classify(method, pathname) === "mobile" } export function isPairingRedemption(method: string, pathname: string) { - return method.toUpperCase() === "POST" && pathname === "/api/pairing/redeem" + return classify(method, pathname) === "pairing-redemption" } -export function requiresAdministrator(pathname: string) { - return ( - pathname === "/api/pairing/invitation" || - pathname === "/api/pairing/device" || - pathname.startsWith("/api/pairing/device/") - ) +export function requiresAdministrator(method: string, pathname: string) { + return classify(method, pathname) === "administrator" } diff --git a/packages/protocol/test/capabilities.test.ts b/packages/protocol/test/capabilities.test.ts index cac4bee2307c..4bf98d5701af 100644 --- a/packages/protocol/test/capabilities.test.ts +++ b/packages/protocol/test/capabilities.test.ts @@ -4,12 +4,27 @@ import { Capabilities } from "../src/capabilities.js" import { ClientApi } from "../src/client.js" describe("mobile capability metadata", () => { - test("every governed method and path exists in the Protocol API", () => { + test("classifies every Protocol operation and derives every mobile route from the contract", () => { const paths = OpenApi.fromApi(ClientApi).paths - for (const [method, template] of Capabilities.mobile) { - const path = template.replace(/:([^/]+)/g, "{$1}") - expect(paths[path]?.[method.toLowerCase() as keyof (typeof paths)[string]]).toBeDefined() - } + const contract = Object.entries(paths).flatMap(([path, operations]) => + Object.entries(operations).flatMap(([method, operation]) => + method === "parameters" || !operation || !("operationId" in operation) || !operation.operationId + ? [] + : [{ method: method.toUpperCase(), path, operationID: operation.operationId }], + ), + ) + expect(Capabilities.routes).toHaveLength(contract.length) + expect( + Capabilities.routes.map((entry) => ({ + method: entry.method, + path: entry.path.replace(/:([^/]+)/g, "{$1}"), + operationID: entry.operationID, + })), + ).toEqual(contract) + expect(Capabilities.routes.every((entry) => entry.capability !== undefined)).toBe(true) + expect( + new Set(Capabilities.routes.filter((entry) => entry.capability === "mobile").map((entry) => entry.operationID)), + ).toEqual(Capabilities.mobileOperationIDs) }) test("classification is method-specific and fails closed", () => { @@ -19,4 +34,10 @@ describe("mobile capability metadata", () => { expect(Capabilities.isPairingRedemption("POST", "/api/pairing/redeem")).toBe(true) expect(Capabilities.isPairingRedemption("GET", "/api/pairing/redeem")).toBe(false) }) + + test("publishes the device name scalar bounds in the redemption contract", () => { + const request = JSON.stringify(OpenApi.fromApi(ClientApi).components.schemas?.["Pairing.RedeemRequest"]) + expect(request).toContain('"minLength":1') + expect(request).toContain('"maxLength":80') + }) }) diff --git a/packages/schema/src/pairing.ts b/packages/schema/src/pairing.ts index 8207e6c62ed4..a9a51e4c6368 100644 --- a/packages/schema/src/pairing.ts +++ b/packages/schema/src/pairing.ts @@ -26,7 +26,14 @@ export const RequestID = Schema.String.check( ).pipe(Schema.brand("Pairing.RequestID")) export type RequestID = typeof RequestID.Type -export const DeviceName = Schema.Trim.check(Schema.isMinLength(1)).pipe(Schema.brand("Pairing.DeviceName")) +export const DeviceName = Schema.Trimmed.check( + Schema.isMinLength(1), + Schema.makeFilter((value) => Array.from(value).length <= 80, { + expected: "a string with at most 80 Unicode scalar values", + meta: { _tag: "isMaxLength", maxLength: 80 }, + arbitrary: { constraint: { maxLength: 80 } }, + }), +).pipe(Schema.brand("Pairing.DeviceName")) export type DeviceName = typeof DeviceName.Type export const Invitation = Schema.Struct({ diff --git a/packages/schema/test/pairing.test.ts b/packages/schema/test/pairing.test.ts new file mode 100644 index 000000000000..44ae5c50d9f3 --- /dev/null +++ b/packages/schema/test/pairing.test.ts @@ -0,0 +1,13 @@ +import { describe, expect, test } from "bun:test" +import { Schema } from "effect" +import { Pairing } from "../src/pairing.js" + +describe("Pairing.DeviceName", () => { + test("accepts 80 Unicode scalar values", () => { + expect(String(Schema.decodeUnknownSync(Pairing.DeviceName)("😀".repeat(80)))).toBe("😀".repeat(80)) + }) + + test("rejects 81 Unicode scalar values", () => { + expect(() => Schema.decodeUnknownSync(Pairing.DeviceName)("😀".repeat(81))).toThrow() + }) +}) diff --git a/packages/server/src/middleware/authorization.ts b/packages/server/src/middleware/authorization.ts index 09ebe22eec8a..506e326ac046 100644 --- a/packages/server/src/middleware/authorization.ts +++ b/packages/server/src/middleware/authorization.ts @@ -65,13 +65,13 @@ export const authorizationLayer = Layer.effect( const principal = bearer ? yield* pairing.authenticate(bearer) : undefined if (principal?.type === "device") { if ( - Capabilities.requiresAdministrator(url.pathname) || + Capabilities.requiresAdministrator(request.method, url.pathname) || !Capabilities.allowsMobile(request.method, url.pathname) ) return yield* new ForbiddenError({ message: "Administrator access required" }) return yield* effect.pipe(Effect.provideService(Principal, principal)) } - if (!ServerAuth.required(config) && !Capabilities.requiresAdministrator(url.pathname)) + if (!ServerAuth.required(config) && !Capabilities.requiresAdministrator(request.method, url.pathname)) return yield* effect.pipe(Effect.provideService(Principal, { type: "unauthenticated", reason: "embedded" })) yield* HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)), diff --git a/packages/tui/src/component/dialog-pair.tsx b/packages/tui/src/component/dialog-pair.tsx index 3035b80e2488..b99a705b7836 100644 --- a/packages/tui/src/component/dialog-pair.tsx +++ b/packages/tui/src/component/dialog-pair.tsx @@ -1,6 +1,6 @@ import { TextAttributes } from "@opentui/core" import { useTerminalDimensions } from "@opentui/solid" -import { createMemo, createResource, createSignal, For, Show } from "solid-js" +import { createMemo, createResource, createSignal, For, onCleanup, Show } from "solid-js" import { renderUnicodeCompact } from "uqr" import { useSDK } from "../context/sdk" import { useTheme } from "../context/theme" @@ -12,20 +12,19 @@ export function DialogPair() { const dialog = useDialog() const dimensions = useTerminalDimensions() const { theme } = useTheme() - const [loadError, setLoadError] = createSignal() + const [actionError, setActionError] = createSignal() const [revoking, setRevoking] = createSignal() + const [now, setNow] = createSignal(Date.now()) dialog.setSize("large") dialog.setCentered(true) - const [invitation, invitationActions] = createResource(() => - sdk.api.pairing.invitation.create().catch((error) => { - setLoadError(error) - return undefined - }), - ) + const [invitation, invitationActions] = createResource(() => sdk.api.pairing.invitation.create()) const [devices, deviceActions] = createResource(() => sdk.api.pairing.device.list()) - const info = createMemo(() => invitation()) + const clock = setInterval(() => setNow(Date.now()), 1_000) + onCleanup(() => clearInterval(clock)) + const status = createMemo(() => invitationStatus(invitation(), invitation.error, invitation.loading, now())) + const info = createMemo(() => (status().type === "active" ? invitation() : undefined)) const horizontal = createMemo(() => dimensions().width >= 96) const content = () => { const value = info() @@ -76,7 +75,7 @@ export function DialogPair() { sdk.api.pairing.device .revoke({ deviceID: device.deviceID }) .then(() => deviceActions.refetch()) - .catch(setLoadError) + .catch(setActionError) .finally(() => setRevoking(undefined)) }} > @@ -102,8 +101,20 @@ export function DialogPair() { esc - {(error) => {errorMessage(error())}} - Loading server information...}> + {(error) => {errorMessage(error())}} + + Loading pairing invitation... + + + Pairing is unavailable: {errorMessage(invitation.error)} + + + This pairing invitation expired. Regenerate it to display a new QR code. + invitationActions.refetch()}> + Regenerate invitation + + + = 36} fallback={ @@ -121,3 +132,15 @@ export function DialogPair() { ) } + +export function invitationStatus( + invitation: { readonly expiresAt: string } | undefined, + error: unknown, + loading: boolean, + now: number, +) { + if (error) return { type: "unavailable" as const } + if (!invitation) return { type: loading ? ("loading" as const) : ("unavailable" as const) } + if (Date.parse(invitation.expiresAt) <= now) return { type: "expired" as const } + return { type: "active" as const } +} diff --git a/packages/tui/test/cli/tui/dialog-pair.test.ts b/packages/tui/test/cli/tui/dialog-pair.test.ts new file mode 100644 index 000000000000..a1b642d80c0e --- /dev/null +++ b/packages/tui/test/cli/tui/dialog-pair.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, test } from "bun:test" +import { invitationStatus } from "../../../src/component/dialog-pair" + +describe("pairing invitation status", () => { + test("expires an invitation at its deadline so stale QR content is hidden", () => { + const invitation = { expiresAt: "2026-07-14T20:00:00.000Z" } + expect(invitationStatus(invitation, undefined, false, Date.parse(invitation.expiresAt) - 1).type).toBe("active") + expect(invitationStatus(invitation, undefined, false, Date.parse(invitation.expiresAt)).type).toBe("expired") + }) + + test("distinguishes unavailable from loading", () => { + expect(invitationStatus(undefined, new Error("offline"), false, Date.now()).type).toBe("unavailable") + expect(invitationStatus(undefined, undefined, false, Date.now()).type).toBe("unavailable") + expect(invitationStatus(undefined, undefined, true, Date.now()).type).toBe("loading") + }) +}) diff --git a/specs/v2/mobile-server-pairing.md b/specs/v2/mobile-server-pairing.md new file mode 100644 index 000000000000..8cb81f5c4290 --- /dev/null +++ b/specs/v2/mobile-server-pairing.md @@ -0,0 +1,382 @@ +# Plan: Mobile-to-Server QR Pairing + +| Field | Value | +| ------ | ------------------------------------------------------------------- | +| Status | Reviewed against the post-M3 repositories; ready for implementation | +| Date | 2026-07-14 | +| Scope | Shuvcode V2, shuvkit, OpenShuv, and coordinated ClankerOne changes | + +## Goal + +Let an operator pair a mobile client with a Shuvcode V2 server by scanning a QR code without transferring the server's long-lived administrator password. + +Pairing must enroll a unique, persistent, revocable credential for each mobile device. A photographed or replayed invitation must stop working after its first successful redemption or a short expiration period. + +## Current State + +Upstream V2 already implements connection sharing in the CLI and TUI: + +- `shuvcode pair` starts or discovers the managed service. +- The client calls authenticated `GET /api/server` to discover usable URLs. +- The CLI and TUI render `{ urls, username, password }` as a QR code. +- The password is the managed server's persistent shared Basic-auth password. + +This is enough to prototype QR scanning, but it is not device pairing. Anyone who obtains the QR receives full access until the shared password is rotated. Rotation invalidates every client at once, and the server cannot identify or revoke one device. + +The V2 PTY connect-ticket implementation provides a useful precedent for short-lived, process-local, atomically consumed tokens. Pairing invitations can follow that pattern, but enrolled device credentials must be durable. + +The reviewed implementation baseline is now: + +| Project | Current baseline | Pairing-relevant state | +| ---------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Shuvcode | `integration-v2` after PR #334 (`dc1fb66264`; attachment contract commit `8f7b0d8dc3`) | Basic-only global authorization; `/api/server` returns runtime connection URLs; no pairing domain or authenticated principal | +| shuvkit | `0.1.5` (`bfac2a6`) pinned to Shuvcode `8f7b0d8dc3` | Swift keeps the password outside `ServerConfig`, but transport authentication is still implicit Basic; TypeScript embeds Basic fields in its client config | +| OpenShuv | `master` (`12e328b`) pinned to shuvkit `0.1.2` | Manual/Bonjour onboarding, Basic password in device-only Keychain, no QR reader, and broad ATS cleartext allowance | +| ClankerOne | M3 merged in PR #7 (`129db3e`); active `codex/project-directory-picker` at `9e797da` | Has attachment camera and VisionKit document-scanner/OCR flows, but no QR barcode scanner and no direct Shuvcode mobile credential path; coordinate pairing changes with its active project work | + +The M3 dependency sequence is complete, so the previous M2 coordination freeze no longer applies. Pairing starts from the current Shuvcode branch and shuvkit `0.1.5`; OpenShuv should move directly to the first immutable pairing release rather than taking an unrelated intermediate dependency-only change. + +## Decision + +Replace the credential-bearing QR payload with a versioned, one-time pairing invitation. Keep the existing administrator Basic credential for operator and compatibility use, and add per-device Bearer credentials for mobile clients. + +The initial QR envelope is: + +```json +{ + "v": 1, + "kind": "shuvcode.pair", + "urls": ["https://shuvdev.example"], + "token": "", + "expiresAt": "2026-07-14T20:00:00Z" +} +``` + +The envelope deliberately contains no administrator username or password. The invitation token is submitted in the redemption request body, never in a URL or query string. + +## Pairing Flow + +1. An authenticated operator requests a pairing invitation through the CLI or TUI. +2. The server creates a 32-byte cryptographically random, base64url-encoded single-use token with a three-minute lifetime. +3. The CLI or TUI renders the versioned envelope as a QR code. +4. The mobile app scans and validates the envelope locally. +5. The app shows the selected hostname, URL, and transport security state for confirmation. +6. The app allocates its stable local server ID, generates a redemption request UUID and a 32-byte base64url device credential, stores the invitation token and device credential in device-only Keychain plus non-secret pending metadata, and only then submits the credential, token, and a user-editable device name to the redemption endpoint. +7. The server atomically consumes the invitation, stores only the device-credential hash, and creates a device record. +8. The server returns the device ID. An exact retry with the same request ID, invitation, and credential reconciles to the same device. +9. The app verifies Bearer access, promotes the pending Keychain record to the active device credential, and deletes the invitation token only after reconciliation succeeds. +10. The operator can later list or revoke that device without affecting other clients. + +## API Surface + +| Method | Path | Authentication | Purpose | +| -------- | ------------------------------- | ------------------------- | ------------------------------- | +| `POST` | `/api/pairing/invitation` | Administrator Basic auth | Create a short-lived invitation | +| `POST` | `/api/pairing/redeem` | One-time invitation token | Enroll one device credential | +| `GET` | `/api/pairing/device` | Administrator Basic auth | List paired devices | +| `DELETE` | `/api/pairing/device/:deviceID` | Administrator Basic auth | Revoke one paired device | + +`POST /api/pairing/redeem` is the only route that bypasses ordinary server credentials. Possession and successful consumption of the invitation token authenticates that request. The exception must be exact-route and method constrained, following the PTY ticket exception rather than making the pairing group generally public. + +The public error contract is: + +| Error | HTTP | Meaning | +| ----------------------------------- | ---: | ------------------------------------------------------------------------------------------------- | +| `InvalidRequestError` | 400 | Malformed request, invalid UUID, name, token, or credential format | +| `UnauthorizedError` | 401 | Missing or invalid Basic/Bearer credentials on an authenticated route | +| `ForbiddenError` | 403 | An authenticated device principal attempted an administrator-only capability | +| `PairingConflictError` | 409 | A supposedly idempotent retry changed its request ID, invitation, or credential | +| `PairingInvitationUnavailableError` | 410 | Invitation is expired, unknown, restarted away, or consumed without a matching durable enrollment | +| `PairingDeviceNotFoundError` | 404 | Revocation referenced an unknown device ID | + +### Create invitation response + +```json +{ + "v": 1, + "kind": "shuvcode.pair", + "urls": ["https://shuvdev.example"], + "token": "", + "expiresAt": "2026-07-14T20:00:00Z" +} +``` + +### Redeem invitation request + +```json +{ + "token": "", + "requestID": "", + "deviceName": "Shuv's iPhone", + "credential": "" +} +``` + +### Redeem invitation response + +```json +{ + "deviceID": "device_..." +} +``` + +The response must not expose the stored credential hash or administrator credential. The server never stores the raw device secret and does not need to reproduce it after a response is lost. + +The device credential wire format is `scd_v1_` where the suffix decodes to exactly 32 bytes. The prefix gives clients and logs a non-secret credential-type discriminator without weakening the secret. Invitation tokens use the same 32-byte entropy but have no reusable credential prefix. + +Wire schemas require a canonical UUID `requestID`, a trimmed device name from 1 through 80 Unicode scalar values, the exact credential format above, and a 32-byte base64url invitation token. Invitation responses must remain below the 4 KiB scanner limit. Device list responses expose only `deviceID`, name, creation/update timestamps, and revocation state; they never expose request IDs or hashes. Revocation is idempotent for an existing device and returns not found for an unknown ID. + +### Redemption recovery + +The redemption operation must reconcile exact retries. A durable unique `request_id` and a hash of the invitation proof allow the server to return the same device record only when the request ID, invitation, and submitted credential hash match the original enrollment. + +The mobile client must distinguish: + +- A definite rejection before enrollment, which may safely return to scanning. +- A timeout before it is known whether the server committed, which must retain pending state and probe Bearer authentication. +- A successful Bearer probe after an ambiguous redemption, which proves enrollment completed even if the redemption response was lost. +- A retry, which must reuse the same request ID, credential, and selected server rather than redeeming through another advertised URL as a new device. + +Expired, restarted-away, and unknown invitations return `PairingInvitationUnavailableError` with HTTP 410 when there is no matching durable enrollment. An exact retry reconciles from the durable device row. Any durable uniqueness collision on request ID, invitation digest, or credential digest that does not match the complete committed tuple returns `PairingConflictError` with HTTP 409. This matches what the process-local invitation model can prove and avoids giving clients false precision. + +## Durable Model + +Add one durable `pairing_device` table. Field names follow the repository's snake_case Drizzle convention and its existing `time_created`/`time_updated` timestamp helpers. + +| Field | Purpose | +| ----------------- | -------------------------------------------------------------- | +| `id` | Stable public device identifier | +| `request_id` | Unique client request ID for exact-retry reconciliation | +| `name` | Operator-visible device label | +| `credential_hash` | Hash of the client-generated secret; raw value is never stored | +| `invitation_hash` | Hash binding an exact retry to its enrollment invitation | +| `time_created` | Enrollment timestamp from the shared timestamp helper | +| `time_updated` | Last durable record mutation from the shared timestamp helper | +| `time_revoked` | Nullable revocation timestamp | + +Invitation state may remain process-local for the first implementation. The cache stores the invitation digest as its key rather than retaining the raw token, is capped at 1,024 outstanding invitations, and atomically consumes entries. Restarting the server invalidates outstanding QR codes, which is safe and understandable. Do not persist invitations unless a concrete need for restart survival emerges. + +`request_id`, `credential_hash`, and `invitation_hash` each require a unique index. Device credentials and invitations are high-entropy random values, so V1 stores their SHA-256 digests as fixed-length lowercase hex. Authentication hashes the presented credential and performs the indexed digest lookup; any in-memory digest comparison uses constant-time equality. A slow password KDF is unnecessary for 256-bit generated secrets and would add cost without improving resistance to offline guessing. + +Do not add `last_used_at` in V1. Updating it on normal requests would put a write on the authentication path or require a new coalescing subsystem before there is a concrete product use for the data. + +## Authentication Changes + +The server authorization layer will authenticate either: + +- The existing configured administrator Basic credential. +- A Bearer device credential whose hash matches a non-revoked device record. + +Successful authentication must produce an explicit request principal such as `administrator` or `device(deviceID)`, not only a boolean result. Pairing-management routes then apply a separate administrator-authorization middleware and reject device principals. + +Authentication and authorization remain separate modules. The global authentication middleware validates Basic or Bearer credentials and supplies the principal. Protocol-owned capability metadata declares whether an endpoint accepts `mobile` or requires `administrator`, and server middleware enforces it. A device principal is fail-closed by default: V1 grants `mobile` only to the exact methods and paths governed for OpenShuv in shuvkit's `contract/contract-usage.json`. Credential, integration, plugin, MCP, debug, PTY, shell, project-copy, pairing-management, and any new unclassified endpoints remain administrator-only. The contract checker must assert the mobile allowlist so a newly generated endpoint cannot silently become device-accessible. + +### Mobile capability source of truth + +Protocol endpoint metadata is the runtime source of truth. During the shuvkit publication phase, add a `mobileCapabilities` array of exact `{ method, path }` pairs to `contract/contract-usage.json` as its governed mirror. Seed it from the Swift `OpenCodeClient` methods used by OpenShuv plus the global `/api/event` and per-session `/api/experimental/session/{id}/log` SSE streams. Do not authorize an entire group or all methods on a path merely because one operation is mobile-facing. + +CI must prove that every governed mobile pair exists in OpenAPI, carries `mobile` metadata in Protocol, and has a characterized ShuvKit request. It must also prove that every Swift request OpenShuv can issue is classified as mobile or intentionally rejected. Basic administrator requests bypass this device-capability restriction after successful authentication; Bearer device requests do not. + +The exact-method `POST /api/pairing/redeem` exception reaches its handler without a principal; the handler authenticates by consuming or reconciling the invitation. Embedded or otherwise unauthenticated servers must not issue invitations or manage paired devices. + +Credential verification must: + +- Require at least 256 bits of cryptographically random input for client-generated device secrets. +- Store only a SHA-256 digest of each generated secret. +- Use the indexed fixed-length digest for lookup and constant-time equality for any comparison performed in memory. +- Avoid logging invitation tokens, device credentials, or authorization headers. +- Reject malformed Bearer values before database lookup and reject revoked records immediately. +- Return both Basic and Bearer authentication challenges where the HTTP stack permits multiple `WWW-Authenticate` values. + +### Mobile authentication representation + +ShuvKit Swift must model transport authentication explicitly as `none`, `basic`, or `deviceBearer`, with the secret supplied separately from non-secret server metadata. The Swift client must retain its existing `init(config:password:session:)` Basic initializer as a compatibility surface while a designated initializer accepts the explicit authentication kind and opaque credential. + +OpenShuv must persist a non-secret authentication-kind discriminator alongside each server and keep the opaque secret in Keychain. Decoding an older saved `ServerConfig` with no discriminator must infer Basic when a username exists and unauthenticated access otherwise; a required new field would cause the current cache decoder to silently drop existing servers. + +Pairing redemption must use a dedicated credential-free pairing client path that cannot accidentally attach an existing Basic or Bearer header. Do not add an `omitAuthentication` boolean to the general request helper; the separate interface makes the security property testable. + +## QR and Transport Rules + +- Reject unknown `kind` values and unsupported versions. +- Reject scanned payloads larger than 4 KiB before decoding JSON. +- Accept only `http` and `https` server URLs. +- Reject userinfo, query strings, fragments, and unexpected paths in pairing URLs. +- Probe advertised URLs without sending the invitation token; an authenticated server's HTTP 401 is sufficient to establish reachability for selection. +- Prefer HTTPS when more than one advertised URL is reachable. +- Pairing V1 permits HTTPS and loopback HTTP only. OpenShuv's existing manual onboarding may retain its current cleartext warning during migration, but a scanned invitation containing non-loopback HTTP is rejected. +- Never place an invitation or device credential in a custom URL, query string, analytics event, crash report, or pasteboard automatically. +- Display the selected hostname and transport security state before redemption. +- Cap outstanding process-local invitations at 1,024. V1 relies on 256-bit invitation entropy rather than proxy-sensitive IP attempt tracking. +- Bound parallel reachability probes and persist only the selected URL; the complete advertised list remains informational. + +## Coordination Baseline + +The old ClankerOne M2 freeze is closed. M3 merged in dependency order as Shuvcode PR #334, shuvkit PR #3 and release `0.1.5`, then ClankerOne PR #7. Pairing implementation and coordination may now proceed across all four repositories from this hub. + +Keep the same publication discipline: + +1. Land and verify Shuvcode pairing first without changing shuvdev. +2. Advance shuvkit's exact Shuvcode pin, OpenAPI snapshot, contract declarations, tests, and immutable tag from the clean `0.1.5` baseline. +3. Update OpenShuv to that immutable pairing tag and complete simulator, hosted-CI, and signed-device gates. +4. Advance ClankerOne's shuvkit submodule to the same governed tag, update deployment and operations material where the shared topology changes, and rerun its bridge, iOS, and live compatibility gates. +5. Change shuvdev's listener/advertisement topology only during the explicit live-rollout phase. + +ClankerOne work is coordinated here like the other projects. Preserve and sequence its active branch normally, but do not treat the repository as immutable or excluded from dependency, deployment, documentation, scanner-sharing, or compatibility changes required by pairing. + +This shared coordination thread is the control plane for cross-project ordering and gates; repository-specific implementation remains in the dedicated Shuvcode, shuvkit, OpenShuv, and ClankerOne execution threads. + +## Implementation Plan + +### Phase 1: Shuvcode pairing domain + +1. Add pairing wire schemas in `packages/schema`. +2. Add `pairing/sql.ts`, the `pairing_device` model, unique indexes, and a generated migration in `packages/core`. +3. Add one process-global `Pairing.Service` module in `packages/core`. Its external interface is limited to issuing invitations, redeeming enrollment, authenticating a device credential, listing devices, and revoking a device. It hides the invitation cache, per-token redemption serialization, hashing, and database implementation, and tests exercise behavior through this interface. +4. The module must: + - issue process-local expiring invitations; + - atomically consume each invitation once; + - validate and hash client-generated per-device credentials; + - serialize competing redemption attempts for the same invitation so one exact retry cannot transiently race another into a false conflict; + - reconcile committed retries before consulting the process-local invitation cache; and + - list and revoke durable device records. +5. Add the pairing endpoints, pairing errors, principal context, and capability metadata to `packages/protocol` without introducing a Protocol-to-Core dependency. +6. Add handlers and narrowly scoped exact-method redemption bypass in `packages/server`. Replace the current URL-only PTY exception check with explicit request classifiers so `POST /api/pairing/redeem` is the only pairing route that can reach a handler without a principal. +7. Extend server authentication to recognize device Bearer credentials, produce an authenticated principal, and enforce protocol-declared capabilities while preserving administrator Basic authentication and the existing PTY ticket behavior. +8. Add an independently validated advertised-URL setting to the server/managed-service configuration. Bind addresses and advertised addresses are different concerns: shuvdev must continue binding Shuvcode to `127.0.0.1:4096` while `/api/server` and invitations advertise its tailnet HTTPS URL. +9. When one or more advertised URLs are configured, `/api/server` and invitation creation return that validated list instead of appending runtime bind URLs. Without the setting, retain today's runtime URL discovery. Advertised values follow the same no-userinfo/query/fragment/path and HTTPS-or-loopback rules as scanned envelopes. +10. Run `bun run generate` from `packages/client` after the public Protocol and Server `HttpApi` are final. +11. Do not edit generated client directories directly. + +### Phase 2: Operator QR surfaces + +1. Update `shuvcode pair` to create an invitation and render the versioned envelope. +2. Stop printing or encoding the administrator password in pairing output. Human-readable output contains only advertised URLs and invitation expiry. +3. Update the TUI Pair dialog to request an invitation and display its expiration. +4. Provide clear states for expiration, server unavailability, and regeneration. +5. Preserve the current localhost warning and multi-URL display, but direct remote operators to configure an advertised URL rather than changing a loopback bind to `0.0.0.0` when a reverse proxy or Tailscale Serve is the intended ingress. +6. Add `shuvcode device list` and `shuvcode device revoke ` administrator commands, and expose the same list/revoke actions in the TUI Pair dialog so per-device revocation is operable rather than API-only. + +### Phase 3: shuvkit support + +1. Start from the immutable `0.1.5` baseline and add a strict `PairingEnvelope` decoder with version and kind discrimination. +2. Add a dedicated pairing URL validator and deterministic, bounded reachable-URL selection; do not reuse the permissive manual `ServerURLNormalizer`. +3. Add a small pairing module whose interface decodes an envelope, selects an eligible URL, and redeems one prepared enrollment. Keep URL probing and credential-free transport inside the module so OpenShuv does not duplicate them. +4. Add Bearer transport support to the Swift client. +5. Add a non-secret `none`/`basic`/`deviceBearer` authentication kind with backward-compatible decoding while keeping secrets outside `ServerConfig`. +6. Add typed pairing errors and map invitation-unavailable, conflicting-retry, malformed, cleartext, and unsupported-version responses into actionable Swift errors. +7. After the Shuvcode API is stable, update the exact Shuvcode pin and OpenAPI snapshot, then add all pairing paths, operation-shape checks, error schemas, capability declarations, fixtures, and live characterization. +8. Run `contract/check-contract.sh` to prove the declared shared-client contract still matches Shuvcode; this checker does not discover undeclared new APIs automatically. +9. Defer TypeScript pairing APIs and its existing `ServerConfig` authentication refactor until a concrete TypeScript consumer requires them. ClankerOne currently consumes Shuvcode through its bridge rather than redeeming a Shuvcode mobile credential, but its governed dependency may still advance with the shared release. + +Admin invitation creation and device list/revoke remain in Shuvcode's generated clients, CLI, and TUI. ShuvKit's first mobile surface needs only envelope parsing, URL selection, redemption, and Bearer transport. + +### Phase 4: OpenShuv onboarding + +1. Add `NSCameraUsageDescription`, regenerate the checked-in Xcode project metadata, and add a QR scanner entry point to Add Server. +2. Parse and validate the envelope before making a network request. +3. Show a confirmation screen with hostname, selected URL, expiry, and transport state. Scanned non-loopback cleartext is rejected rather than offered as a confirmable warning. +4. Allocate the stable local server identity, request ID, and device credential before redemption. Store the invitation token and device credential in a versioned pending Keychain record using the existing device-only accessibility policy; store only URL, expiry, request ID, device name, and local server ID in recoverable non-secret pending metadata. +5. Redeem or reconcile, verify authenticated server access, then atomically promote the Keychain entry to the active device credential and save non-secret server metadata with `deviceBearer` authentication. +6. Delete pending secrets after a definite rejection or successful promotion; retain recoverable pending state after ambiguous network failure and resume it on next launch while the invitation may still reconcile. +7. Rename password-specific OpenShuv Keychain APIs to opaque credential terminology and introduce versioned secret records without changing device-only accessibility or breaking the existing plain-string Basic entries. +8. Keep manual URL and Basic credential entry as a fallback during migration. +9. Add recovery UX for invitation-unavailable, conflicting-retry, unreachable, malformed, cleartext, and unsupported-version failures. +10. Put camera access behind a scanner abstraction or injectable scanned-payload path so parser and onboarding tests do not require Simulator camera input. + +### Phase 5: Migration and rollout + +1. Preserve Shuvcode on `127.0.0.1:4096`, ClankerOne on `127.0.0.1:8787`, and its attachment handoff on `127.0.0.1:8788`. +2. Add a dedicated tailnet-only Tailscale Serve HTTPS listener at port `10001` forwarding to Shuvcode `127.0.0.1:4096`; keep ClankerOne on HTTPS port `10000`. Do not expose port 8788. +3. Update the ClankerOne-owned deployment and operations material for the additional tailnet listener, configure Shuvcode's advertised URL as `https://shuvdev.tail586a6d.ts.net:10001` while retaining its loopback bind, and deploy the coordinated server changes without collapsing the two credential domains. +4. Verify administrator Basic authentication still works for existing tools. +5. Run ClankerOne's package, bridge health, session, governed attachment, iOS, and live smoke gates against its updated shuvkit pin and the replaced Shuvcode process. +6. Pair one development device and verify reconnect across app and server restarts. +7. Revoke that device and prove subsequent requests fail without affecting other clients. +8. Ship the OpenShuv build for real-device dogfooding. +9. Keep the legacy credential-bearing QR decoder out of production unless a temporary compatibility decision is made explicitly. + +## Verification Gates + +### Shuvcode + +- Pairing invitation can be consumed exactly once under concurrent redemption. +- An exact redemption retry reconciles to one device, while a changed request ID, credential, or invitation fails closed. +- Concurrent exact retries serialize to one device and one consistent response; competing non-identical redemption loses without creating a second row. +- A lost redemption response can be recovered using retained client credential state and an authenticated probe. +- Expired, unknown, malformed, and previously consumed invitations fail. +- Server restart invalidates process-local invitations. +- Client-generated device secrets are accepted only during redemption and never returned or stored raw. +- Revoked credentials fail authentication immediately. +- One device can be revoked without invalidating another. +- Device credentials cannot create invitations, list devices, or revoke devices. +- Device credentials can access only the protocol-declared mobile capability allowlist; administrator-only and unclassified routes return forbidden. +- Unauthenticated and embedded servers cannot expose pairing management. +- Existing administrator Basic authentication remains compatible. +- Unauthenticated access to every route except exact pairing redemption remains rejected. +- Generated promise and Effect clients compile against the final API. +- Loopback bind configuration and public advertised URL configuration are independently validated and tested. +- Run tests and `bun typecheck` from the affected package directories, never the repository root. + +### shuvkit and OpenShuv + +- Envelope decoding rejects oversized, malformed, unknown-kind, and unsupported-version input. +- Reachable URL selection behaves deterministically with multiple advertised URLs. +- Scanned non-loopback cleartext is rejected; loopback HTTP is visibly identified. +- The active post-pairing Keychain record contains only the device credential; the temporary invitation token is removed after successful promotion. +- A process termination before or after the redemption response can recover from the versioned pending Keychain record without storing either secret in SQLite or UserDefaults. +- Legacy saved servers decode with the same Basic or unauthenticated behavior after the authentication-kind migration. +- Expired and replayed QR codes produce actionable UI errors. +- An injected scanner payload can drive parser and onboarding tests without camera hardware. +- Manual server onboarding continues to work. +- Package tests, app-level simulator build, hosted CI, and a signed-device build pass before rollout is considered complete. + +### Live shuvdev + +- `GET /api/server` advertises `https://shuvdev.tail586a6d.ts.net:10001` while the Shuvcode process remains bound only to `127.0.0.1:4096`. +- Tailscale Serve keeps HTTPS 10000 mapped to ClankerOne 8787 and adds HTTPS 10001 mapped to Shuvcode 4096; port 8788 remains unadvertised. +- Pairing works against the systemd-managed service through the same wrapper and XDG configuration used in production. +- ClankerOne's Basic-auth bridge path and loopback attachment handoff still pass their M3 compatibility smoke after its governed shuvkit update and the Shuvcode replacement. +- Logs contain no invitation token, device secret, administrator password, or authorization header. +- A real device can reconnect after both app termination and server restart. + +## ClankerOne Integration + +ClankerOne is managed as part of this multi-project plan. Its repository may receive dependency, deployment, documentation, shared scanner, test, or compatibility changes when they are required to keep the complete system aligned. + +Its iOS app authenticates to the ClankerOne bridge with a static Bearer token rather than connecting directly to Shuvcode. A future ClankerOne pairing design may reuse the versioned envelope and scanner components, but the bridge must issue and redeem its own credential type. Shuvcode must not issue credentials that bypass the bridge architecture. + +Keep three concepts distinct in schemas, storage, UI copy, and operational docs: + +- A Shuvcode authenticated-device credential issued through this plan. +- A ClankerOne bridge credential accepted by the bridge. +- An APNs device-registration token used only for push delivery. + +ClankerOne M3 now provides camera capture and a VisionKit document scanner with OCR for attachments. That implementation does not recognize QR barcodes and is not currently packaged as a shared module. OpenShuv owns the first Shuvcode QR onboarding flow, but reusable scanner infrastructure may be deliberately moved into shuvkit or adopted by ClankerOne when doing so creates a real shared seam. + +The architectural distinction is still mandatory: ClankerOne's phone authenticates to its bridge, not directly to Shuvcode. If ClankerOne gains QR onboarding, the bridge issues and redeems its own credential even if it shares envelope and scanner modules with OpenShuv. + +## Non-Goals + +- Replacing administrator Basic authentication in the first release. +- General OAuth, passkeys, cloud accounts, or cross-server identity federation. +- Persisting unused invitations across server restarts. +- Pairing over Bluetooth or peer-to-peer transport. +- Automatically trusting every URL contained in a scanned QR code. +- Changing V1 code under `packages/opencode`. +- Replacing ClankerOne's bridge credential with a Shuvcode device credential. +- Recording per-request device usage timestamps in V1. +- Adding a durable server fingerprint or public-key identity in the first envelope; V1 confirmation shows the selected HTTPS hostname and transport state. + +## Resolved Implementation Decisions + +1. Device credentials and invitations contain 32 random bytes; the server stores fixed-length SHA-256 hex digests and never raw values. +2. V1 does not add a durable server fingerprint. Confirmation uses the selected HTTPS hostname and transport state. +3. Scanned pairing permits HTTPS and loopback HTTP only. Non-loopback cleartext remains available solely through the existing manual onboarding path during migration. +4. V1 omits `last_used_at` rather than introducing writes or coalescing on the authentication path. +5. Device principals are fail-closed and receive only the exact OpenShuv methods and paths declared in shuvkit's governed contract; explicit protocol capability metadata and contract checks enforce the set. +6. shuvdev keeps all processes loopback-only and adds Tailscale Serve HTTPS 10001 for direct Shuvcode access alongside ClankerOne HTTPS 10000. +7. Server bind addresses and advertised URLs are separate configuration. Pairing never asks an operator to widen the Shuvcode bind merely to publish a reverse-proxy address. +8. Pending enrollment secrets are written to device-only Keychain before redemption. Non-secret recovery metadata may be durable elsewhere, and promotion to an active credential occurs only after Bearer verification. + +These decisions are part of the V1 contract and must be reflected in schemas and tests before generated clients are published. From 4ef996e75040db70389246713d35e641e1589297 Mon Sep 17 00:00:00 2001 From: shuv Date: Tue, 14 Jul 2026 16:38:14 -0700 Subject: [PATCH 3/7] fix(pairing): align final security seams --- packages/core/src/pairing.ts | 94 +++++++++++------- packages/core/test/pairing.test.ts | 67 ++++++++++++- .../server/src/middleware/authentication.ts | 86 +++++++++++++++++ .../server/src/middleware/authorization.ts | 96 +++++-------------- packages/server/test/pairing.test.ts | 20 +++- 5 files changed, 254 insertions(+), 109 deletions(-) create mode 100644 packages/server/src/middleware/authentication.ts diff --git a/packages/core/src/pairing.ts b/packages/core/src/pairing.ts index 9ae060afd135..3e6d06cbc7f5 100644 --- a/packages/core/src/pairing.ts +++ b/packages/core/src/pairing.ts @@ -1,10 +1,12 @@ export * as Pairing from "./pairing" import { asc, eq, or } from "drizzle-orm" -import { Context, Data, Duration, Effect, Layer, Schema, Semaphore } from "effect" +import { timingSafeEqual } from "node:crypto" +import { Context, Data, Duration, Effect, Layer, Schema } from "effect" import { Pairing } from "@opencode-ai/schema/pairing" import { Database } from "./database/database" import { makeGlobalNode } from "./effect/app-node" +import { KeyedMutex } from "./effect/keyed-mutex" import { PairingDeviceTable } from "./pairing/sql" import { Hash } from "./util/hash" @@ -53,7 +55,7 @@ export class Service extends Context.Service()("@opencode/Pa export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => Effect.gen(function* () { const database = yield* Database.Service - const lock = Semaphore.makeUnsafe(1) + const redemptionLocks = KeyedMutex.makeUnsafe() const invitations = new Map() const rowDevice = (row: typeof PairingDeviceTable.$inferSelect): Pairing.Device => ({ @@ -71,33 +73,29 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => catch: () => new InvalidRequest({ message: "Invalid advertised pairing URL" }), }) if (urls.length === 0) return yield* new InvalidRequest({ message: "No pairing URL is available" }) - return yield* lock.withPermit( - Effect.gen(function* () { - const now = Date.now() - for (const [digest, invitation] of invitations) { - if (invitation.expiresAt <= now) invitations.delete(digest) - } - if (invitations.size >= capacity) - return yield* new CapacityExceeded({ message: "Too many outstanding pairing invitations" }) - const token = Pairing.InvitationToken.make( - Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("base64url"), - ) - const expiresAt = now + Duration.toMillis(Duration.fromInputUnsafe(ttl)) - invitations.set(Hash.sha256(token), { expiresAt }) - const result = { - v: 1 as const, - kind: "shuvcode.pair" as const, - urls, - token, - expiresAt: new Date(expiresAt).toISOString(), - } - if (Buffer.byteLength(JSON.stringify(result)) > 4_096) { - invitations.delete(Hash.sha256(token)) - return yield* new InvalidRequest({ message: "Pairing invitation exceeds the scanner size limit" }) - } - return result - }), + const now = Date.now() + for (const [digest, invitation] of invitations) { + if (invitation.expiresAt <= now) invitations.delete(digest) + } + if (invitations.size >= capacity) + return yield* new CapacityExceeded({ message: "Too many outstanding pairing invitations" }) + const token = Pairing.InvitationToken.make( + Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString("base64url"), ) + const expiresAt = now + Duration.toMillis(Duration.fromInputUnsafe(ttl)) + invitations.set(Hash.sha256(token), { expiresAt }) + const result = { + v: 1 as const, + kind: "shuvcode.pair" as const, + urls, + token, + expiresAt: new Date(expiresAt).toISOString(), + } + if (Buffer.byteLength(JSON.stringify(result)) > 4_096) { + invitations.delete(Hash.sha256(token)) + return yield* new InvalidRequest({ message: "Pairing invitation exceeds the scanner size limit" }) + } + return result }), redeem: Effect.fn("Pairing.redeem")(function* (input) { if (!Schema.is(Pairing.DeviceCredential)(input.credential)) @@ -106,7 +104,7 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => return yield* new InvalidRequest({ message: "Invalid device name" }) const invitationHash = Hash.sha256(input.token) const credentialHash = Hash.sha256(input.credential) - return yield* lock.withPermit( + return yield* redemptionLocks.withLock(invitationHash)( Effect.gen(function* () { const existing = yield* database.db .select() @@ -123,8 +121,8 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => if (existing) { if ( existing.request_id === input.requestID && - existing.invitation_hash === invitationHash && - existing.credential_hash === credentialHash + digestEqual(existing.invitation_hash, invitationHash) && + digestEqual(existing.credential_hash, credentialHash) ) return { deviceID: existing.id } return yield* new Conflict({ message: "Pairing redemption does not match the committed enrollment" }) @@ -136,7 +134,7 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => return yield* new InvitationUnavailable({ message: "Pairing invitation is unavailable" }) } const deviceID = Pairing.DeviceID.create() - yield* database.db + const stored = yield* database.db .transaction((tx) => tx .insert(PairingDeviceTable) @@ -149,7 +147,31 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => }) .run(), ) - .pipe(Effect.orDie) + .pipe(Effect.orDie, Effect.exit) + if (stored._tag === "Failure") { + const committed = yield* database.db + .select() + .from(PairingDeviceTable) + .where( + or( + eq(PairingDeviceTable.request_id, input.requestID), + eq(PairingDeviceTable.invitation_hash, invitationHash), + eq(PairingDeviceTable.credential_hash, credentialHash), + ), + ) + .get() + .pipe(Effect.orDie) + if (!committed) return yield* Effect.failCause(stored.cause) + if ( + committed.request_id === input.requestID && + digestEqual(committed.invitation_hash, invitationHash) && + digestEqual(committed.credential_hash, credentialHash) + ) { + invitations.delete(invitationHash) + return { deviceID: committed.id } + } + return yield* new Conflict({ message: "Pairing redemption conflicts with another enrollment" }) + } invitations.delete(invitationHash) return { deviceID } }), @@ -196,3 +218,9 @@ export const make = (ttl: Duration.Input = DEFAULT_TTL, capacity = CAPACITY) => const layer = Layer.effect(Service, make()) export const node = makeGlobalNode({ service: Service, layer, deps: [Database.node] }) + +function digestEqual(left: string, right: string) { + const leftBytes = Buffer.from(left, "hex") + const rightBytes = Buffer.from(right, "hex") + return leftBytes.length === rightBytes.length && timingSafeEqual(leftBytes, rightBytes) +} diff --git a/packages/core/test/pairing.test.ts b/packages/core/test/pairing.test.ts index bdf34a5d73e6..b314f57f91c1 100644 --- a/packages/core/test/pairing.test.ts +++ b/packages/core/test/pairing.test.ts @@ -1,5 +1,5 @@ import { describe, expect } from "bun:test" -import { Duration, Effect, Exit, Layer } from "effect" +import { Deferred, Duration, Effect, Exit, Fiber, Layer } from "effect" import { eq } from "drizzle-orm" import { Pairing } from "@opencode-ai/core/pairing" import { PairingDeviceTable } from "@opencode-ai/core/pairing/sql" @@ -96,6 +96,71 @@ describe("Pairing.Service", () => { }), ) + it.live("keeps issuance independent while an unrelated redemption waits on storage", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const database = yield* Database.Service + const invitation = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const transactionStarted = yield* Deferred.make() + const releaseTransaction = yield* Deferred.make() + const transaction = yield* database.db + .transaction(() => + Deferred.succeed(transactionStarted, undefined).pipe(Effect.andThen(Deferred.await(releaseTransaction))), + ) + .pipe(Effect.forkScoped) + yield* Deferred.await(transactionStarted) + const redemption = yield* pairing + .redeem({ + token: invitation.token, + requestID: RequestID.make("f94cd2d0-40be-4d3b-b876-4bfc31f70027"), + deviceName: DeviceName.make("Independent Phone"), + credential: DeviceCredential.make("scd_v1_OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"), + }) + .pipe(Effect.forkScoped) + yield* Effect.yieldNow + + expect(yield* pairing.issue({ urls: ["https://shuvdev.example"] }).pipe(Effect.timeout("100 millis"))).toEqual( + expect.objectContaining({ kind: "shuvcode.pair" }), + ) + yield* Deferred.succeed(releaseTransaction, undefined) + yield* Fiber.join(transaction) + expect((yield* Fiber.join(redemption)).deviceID).toStartWith("device_") + }), + ) + + it.effect("redeems unrelated invitations concurrently without cross-token interference", () => + Effect.gen(function* () { + const pairing = yield* Pairing.Service + const first = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const second = yield* pairing.issue({ urls: ["https://shuvdev.example"] }) + const devices = yield* Effect.all( + [ + pairing.redeem({ + token: first.token, + requestID: RequestID.make("716cb8b5-6e73-4584-b65b-47e2f6ffde90"), + deviceName: DeviceName.make("First Independent Phone"), + credential: DeviceCredential.make("scd_v1_PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"), + }), + pairing.redeem({ + token: second.token, + requestID: RequestID.make("67fd50f9-410c-48f1-a8bb-54f61349870a"), + deviceName: DeviceName.make("Second Independent Phone"), + credential: DeviceCredential.make("scd_v1_QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ"), + }), + ], + { concurrency: 2 }, + ) + + expect(devices[0].deviceID).not.toBe(devices[1].deviceID) + expect(yield* pairing.list()).toEqual( + expect.arrayContaining([ + expect.objectContaining({ name: "First Independent Phone" }), + expect.objectContaining({ name: "Second Independent Phone" }), + ]), + ) + }), + ) + it.effect("fails competing concurrent redemptions closed after creating one device", () => Effect.gen(function* () { const pairing = yield* Pairing.Service diff --git a/packages/server/src/middleware/authentication.ts b/packages/server/src/middleware/authentication.ts new file mode 100644 index 000000000000..6a8bc1a98ab6 --- /dev/null +++ b/packages/server/src/middleware/authentication.ts @@ -0,0 +1,86 @@ +export * as Authentication from "./authentication" + +import { Pairing } from "@opencode-ai/core/pairing" +import { UnauthorizedError } from "@opencode-ai/protocol/errors" +import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty" +import { Capabilities } from "@opencode-ai/protocol/capabilities" +import { Principal, type PrincipalInfo } from "@opencode-ai/protocol/middleware/authorization" +import { Context, Effect, Encoding, Layer, Redacted } from "effect" +import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" +import { ServerAuth } from "../auth" + +const AUTH_TOKEN_QUERY = "auth_token" +const WWW_AUTHENTICATE = 'Basic realm="Secure Area", Bearer realm="Shuvcode Device"' + +export interface Interface { + readonly withPrincipal: ( + effect: Effect.Effect, + ) => Effect.Effect +} + +export class Service extends Context.Service()("@opencode/ServerAuthentication") {} + +export const layer = Layer.effect( + Service, + Effect.gen(function* () { + const config = yield* ServerAuth.Config + const pairing = yield* Pairing.Service + return Service.of({ + withPrincipal: (effect) => + Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest + const url = new URL(request.url, "http://localhost") + if (hasPtyConnectTicketURL(url)) + return yield* provide(effect, { type: "unauthenticated", reason: "pty-ticket" }) + if (Capabilities.isPairingRedemption(request.method, url.pathname)) + return yield* provide(effect, { type: "unauthenticated", reason: "pairing-redemption" }) + if (ServerAuth.authorized(yield* credentialFromRequest(request), config)) + return yield* provide(effect, { type: "administrator" }) + const bearer = bearerFromRequest(request) + const principal = bearer ? yield* pairing.authenticate(bearer) : undefined + if (principal) return yield* provide(effect, principal) + if (!ServerAuth.required(config)) + return yield* provide(effect, { type: "unauthenticated", reason: "embedded" }) + yield* challenge + return yield* new UnauthorizedError({ message: "Authentication required" }) + }), + }) + }), +) + +export const challenge = HttpEffect.appendPreResponseHandler((_request, response) => + Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)), +) + +function provide(effect: Effect.Effect, principal: PrincipalInfo) { + return effect.pipe(Effect.provideService(Principal, principal)) +} + +function emptyCredential() { + return { username: "", password: Redacted.make("") } +} + +function decodeCredential(input: string) { + return Effect.fromResult(Encoding.decodeBase64String(input)).pipe( + Effect.match({ + onFailure: emptyCredential, + onSuccess: (header) => { + const separator = header.indexOf(":") + if (separator === -1) return emptyCredential() + return { username: header.slice(0, separator), password: Redacted.make(header.slice(separator + 1)) } + }, + }), + ) +} + +function credentialFromRequest(request: HttpServerRequest.HttpServerRequest) { + const token = new URL(request.url, "http://localhost").searchParams.get(AUTH_TOKEN_QUERY) + if (token) return decodeCredential(token) + const match = /^Basic\s+(.+)$/i.exec(request.headers.authorization ?? "") + if (match) return decodeCredential(match[1]) + return Effect.succeed(emptyCredential()) +} + +function bearerFromRequest(request: HttpServerRequest.HttpServerRequest) { + return /^Bearer\s+(\S+)$/i.exec(request.headers.authorization ?? "")?.[1] +} diff --git a/packages/server/src/middleware/authorization.ts b/packages/server/src/middleware/authorization.ts index 506e326ac046..8bd239939aa5 100644 --- a/packages/server/src/middleware/authorization.ts +++ b/packages/server/src/middleware/authorization.ts @@ -1,83 +1,33 @@ -import { ServerAuth } from "../auth" -import { Pairing } from "@opencode-ai/core/pairing" import { ForbiddenError, UnauthorizedError } from "@opencode-ai/protocol/errors" +import { Capabilities } from "@opencode-ai/protocol/capabilities" import { Authorization, Principal } from "@opencode-ai/protocol/middleware/authorization" export { Authorization, Principal } from "@opencode-ai/protocol/middleware/authorization" -import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty" -import { Capabilities } from "@opencode-ai/protocol/capabilities" -import { Effect, Encoding, Layer, Redacted } from "effect" -import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" - -const AUTH_TOKEN_QUERY = "auth_token" -const WWW_AUTHENTICATE = 'Basic realm="Secure Area", Bearer realm="Shuvcode Device"' - -function emptyCredential() { - return { username: "", password: Redacted.make("") } -} - -function decodeCredential(input: string) { - return Effect.fromResult(Encoding.decodeBase64String(input)).pipe( - Effect.match({ - onFailure: emptyCredential, - onSuccess: (header) => { - const separator = header.indexOf(":") - if (separator === -1) return emptyCredential() - return { username: header.slice(0, separator), password: Redacted.make(header.slice(separator + 1)) } - }, - }), - ) -} - -function credentialFromRequest(request: HttpServerRequest.HttpServerRequest) { - const url = new URL(request.url, "http://localhost") - const token = url.searchParams.get(AUTH_TOKEN_QUERY) - if (token) return decodeCredential(token) - const match = /^Basic\s+(.+)$/i.exec(request.headers.authorization ?? "") - if (match) return decodeCredential(match[1]) - return Effect.succeed(emptyCredential()) -} - -function bearerFromRequest(request: HttpServerRequest.HttpServerRequest) { - return /^Bearer\s+(\S+)$/i.exec(request.headers.authorization ?? "")?.[1] -} +import { Effect, Layer } from "effect" +import { HttpServerRequest } from "effect/unstable/http" +import { Authentication } from "./authentication" export const authorizationLayer = Layer.effect( Authorization, Effect.gen(function* () { - const config = yield* ServerAuth.Config - const pairing = yield* Pairing.Service + const authentication = yield* Authentication.Service return Authorization.of((effect) => - Effect.gen(function* () { - const request = yield* HttpServerRequest.HttpServerRequest - const url = new URL(request.url, "http://localhost") - // Browsers cannot set headers on WebSocket upgrades, so a ticketed PTY connect skips - // credential checks here; the connect handler consumes and validates the ticket. - if (hasPtyConnectTicketURL(url)) - return yield* effect.pipe(Effect.provideService(Principal, { type: "unauthenticated", reason: "pty-ticket" })) - if (Capabilities.isPairingRedemption(request.method, url.pathname)) - return yield* effect.pipe( - Effect.provideService(Principal, { type: "unauthenticated", reason: "pairing-redemption" }), - ) - const credential = yield* credentialFromRequest(request) - if (ServerAuth.authorized(credential, config)) - return yield* effect.pipe(Effect.provideService(Principal, { type: "administrator" })) - const bearer = bearerFromRequest(request) - const principal = bearer ? yield* pairing.authenticate(bearer) : undefined - if (principal?.type === "device") { - if ( - Capabilities.requiresAdministrator(request.method, url.pathname) || - !Capabilities.allowsMobile(request.method, url.pathname) - ) - return yield* new ForbiddenError({ message: "Administrator access required" }) - return yield* effect.pipe(Effect.provideService(Principal, principal)) - } - if (!ServerAuth.required(config) && !Capabilities.requiresAdministrator(request.method, url.pathname)) - return yield* effect.pipe(Effect.provideService(Principal, { type: "unauthenticated", reason: "embedded" })) - yield* HttpEffect.appendPreResponseHandler((_request, response) => - Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)), - ) - return yield* new UnauthorizedError({ message: "Authentication required" }) - }), + authentication.withPrincipal( + Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest + const principal = yield* Principal + const url = new URL(request.url, "http://localhost") + if (principal.type === "administrator") return yield* effect + if (principal.type === "device") { + if (!Capabilities.allowsMobile(request.method, url.pathname)) + return yield* new ForbiddenError({ message: "Administrator access required" }) + return yield* effect + } + if (principal.reason !== "embedded") return yield* effect + if (!Capabilities.requiresAdministrator(request.method, url.pathname)) return yield* effect + yield* Authentication.challenge + return yield* new UnauthorizedError({ message: "Authentication required" }) + }), + ), ) }), -) +).pipe(Layer.provide(Authentication.layer)) diff --git a/packages/server/test/pairing.test.ts b/packages/server/test/pairing.test.ts index dfbefe38411e..c9319b6ec5a1 100644 --- a/packages/server/test/pairing.test.ts +++ b/packages/server/test/pairing.test.ts @@ -2,7 +2,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test" import { Layer } from "effect" import { randomBytes, randomUUID } from "node:crypto" import { HttpRouter, HttpServer } from "effect/unstable/http" -import { createRoutes } from "../src/routes" +import { createEmbeddedRoutes, createRoutes } from "../src/routes" process.env.OPENCODE_DB = ":memory:" @@ -11,12 +11,15 @@ const basic = `Basic ${Buffer.from(`opencode:${password}`).toString("base64")}` const app = HttpRouter.toWebHandler( createRoutes(password, () => ["https://shuvdev.example"]).pipe(Layer.provide(HttpServer.layerServices)), ) +const embedded = HttpRouter.toWebHandler(createEmbeddedRoutes().pipe(Layer.provide(HttpServer.layerServices))) beforeAll(async () => { await app.handler(new Request("http://localhost/api/health", { headers: { authorization: basic } })) }) -afterAll(() => app.dispose()) +afterAll(async () => { + await Promise.all([app.dispose(), embedded.dispose()]) +}) describe("pairing HTTP authorization", () => { test("redeems without server credentials but keeps management administrator-only", async () => { @@ -97,4 +100,17 @@ describe("pairing HTTP authorization", () => { ).status, ).toBe(400) }) + + test("keeps embedded server routes open while protecting pairing management", async () => { + expect((await embedded.handler(new Request("http://localhost/api/server"))).status).toBe(200) + expect( + ( + await embedded.handler( + new Request("http://localhost/api/pairing/invitation", { + method: "POST", + }), + ) + ).status, + ).toBe(401) + }) }) From 84b095860a5acb8aaff04c3a027d6395190c00c8 Mon Sep 17 00:00:00 2001 From: shuv Date: Tue, 14 Jul 2026 17:29:48 -0700 Subject: [PATCH 4/7] fix(server): preserve legacy auth composition --- .../server/src/middleware/authentication.ts | 54 +++++++++---------- .../server/src/middleware/authorization.ts | 4 +- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/packages/server/src/middleware/authentication.ts b/packages/server/src/middleware/authentication.ts index 6a8bc1a98ab6..ffcfabe992a8 100644 --- a/packages/server/src/middleware/authentication.ts +++ b/packages/server/src/middleware/authentication.ts @@ -5,7 +5,7 @@ import { UnauthorizedError } from "@opencode-ai/protocol/errors" import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty" import { Capabilities } from "@opencode-ai/protocol/capabilities" import { Principal, type PrincipalInfo } from "@opencode-ai/protocol/middleware/authorization" -import { Context, Effect, Encoding, Layer, Redacted } from "effect" +import { Context, Effect, Encoding, Layer, Option, Redacted } from "effect" import { HttpEffect, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" import { ServerAuth } from "../auth" @@ -20,33 +20,31 @@ export interface Interface { export class Service extends Context.Service()("@opencode/ServerAuthentication") {} -export const layer = Layer.effect( - Service, - Effect.gen(function* () { - const config = yield* ServerAuth.Config - const pairing = yield* Pairing.Service - return Service.of({ - withPrincipal: (effect) => - Effect.gen(function* () { - const request = yield* HttpServerRequest.HttpServerRequest - const url = new URL(request.url, "http://localhost") - if (hasPtyConnectTicketURL(url)) - return yield* provide(effect, { type: "unauthenticated", reason: "pty-ticket" }) - if (Capabilities.isPairingRedemption(request.method, url.pathname)) - return yield* provide(effect, { type: "unauthenticated", reason: "pairing-redemption" }) - if (ServerAuth.authorized(yield* credentialFromRequest(request), config)) - return yield* provide(effect, { type: "administrator" }) - const bearer = bearerFromRequest(request) - const principal = bearer ? yield* pairing.authenticate(bearer) : undefined - if (principal) return yield* provide(effect, principal) - if (!ServerAuth.required(config)) - return yield* provide(effect, { type: "unauthenticated", reason: "embedded" }) - yield* challenge - return yield* new UnauthorizedError({ message: "Authentication required" }) - }), - }) - }), -) +export const make = Effect.gen(function* () { + const config = yield* ServerAuth.Config + const pairing = yield* Effect.serviceOption(Pairing.Service) + return Service.of({ + withPrincipal: (effect) => + Effect.gen(function* () { + const request = yield* HttpServerRequest.HttpServerRequest + const url = new URL(request.url, "http://localhost") + if (hasPtyConnectTicketURL(url)) + return yield* provide(effect, { type: "unauthenticated", reason: "pty-ticket" }) + if (Capabilities.isPairingRedemption(request.method, url.pathname)) + return yield* provide(effect, { type: "unauthenticated", reason: "pairing-redemption" }) + if (ServerAuth.authorized(yield* credentialFromRequest(request), config)) + return yield* provide(effect, { type: "administrator" }) + const bearer = bearerFromRequest(request) + const principal = bearer && Option.isSome(pairing) ? yield* pairing.value.authenticate(bearer) : undefined + if (principal) return yield* provide(effect, principal) + if (!ServerAuth.required(config)) return yield* provide(effect, { type: "unauthenticated", reason: "embedded" }) + yield* challenge + return yield* new UnauthorizedError({ message: "Authentication required" }) + }), + }) +}) + +export const layer = Layer.effect(Service, make) export const challenge = HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeader(response, "www-authenticate", WWW_AUTHENTICATE)), diff --git a/packages/server/src/middleware/authorization.ts b/packages/server/src/middleware/authorization.ts index 8bd239939aa5..f5867116d111 100644 --- a/packages/server/src/middleware/authorization.ts +++ b/packages/server/src/middleware/authorization.ts @@ -9,7 +9,7 @@ import { Authentication } from "./authentication" export const authorizationLayer = Layer.effect( Authorization, Effect.gen(function* () { - const authentication = yield* Authentication.Service + const authentication = yield* Authentication.make return Authorization.of((effect) => authentication.withPrincipal( Effect.gen(function* () { @@ -30,4 +30,4 @@ export const authorizationLayer = Layer.effect( ), ) }), -).pipe(Layer.provide(Authentication.layer)) +) From 457138d3fdbdeb62465e159ebec9f64f5592b92e Mon Sep 17 00:00:00 2001 From: shuv Date: Tue, 14 Jul 2026 22:30:19 -0700 Subject: [PATCH 5/7] docs: define shared host service --- deploy/systemd/shuvcode.service | 16 +++++++ docs/shared-service.md | 70 +++++++++++++++++++++++++++++++ specs/v2/mobile-server-pairing.md | 19 +++++---- 3 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 deploy/systemd/shuvcode.service create mode 100644 docs/shared-service.md diff --git a/deploy/systemd/shuvcode.service b/deploy/systemd/shuvcode.service new file mode 100644 index 000000000000..9315777a698b --- /dev/null +++ b/deploy/systemd/shuvcode.service @@ -0,0 +1,16 @@ +[Unit] +Description=Shared Shuvcode V2 server +Wants=network-online.target +After=network-online.target + +[Service] +Type=simple +ExecStart=%h/.local/bin/shuvcode serve --service --hostname=127.0.0.1 --port=4096 +Restart=on-failure +RestartSec=2s +KillMode=control-group +TimeoutStopSec=20s +UMask=0077 + +[Install] +WantedBy=default.target diff --git a/docs/shared-service.md b/docs/shared-service.md new file mode 100644 index 000000000000..e86fbed84ec7 --- /dev/null +++ b/docs/shared-service.md @@ -0,0 +1,70 @@ +# Shared host service + +A Shuvcode host runs one managed V2 server for the interactive Shuvcode TUI and +all local or remote clients. The server owns sessions, projects, integrations, +providers, agents, plugins, MCP servers, skills, and instructions. Consumers do +not create caller-specific Shuvcode configuration domains. + +## Ownership and paths + +- Shuvcode owns `shuvcode.service` and its lifecycle. +- The canonical configuration root is `~/.config/opencode`; neither the unit nor + an interactive shell sets `OPENCODE_CONFIG_DIR`. +- Shuvcode's normal XDG data and state roots remain canonical for every caller. +- `~/.config/opencode/service.json` is private mode `0600` and contains the + administrator credential used by trusted loopback clients. +- Mobile clients receive independently revocable device credentials through + pairing. A bridge may keep its own client-facing credential domain while + using the administrator credential on loopback. + +Install the user unit from this repository: + +```sh +install -m 0644 deploy/systemd/shuvcode.service ~/.config/systemd/user/ +systemctl --user daemon-reload +systemctl --user enable --now shuvcode.service +``` + +The managed server binds loopback. Publish a separate advertised URL when a +tailnet reverse proxy is used: + +```sh +shuvcode service set hostname 127.0.0.1 +shuvcode service set port 4096 +shuvcode service set advertised-urls https://shuvdev.tail586a6d.ts.net:10001 +systemctl --user restart shuvcode.service +tailscale serve --bg --https=10001 http://127.0.0.1:4096 +``` + +The bind and advertised URL are deliberately different. Do not widen the bind +to make a reverse-proxy URL reachable. + +## Migrating an isolated service + +Stop dependent callers, back up both configuration roots, and merge the desired +server configuration into `~/.config/opencode`. Preserve the active service +password by moving it into the canonical `service.json`, then update dependent +loopback clients to the same value without printing it. Remove any systemd +drop-in that sets `OPENCODE_CONFIG_DIR`, reload the user manager, and restart +Shuvcode before its dependants. + +Provider authentication stored only in a legacy credential file is not a V2 +integration connection. Reconnect those providers through the V2 TUI after the +shared server is active. Do not copy credential records directly into the V2 +database. + +## Verification + +```sh +systemctl --user show shuvcode.service \ + -p ActiveState -p SubState -p ExecStart -p Environment +ss -ltnp | rg '127\.0\.0\.1:4096' +tailscale serve status +shuvcode service get +shuvcode pair +``` + +Verify that interactive TUI sessions and paired mobile sessions appear in the +same session list, and that every configured V2 provider appears through the +model endpoint. Logs and verification output must not contain administrator +passwords, invitation tokens, or device credentials. diff --git a/specs/v2/mobile-server-pairing.md b/specs/v2/mobile-server-pairing.md index 8cb81f5c4290..9de3219c6f98 100644 --- a/specs/v2/mobile-server-pairing.md +++ b/specs/v2/mobile-server-pairing.md @@ -288,13 +288,14 @@ Admin invitation creation and device list/revoke remain in Shuvcode's generated 1. Preserve Shuvcode on `127.0.0.1:4096`, ClankerOne on `127.0.0.1:8787`, and its attachment handoff on `127.0.0.1:8788`. 2. Add a dedicated tailnet-only Tailscale Serve HTTPS listener at port `10001` forwarding to Shuvcode `127.0.0.1:4096`; keep ClankerOne on HTTPS port `10000`. Do not expose port 8788. -3. Update the ClankerOne-owned deployment and operations material for the additional tailnet listener, configure Shuvcode's advertised URL as `https://shuvdev.tail586a6d.ts.net:10001` while retaining its loopback bind, and deploy the coordinated server changes without collapsing the two credential domains. -4. Verify administrator Basic authentication still works for existing tools. -5. Run ClankerOne's package, bridge health, session, governed attachment, iOS, and live smoke gates against its updated shuvkit pin and the replaced Shuvcode process. -6. Pair one development device and verify reconnect across app and server restarts. -7. Revoke that device and prove subsequent requests fail without affecting other clients. -8. Ship the OpenShuv build for real-device dogfooding. -9. Keep the legacy credential-bearing QR decoder out of production unless a temporary compatibility decision is made explicitly. +3. Make Shuvcode the owner of one host-level managed service using the canonical `~/.config/opencode` domain. The interactive TUI, ClankerOne bridge, and OpenShuv must share its sessions, projects, integrations, providers, agents, plugins, MCP servers, skills, and instructions. ClankerOne depends on that service but must not set `OPENCODE_CONFIG_DIR`, override its process, or stop it with the bridge target. +4. Update the coordinated deployment and operations material for the additional tailnet listener, configure Shuvcode's advertised URL as `https://shuvdev.tail586a6d.ts.net:10001` while retaining its loopback bind, and deploy the server changes without collapsing the bridge and Shuvcode client credential domains. +5. Verify administrator Basic authentication still works for existing tools. +6. Run ClankerOne's package, bridge health, session, governed attachment, iOS, and live smoke gates against its updated shuvkit pin and the replaced Shuvcode process. +7. Pair one development device and verify reconnect across app and server restarts. +8. Revoke that device and prove subsequent requests fail without affecting other clients. +9. Ship the OpenShuv build for real-device dogfooding. +10. Keep the legacy credential-bearing QR decoder out of production unless a temporary compatibility decision is made explicitly. ## Verification Gates @@ -336,13 +337,15 @@ Admin invitation creation and device list/revoke remain in Shuvcode's generated - `GET /api/server` advertises `https://shuvdev.tail586a6d.ts.net:10001` while the Shuvcode process remains bound only to `127.0.0.1:4096`. - Tailscale Serve keeps HTTPS 10000 mapped to ClankerOne 8787 and adds HTTPS 10001 mapped to Shuvcode 4096; port 8788 remains unadvertised. - Pairing works against the systemd-managed service through the same wrapper and XDG configuration used in production. +- The systemd-managed service and an ordinary interactive `shuvcode` invocation use the same canonical `~/.config/opencode` configuration and V2 data roots; no caller-specific `OPENCODE_CONFIG_DIR` is present. +- Sessions created through the TUI, ClankerOne, and OpenShuv appear through the same server session interface, and provider connections made through the TUI are visible to mobile model selection. - ClankerOne's Basic-auth bridge path and loopback attachment handoff still pass their M3 compatibility smoke after its governed shuvkit update and the Shuvcode replacement. - Logs contain no invitation token, device secret, administrator password, or authorization header. - A real device can reconnect after both app termination and server restart. ## ClankerOne Integration -ClankerOne is managed as part of this multi-project plan. Its repository may receive dependency, deployment, documentation, shared scanner, test, or compatibility changes when they are required to keep the complete system aligned. +ClankerOne is managed as part of this multi-project plan. Its repository may receive dependency, deployment, documentation, shared scanner, test, or compatibility changes when they are required to keep the complete system aligned. Shuvcode owns the shared host process and canonical configuration domain; ClankerOne owns only its bridge process and bridge state. Its iOS app authenticates to the ClankerOne bridge with a static Bearer token rather than connecting directly to Shuvcode. A future ClankerOne pairing design may reuse the versioned envelope and scanner components, but the bridge must issue and redeem its own credential type. Shuvcode must not issue credentials that bypass the bridge architecture. From d0f07c025eedf19914f247c0fddb151858a25fef Mon Sep 17 00:00:00 2001 From: shuv Date: Tue, 14 Jul 2026 22:34:27 -0700 Subject: [PATCH 6/7] docs: sequence shared service activation --- docs/shared-service.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/shared-service.md b/docs/shared-service.md index e86fbed84ec7..d0709bc2f524 100644 --- a/docs/shared-service.md +++ b/docs/shared-service.md @@ -22,20 +22,16 @@ Install the user unit from this repository: ```sh install -m 0644 deploy/systemd/shuvcode.service ~/.config/systemd/user/ systemctl --user daemon-reload -systemctl --user enable --now shuvcode.service -``` - -The managed server binds loopback. Publish a separate advertised URL when a -tailnet reverse proxy is used: - -```sh shuvcode service set hostname 127.0.0.1 shuvcode service set port 4096 shuvcode service set advertised-urls https://shuvdev.tail586a6d.ts.net:10001 -systemctl --user restart shuvcode.service +systemctl --user enable --now shuvcode.service tailscale serve --bg --https=10001 http://127.0.0.1:4096 ``` +The managed server binds loopback. Configure it while stopped, then publish the +separate advertised URL through the tailnet reverse proxy as shown above. + The bind and advertised URL are deliberately different. Do not widen the bind to make a reverse-proxy URL reachable. From 183165df220183a0d3adb0d921e641f13a2208aa Mon Sep 17 00:00:00 2001 From: shuv Date: Wed, 15 Jul 2026 00:52:26 -0700 Subject: [PATCH 7/7] fix(ci): align service smoke with fork --- .github/workflows/test.yml | 4 ---- packages/cli/script/service-smoke.ts | 4 ++-- packages/server/src/process.ts | 19 +++++++++++++++---- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b2e87fc7a3c..d29da4b7696b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,8 +30,6 @@ jobs: settings: - name: linux host: blacksmith-4vcpu-ubuntu-2404 - - name: windows - host: blacksmith-4vcpu-windows-2025 runs-on: ${{ matrix.settings.host }} defaults: run: @@ -92,8 +90,6 @@ jobs: settings: - name: linux host: blacksmith-4vcpu-ubuntu-2404 - - name: windows - host: blacksmith-4vcpu-windows-2025 runs-on: ${{ matrix.settings.host }} env: PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.playwright-browsers diff --git a/packages/cli/script/service-smoke.ts b/packages/cli/script/service-smoke.ts index ff6b25270892..2ef93b264709 100644 --- a/packages/cli/script/service-smoke.ts +++ b/packages/cli/script/service-smoke.ts @@ -7,9 +7,9 @@ import fs from "node:fs/promises" import os from "node:os" import path from "node:path" -const target = `cli-${process.platform === "win32" ? "windows" : process.platform}-${process.arch}` +const target = `shuvcode-${process.platform === "win32" ? "windows" : process.platform}-${process.arch}` const directory = path.join(import.meta.dir, "..", "dist", target, "bin") -const binary = path.join(directory, `opencode2${process.platform === "win32" ? ".exe" : ""}`) +const binary = path.join(directory, `shuvcode${process.platform === "win32" ? ".exe" : ""}`) if (!(await Bun.file(binary).exists())) throw new Error(`Missing compiled CLI in ${directory}`) const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-service-smoke-")) diff --git a/packages/server/src/process.ts b/packages/server/src/process.ts index f2b595428075..01456245e5dc 100644 --- a/packages/server/src/process.ts +++ b/packages/server/src/process.ts @@ -2,7 +2,9 @@ export * as ServerProcess from "./process" import { NodeHttpServer, NodeHttpServerRequest } from "@effect/platform-node" import { SessionRestart } from "@opencode-ai/core/session/execution/restart" +import { Capabilities } from "@opencode-ai/protocol/capabilities" import { ServiceStatus } from "@opencode-ai/protocol/groups/health" +import { hasPtyConnectTicketURL } from "@opencode-ai/protocol/groups/pty" import { Cause, Context, Deferred, Effect, Exit, Layer, Option, Ref, Schema, Scope } from "effect" import { HttpMiddleware, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse } from "effect/unstable/http" import { createServer } from "node:http" @@ -138,6 +140,10 @@ function dispatch( return Effect.gen(function* () { const request = yield* HttpServerRequest.HttpServerRequest const url = new URL(request.url, "http://localhost") + const state = yield* status.current + const applicationEffect = yield* Ref.get(application) + const ready = state.type === "ready" && Option.isSome(applicationEffect) + const bearer = /^Bearer\s+\S+$/i.test(request.headers.authorization ?? "") const lifecycle = request.method === "GET" && url.pathname === "/api/health" ? "health" @@ -145,14 +151,19 @@ function dispatch( ? "stop" : undefined if (lifecycle !== undefined) { + if (lifecycle === "health" && ready && bearer) return yield* applicationEffect.value if (!(yield* authorizedRequest(request, auth))) return unauthorized() return yield* control(request, lifecycle, status, () => Deferred.doneUnsafe(shutdown, Effect.void)) } - const state = yield* status.current - const app = yield* Ref.get(application) - const ready = state.type === "ready" && Option.isSome(app) - if (ready) return yield* app.value + if ( + ready && + (bearer || + Capabilities.isPairingRedemption(request.method, url.pathname) || + hasPtyConnectTicketURL(url)) + ) + return yield* applicationEffect.value if (!(yield* authorizedRequest(request, auth))) return unauthorized() + if (ready) return yield* applicationEffect.value return unavailable(state) }) }