diff --git a/README.md b/README.md index ca317a21..624f9ab1 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,13 @@ Browse the [experimental v2 TypeScript API reference](https://agentclientprotoco and the [draft ACP v2 protocol documentation](https://agentclientprotocol.com/protocol/v2/draft/overview) for the current SDK and protocol designs. +In v2, the extensible unions (`SessionUpdate`, `ContentBlock`, and so on) also +accept variants this SDK does not know, such as ones added by a newer ACP +version. Only received values can be such an `UnknownVariant`, so a value you +build is checked field by field at any depth, and a custom variant needs a tag +that starts with `_`. Received values pass on unchanged, for example when an +agent echoes a prompt's content into its user message. + ## Get Started ### Understand the Protocol diff --git a/scripts/generate.js b/scripts/generate.js index 0f3032fc..173844a0 100644 --- a/scripts/generate.js +++ b/scripts/generate.js @@ -31,6 +31,11 @@ const CHECK_GENERATED = process.argv.includes("--check"); // vendor payloads). Both helpers live in src/schema-deserialize.ts. // 3. emitExtensibleUnionGuards writes each lane's guards.gen.ts — validated, // declaration-merged type guards consumers use to narrow the unions. +// 4. For a lane with `brandUnknownVariants`, brandedTypesSource splits each +// catch-all in the generated *types* into custom variants under a +// `_`-prefixed tag and `UnknownVariant`s, which only received values +// have. A value you build then cannot pass a malformed known variant as +// the catch-all, while received values pass on unchanged. // Drift protection, each assertion guarding a different failure mode: // - The lane's expectedExtensibleUnions list (below): detection missed a // union in the raw schema, or found an unexpected one. @@ -90,6 +95,7 @@ const SCHEMA_CONFIGS = [ previousDir: "./src/.schema-v2-previous", schemaDeserializeImport: "../../schema-deserialize.js", expectedExtensibleUnions: V2_EXTENSIBLE_UNIONS, + brandUnknownVariants: true, openApiVersion: "2.0.0", releaseTag: CURRENT_V2_SCHEMA_RELEASE, }, @@ -101,6 +107,27 @@ const SCHEMA_CONFIGS = [ // be initialized. const EXCLUDE_KNOWN_TAGS_ATTR = "x-exclude-known-tags"; const OPEN_OBJECT_ATTR = "x-acp-open-object"; +// The constant that pins each catch-all tag in the types pass of a lane with +// `brandUnknownVariants` (see brandedTypesSource). +const CATCH_ALL_TAG_MARKER = "__ACP_CATCH_ALL_TAG__"; +// Declared in the types of a lane with `brandUnknownVariants`. +const UNKNOWN_VARIANT_DECLARATION = ` +declare const unknownVariant: unique symbol; + +/** + * A variant of an extensible union that this SDK does not know, as received + * from a peer, for example one added by a newer ACP version. + * + * Only received values have this type. A value you build is a known variant, + * checked field by field, or a custom variant whose tag starts with \`_\`, so + * a misspelled or missing field on a known variant is a compile error. + * Received values keep the type, so passing them on needs no conversion. The + * brand exists only in the types. + * + * @experimental + */ +export type UnknownVariant = T & { readonly [unknownVariant]: true }; +`; await main(); @@ -134,6 +161,10 @@ async function generateSchema(config, checkGenerated) { const defExclusions = annotateExtensibleUnions(jsonSchema.$defs); annotateOpenObjects(jsonSchema.$defs); const schemaDefs = jsonSchema.$defs; + // Copied before the main pass, which may rewrite the schemas it reads. + const typesDefs = config.brandUnknownVariants + ? structuredClone(schemaDefs) + : undefined; // Generate into a staging directory and swap into place only after every // step (including the drift assertions below) has succeeded: hey-api wipes @@ -214,7 +245,9 @@ async function generateSchema(config, checkGenerated) { await fs.writeFile(zodPath, zod); const tsPath = `${stagingDir}/types.gen.ts`; - const tsSrc = await fs.readFile(tsPath, "utf8"); + const tsSrc = typesDefs + ? await brandedTypesSource(config, typesDefs) + : await fs.readFile(tsPath, "utf8"); const ts = await formatStable( updateDocs( tsSrc.replace( @@ -228,12 +261,13 @@ async function generateSchema(config, checkGenerated) { // Always write the file: the staging swap replaces the whole directory, so // skipping the write here would silently delete guards.gen.ts. - const guardsSrc = emitExtensibleUnionGuards( + const unions = detectExtensibleUnions( schemaDefs, config.expectedExtensibleUnions, config.name, + config.brandUnknownVariants === true, ); - const guards = await formatStable(guardsSrc); + const guards = await formatStable(emitExtensibleUnionGuards(unions)); await fs.writeFile(`${stagingDir}/guards.gen.ts`, guards); const meta = `export const AGENT_METHODS = ${JSON.stringify(metadata.agentMethods, null, 2)} as const; @@ -638,10 +672,10 @@ function notClauseExclusion(not) { // payload where the catch-all carries structure). A malformed known variant // matches no guard — the same classification the wire validators apply via // excludeKnownTags (see createDeserializationResolvers' union resolver). -function emitExtensibleUnionGuards(schemaDefs, expectedUnions, lane) { +function detectExtensibleUnions(schemaDefs, expectedUnions, lane, branded) { const unions = []; for (const [name, def] of Object.entries(schemaDefs)) { - const union = analyzeExtensibleUnion(name, def); + const union = analyzeExtensibleUnion(name, def, branded); if (union) unions.push(union); } @@ -656,6 +690,127 @@ function emitExtensibleUnionGuards(schemaDefs, expectedUnions, lane) { `scripts/generate.js and that lane's guard exports.`, ); } + return unions; +} + +// Unknown variants. A catch-all must accept any string tag, because receivers +// must tolerate future ACP variants. Typed that way, it also lets a value you +// build pass a malformed known variant (right tag, misspelled or missing +// field) as the catch-all, at any depth of a message. In a branded lane the +// types split each catch-all in two: +// +// - custom variants, whose tag is `_${string}`, the protocol's prefix for +// implementation-specific values. Known tags never start with `_`. +// - `UnknownVariant`s, with any string tag, branded with a symbol that only +// the types of received values carry. A value you build cannot have it. +// +// Received values keep the brand, so passing them on needs no conversion. +// The brand exists only in the types: zod still validates the catch-all as +// before. TypeScript cannot narrow past a member with a wide string tag in +// either form, so receivers narrow with the generated guards as before. + +// Generates the types of a branded lane from a copy of its schema whose +// catch-all tags are pinned to a marker, then splits the object type that +// holds each marker into its custom and unknown forms. +async function brandedTypesSource(config, typesDefs) { + let marked = 0; + for (const def of Object.values(typesDefs)) { + walkSchema(def, (node) => { + const exclusion = node[EXCLUDE_KNOWN_TAGS_ATTR]; + if (!exclusion) return; + node.properties = { + ...node.properties, + [exclusion.key]: { + ...node.properties?.[exclusion.key], + type: "string", + const: CATCH_ALL_TAG_MARKER, + }, + }; + marked += 1; + }); + } + + const outputDir = `${config.stagingDir}-types`; + await fs.rm(outputDir, { recursive: true, force: true }); + await createClient({ + input: { + openapi: "3.1.0", + info: { + title: "Agent Client Protocol", + version: config.openApiVersion, + }, + components: { schemas: typesDefs }, + }, + output: { path: outputDir }, + plugins: [typescriptPlugin()], + }); + const generated = await fs.readFile(`${outputDir}/types.gen.ts`, "utf8"); + await fs.rm(outputDir, { recursive: true, force: true }); + + const { source, split } = splitCatchAllMembers(generated); + if (split !== marked) { + throw new Error( + `[${config.name}] Marked ${marked} catch-all tags, but split ${split} ` + + `catch-all members in the generated types; the branded types pass ` + + `may have drifted`, + ); + } + const banner = source.indexOf("\n") + 1; + return ( + source.slice(0, banner) + UNKNOWN_VARIANT_DECLARATION + source.slice(banner) + ); +} + +// Replaces each object type literal that directly holds the marker with +// `(custom | UnknownVariant)`. Scans the source once, skipping +// comments and string literals (the docs contain braces), to find the +// innermost object type literal around each marker. +function splitCatchAllMembers(source) { + const markerLiterals = new Set([ + `"${CATCH_ALL_TAG_MARKER}"`, + `'${CATCH_ALL_TAG_MARKER}'`, + ]); + const opens = []; + const owners = new Set(); + const spans = []; + for (let i = 0; i < source.length; i++) { + const char = source[i]; + if (char === "/" && source[i + 1] === "*") { + i = source.indexOf("*/", i + 2) + 1; + } else if (char === "/" && source[i + 1] === "/") { + const end = source.indexOf("\n", i); + i = end === -1 ? source.length : end; + } else if (char === '"' || char === "'" || char === "`") { + let end = i + 1; + while (source[end] !== char) end += source[end] === "\\" ? 2 : 1; + if (markerLiterals.has(source.slice(i, end + 1))) { + owners.add(opens.at(-1)); + } + i = end; + } else if (char === "{") { + opens.push(i); + } else if (char === "}") { + const start = opens.pop(); + if (owners.has(start)) spans.push([start, i + 1]); + } + } + + const marker = new RegExp(`["']${CATCH_ALL_TAG_MARKER}["']`, "g"); + let result = source; + // Spans close in source order and never nest, so replace from the end. + for (const [start, end] of spans.reverse()) { + const member = result.slice(start, end); + const custom = member.replace(marker, "`_${string}`"); + const unknown = member.replace(marker, "string"); + result = + result.slice(0, start) + + `(${custom} | UnknownVariant<${unknown}>)` + + result.slice(end); + } + return { source: result, split: spans.length }; +} + +function emitExtensibleUnionGuards(unions) { if (unions.length === 0) return "// This file is auto-generated by scripts/generate.js\nexport {};\n"; @@ -754,7 +909,7 @@ function emitExtensibleUnionGuards(schemaDefs, expectedUnions, lane) { ); } -function analyzeExtensibleUnion(name, def) { +function analyzeExtensibleUnion(name, def, branded) { const variants = def.anyOf ?? def.oneOf; if (!Array.isArray(variants)) return undefined; @@ -851,7 +1006,12 @@ function analyzeExtensibleUnion(name, def) { // Reconstruct the catch-all's TS type so `isCustom`'s predicate stays assignable // to the union, plus a zod expression for its payload when it carries structure // beyond an open bag of properties (e.g. a nested scope union). - const catchAll = analyzeCatchAll(name, catchAllVariant, discriminant); + const catchAll = analyzeCatchAll( + name, + catchAllVariant, + discriminant, + branded, + ); catchAll.tsType += commonPick; return { @@ -933,16 +1093,21 @@ function isUnconstrainedSchema(schema) { ); } -function analyzeCatchAll(name, variant, discriminant) { +function analyzeCatchAll(name, variant, discriminant, branded) { const inlineRequired = requiredInlineProps(name, variant, [discriminant]); const nested = variant.anyOf ?? variant.oneOf; const refUnion = Array.isArray(nested) ? nested.flatMap(allOfRefs) : []; const directRefs = allOfRefs(variant); + // Matches the generated index-signature variant: in a branded lane, split + // into its custom and unknown forms (see brandedTypesSource). + const bag = (tag) => `{ ${discriminant}: ${tag}; [key: string]: unknown }`; + const openBag = branded + ? `(${bag("`_${string}`")} | types.UnknownVariant<${bag("string")}>)` + : `(${bag("string")})`; if (directRefs.length === 0 && refUnion.length === 0) { - // Open bag of properties — matches the generated index-signature variant, - // and the discriminant check in isCustom validates its tag. - const openBag = `({ ${discriminant}: string; [key: string]: unknown })`; + // Open bag of properties; the discriminant check in isCustom validates + // its tag. return { tsType: inlineRequired ? `(${openBag} & ${inlineRequired.tsType})` @@ -964,7 +1129,7 @@ function analyzeCatchAll(name, variant, discriminant) { // The index signature matches the generated union member: a custom // variant's extra keys are its payload and survive parsing. tsType: - `(${tsRefs} & { ${discriminant}: string; [key: string]: unknown }` + + `(${tsRefs} & ${openBag}` + `${inlineRequired ? ` & ${inlineRequired.tsType}` : ""})`, zodExpr: chainAnd(zodParts), }; diff --git a/src/v2/acp.test.ts b/src/v2/acp.test.ts index d6ea2b69..1a13b533 100644 --- a/src/v2/acp.test.ts +++ b/src/v2/acp.test.ts @@ -413,6 +413,111 @@ describe("experimental v2 app API", () => { } }); + describe("extensible unions", () => { + it("check a known variant you build, at any depth", () => { + // @ts-expect-error a select option with a field it does not have + const option: sdk.SessionConfigOption = { + type: "select", + configId: "model", + name: "Model", + currentValue: "fast", + options: [{ value: "fast", name: "Fast", title: "Fast model" }], + }; + // @ts-expect-error a known variant missing a required field + const usage: SessionUpdate = { sessionUpdate: "usage_update", used: 1 }; + // @ts-expect-error a misnamed field in a nested known block + const chunk: SessionUpdate = { + sessionUpdate: "agent_message_chunk", + messageId: "m", + content: { type: "text", txt: "hello" }, + }; + expect([option, usage, chunk]).toHaveLength(3); + }); + + it("accept custom variants only under a `_`-prefixed tag", () => { + const custom: SessionUpdate = { + sessionUpdate: "_acme/progress", + percent: 40, + }; + const customOption: sdk.SessionConfigOption = { + type: "_slider", + configId: "temperature", + name: "Temperature", + min: 0, + max: 1, + }; + const customBlock: SessionUpdate = { + sessionUpdate: "agent_message_chunk", + messageId: "m", + content: { type: "_acme/widget", spec: 1 }, + }; + // @ts-expect-error unknown tags without `_` are reserved for future ACP versions + const reserved: SessionUpdate = { sessionUpdate: "progress" }; + // @ts-expect-error a custom variant still carries the union's shared fields + const missingShared: sdk.SessionConfigOption = { + type: "_slider", + name: "Temperature", + }; + expect([ + custom, + customOption, + customBlock, + reserved, + missingShared, + ]).toHaveLength(5); + }); + + it("pass on received values, unknown variants included", async () => { + // A newer client sends a content block this SDK does not know. The agent + // echoes the prompt into its user message without converting it. + const futureBlock = { type: "future_block", payload: 1 }; + const echoed = Promise.withResolvers(); + const agentApp = testAgent() + .onRequest(methods.agent.session.new, () => ({ sessionId: "s" })) + .onRequest( + methods.agent.session.prompt, + async ({ params, client: agentClient }) => { + await agentClient.notify(methods.client.session.update, { + sessionId: params.sessionId, + update: { + sessionUpdate: "user_message", + messageId: "u", + content: params.prompt, + }, + }); + return { messageId: "u" }; + }, + ); + const clientApp = client().onNotification( + methods.client.session.update, + ({ params }) => echoed.resolve(params.update), + ); + await clientApp.connectWith(agentApp, async (agent) => { + await agent.request(methods.agent.initialize, { + protocolVersion: PROTOCOL_VERSION, + info: clientInfo, + }); + await agent.request(methods.agent.session.prompt, { + sessionId: "s", + // Simulates a newer peer: a value you build cannot be unknown. + prompt: [futureBlock as sdk.UnknownVariant], + }); + }); + expect(await echoed.promise).toEqual({ + sessionUpdate: "user_message", + messageId: "u", + content: [futureBlock], + }); + }); + + it("keep known variants that have no tag", () => { + const titled: sdk.MultiSelectItems = { + anyOf: [{ const: "a", title: "A" }], + }; + expect(titled).toBeDefined(); + }); + }); + it("initializes exactly once, queues later calls, and exposes the exchange", async () => { const initializeGate = Promise.withResolvers(); const agentReady = Promise.withResolvers(); diff --git a/src/v2/acp.ts b/src/v2/acp.ts index c35e68ed..b8a0755d 100644 --- a/src/v2/acp.ts +++ b/src/v2/acp.ts @@ -389,7 +389,9 @@ function assertV2BatchMethods( } function parseV2InitializeRequest(params: unknown): schema.InitializeRequest { - const request = validate.zInitializeRequest.parse(params); + const request = received( + validate.zInitializeRequest, + ).parse(params); if (request.protocolVersion !== schema.PROTOCOL_VERSION) { throw RequestError.invalidParams( { @@ -416,7 +418,9 @@ function normalizeOutgoingV2InitializeRequest( } function mapV2InitializeResponse(response: unknown): schema.InitializeResponse { - const parsed = validate.zInitializeResponse.parse(response); + const parsed = received( + validate.zInitializeResponse, + ).parse(response); if (parsed.protocolVersion !== schema.PROTOCOL_VERSION) { throw RequestError.invalidRequest( { @@ -2170,6 +2174,20 @@ type AcpNotificationSpec = { params?: ParamsParser; }; +/** + * A generated validator as the parser of received values. + * + * The validators type the tag of an unknown variant as a plain string, while + * the protocol types brand it as an `UnknownVariant`, which only received + * values have. Everything a validator parses is received, so its output has + * the protocol type. + */ +function received(validator: { parse(value: unknown): unknown }): { + parse(value: unknown): T; +} { + return validator as { parse(value: unknown): T }; +} + function requestSpec( method: string, params: ParamsParser, @@ -2300,21 +2318,21 @@ const agentRequestSpecs = { ), newSession: requestSpec( schema.AGENT_METHODS.session_new, - validate.zNewSessionRequest, - validate.zNewSessionResponse, + received(validate.zNewSessionRequest), + received(validate.zNewSessionResponse), ), setSessionConfigOption: requestSpec< schema.SetSessionConfigOptionRequest, schema.SetSessionConfigOptionResponse >( schema.AGENT_METHODS.session_set_config_option, - validate.zSetSessionConfigOptionRequest, - validate.zSetSessionConfigOptionResponse, + received(validate.zSetSessionConfigOptionRequest), + received(validate.zSetSessionConfigOptionResponse), ), prompt: requestSpec( schema.AGENT_METHODS.session_prompt, - validate.zPromptRequest, - validate.zPromptResponse, + received(validate.zPromptRequest), + received(validate.zPromptResponse), ), listSessions: requestSpec< schema.ListSessionsRequest, @@ -2339,16 +2357,16 @@ const agentRequestSpecs = { schema.ForkSessionResponse >( schema.AGENT_METHODS.session_fork, - validate.zForkSessionRequest, - validate.zForkSessionResponse, + received(validate.zForkSessionRequest), + received(validate.zForkSessionResponse), ), resumeSession: requestSpec< schema.ResumeSessionRequest, schema.ResumeSessionResponse >( schema.AGENT_METHODS.session_resume, - validate.zResumeSessionRequest, - validate.zResumeSessionResponse, + received(validate.zResumeSessionRequest), + received(validate.zResumeSessionResponse), ), closeSession: requestSpec< schema.CloseSessionRequest, @@ -2383,8 +2401,8 @@ const agentRequestSpecs = { schema.SuggestNesResponse >( schema.AGENT_METHODS.nes_suggest, - validate.zSuggestNesRequest, - validate.zSuggestNesResponse, + received(validate.zSuggestNesRequest), + received(validate.zSuggestNesResponse), ), unstable_closeNes: requestSpec< schema.CloseNesRequest, @@ -2448,8 +2466,8 @@ const clientRequestSpecs = { schema.RequestPermissionResponse >( schema.CLIENT_METHODS.session_request_permission, - validate.zRequestPermissionRequest, - validate.zRequestPermissionResponse, + received(validate.zRequestPermissionRequest), + received(validate.zRequestPermissionResponse), ), unstable_messageMcp: requestSpec< schema.MessageMcpRequest, @@ -2464,15 +2482,15 @@ const clientRequestSpecs = { schema.CreateElicitationResponse >( schema.CLIENT_METHODS.elicitation_create, - validate.zCreateElicitationRequest, - validate.zCreateElicitationResponse, + received(validate.zCreateElicitationRequest), + received(validate.zCreateElicitationResponse), ), }; const clientNotificationSpecs = { sessionUpdate: notificationSpec( schema.CLIENT_METHODS.session_update, - validate.zUpdateSessionNotification, + received(validate.zUpdateSessionNotification), ), completeElicitation: notificationSpec( schema.CLIENT_METHODS.elicitation_complete, @@ -2786,9 +2804,9 @@ class SessionUpdateRouter { return Handled.no(message); } - const notification = validate.zUpdateSessionNotification.parse( - message.params, - ); + const notification = received( + validate.zUpdateSessionNotification, + ).parse(message.params); const { update } = notification; const isIdle = guards.SessionUpdate.isStateUpdate(update) && diff --git a/src/v2/schema/guards.gen.ts b/src/v2/schema/guards.gen.ts index f5708847..5afbf066 100644 --- a/src/v2/schema/guards.gen.ts +++ b/src/v2/schema/guards.gen.ts @@ -323,7 +323,9 @@ export const RequestPermissionSubject = { */ isCustom( value: types.RequestPermissionSubject, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return typeof tag === "string" && !["command", "tool_call"].includes(tag); }, @@ -391,7 +393,9 @@ export const ToolCallContent = { */ isCustom( value: types.ToolCallContent, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return ( typeof tag === "string" && !["content", "diff", "terminal"].includes(tag) @@ -492,7 +496,9 @@ export const ContentBlock = { */ isCustom( value: types.ContentBlock, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return ( typeof tag === "string" && @@ -588,10 +594,11 @@ export const DiffChange = { */ isCustom( value: types.DiffChange, - ): value is { operation: string; [key: string]: unknown } & Pick< - types.DiffChange, - "fileType" | "mimeType" | "_meta" - > { + ): value is ( + | { operation: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ operation: string; [key: string]: unknown }> + ) & + Pick { const tag = tagOf(value, "operation"); return ( typeof tag === "string" && @@ -655,9 +662,11 @@ export const CreateElicitationRequest = { */ isCustom( value: types.CreateElicitationRequest, - ): value is (( - types.ElicitationSessionScope | types.ElicitationRequestScope - ) & { mode: string; [key: string]: unknown }) & + ): value is ((types.ElicitationSessionScope | types.ElicitationRequestScope) & + ( + | { mode: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ mode: string; [key: string]: unknown }> + )) & Pick { const tag = tagOf(value, "mode"); return ( @@ -751,7 +760,9 @@ export const ElicitationPropertySchema = { */ isCustom( value: types.ElicitationPropertySchema, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return ( typeof tag === "string" && @@ -809,7 +820,9 @@ export const MultiSelectItems = { */ isCustom( value: types.MultiSelectItems, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return typeof tag === "string" && !["string"].includes(tag); }, @@ -864,13 +877,12 @@ export const AuthMethod = { * structural subtypes of the catch-all), so read vendor payload keys * via a widening cast: `(value as Record).someKey`. */ - isCustom(value: types.AuthMethod): value is { - type: string; - [key: string]: unknown; - } & { - methodId: types.AuthMethodId; - name: string; - } { + isCustom( + value: types.AuthMethod, + ): value is ( + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> + ) & { methodId: types.AuthMethodId; name: string } { const tag = tagOf(value, "type"); return ( typeof tag === "string" && @@ -937,10 +949,14 @@ export const SessionConfigOption = { */ isCustom( value: types.SessionConfigOption, - ): value is { type: string; [key: string]: unknown } & Pick< - types.SessionConfigOption, - "configId" | "name" | "description" | "category" | "_meta" - > { + ): value is ( + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> + ) & + Pick< + types.SessionConfigOption, + "configId" | "name" | "description" | "category" | "_meta" + > { const tag = tagOf(value, "type"); return ( typeof tag === "string" && @@ -989,7 +1005,9 @@ export const AvailableCommandInput = { */ isCustom( value: types.AvailableCommandInput, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return typeof tag === "string" && !["text"].includes(tag); }, @@ -1064,12 +1082,12 @@ export const NesSuggestion = { * structural subtypes of the catch-all), so read vendor payload keys * via a widening cast: `(value as Record).someKey`. */ - isCustom(value: types.NesSuggestion): value is { - kind: string; - [key: string]: unknown; - } & { - suggestionId: types.NesSuggestionId; - } { + isCustom( + value: types.NesSuggestion, + ): value is ( + | { kind: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ kind: string; [key: string]: unknown }> + ) & { suggestionId: types.NesSuggestionId } { const tag = tagOf(value, "kind"); return ( typeof tag === "string" && @@ -1356,7 +1374,9 @@ export const SessionUpdate = { */ isCustom( value: types.SessionUpdate, - ): value is { sessionUpdate: string; [key: string]: unknown } { + ): value is + | { sessionUpdate: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ sessionUpdate: string; [key: string]: unknown }> { const tag = tagOf(value, "sessionUpdate"); return ( typeof tag === "string" && @@ -1461,7 +1481,9 @@ export const StateUpdate = { */ isCustom( value: types.StateUpdate, - ): value is { state: string; [key: string]: unknown } { + ): value is + | { state: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ state: string; [key: string]: unknown }> { const tag = tagOf(value, "state"); return ( typeof tag === "string" && @@ -1527,12 +1549,12 @@ export const PlanUpdateContent = { * structural subtypes of the catch-all), so read vendor payload keys * via a widening cast: `(value as Record).someKey`. */ - isCustom(value: types.PlanUpdateContent): value is { - type: string; - [key: string]: unknown; - } & { - planId: types.PlanId; - } { + isCustom( + value: types.PlanUpdateContent, + ): value is ( + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> + ) & { planId: types.PlanId } { const tag = tagOf(value, "type"); return ( typeof tag === "string" && @@ -1604,7 +1626,9 @@ export const McpServer = { */ isCustom( value: types.McpServer, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return typeof tag === "string" && !["acp", "http", "stdio"].includes(tag); }, @@ -1651,7 +1675,9 @@ export const ReplayFrom = { */ isCustom( value: types.ReplayFrom, - ): value is { type: string; [key: string]: unknown } { + ): value is + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> { const tag = tagOf(value, "type"); return typeof tag === "string" && !["start"].includes(tag); }, @@ -1714,7 +1740,10 @@ export const SetSessionConfigOptionRequest = { */ isCustom( value: types.SetSessionConfigOptionRequest, - ): value is ({ type: string; [key: string]: unknown } & { value: unknown }) & + ): value is (( + | { type: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ type: string; [key: string]: unknown }> + ) & { value: unknown }) & Pick< types.SetSessionConfigOptionRequest, "sessionId" | "configId" | "_meta" @@ -1777,7 +1806,9 @@ export const RequestPermissionOutcome = { */ isCustom( value: types.RequestPermissionOutcome, - ): value is { outcome: string; [key: string]: unknown } { + ): value is + | { outcome: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ outcome: string; [key: string]: unknown }> { const tag = tagOf(value, "outcome"); return typeof tag === "string" && !["cancelled", "selected"].includes(tag); }, @@ -1849,10 +1880,11 @@ export const CreateElicitationResponse = { */ isCustom( value: types.CreateElicitationResponse, - ): value is { action: string; [key: string]: unknown } & Pick< - types.CreateElicitationResponse, - "_meta" - > { + ): value is ( + | { action: `_${string}`; [key: string]: unknown } + | types.UnknownVariant<{ action: string; [key: string]: unknown }> + ) & + Pick { const tag = tagOf(value, "action"); return ( typeof tag === "string" && !["accept", "cancel", "decline"].includes(tag) diff --git a/src/v2/schema/types.gen.ts b/src/v2/schema/types.gen.ts index 0138a478..6a1199bc 100644 --- a/src/v2/schema/types.gen.ts +++ b/src/v2/schema/types.gen.ts @@ -1,5 +1,21 @@ // This file is auto-generated by @hey-api/openapi-ts +declare const unknownVariant: unique symbol; + +/** + * A variant of an extensible union that this SDK does not know, as received + * from a peer, for example one added by a newer ACP version. + * + * Only received values have this type. A value you build is a known variant, + * checked field by field, or a custom variant whose tag starts with `_`, so + * a misspelled or missing field on a known variant is a compile error. + * Received values keep the type, so passing them on needs no conversion. The + * brand exists only in the types. + * + * @experimental + */ +export type UnknownVariant = T & { readonly [unknownVariant]: true }; + // eslint-disable-next-line @typescript-eslint/no-unused-vars type ClientOptions = { baseUrl: `${string}://${string}` | (string & {}); @@ -111,17 +127,30 @@ export type RequestPermissionSubject = | (CommandPermissionSubject & { type: "command"; }) - | { - /** - * Custom or future permission subject type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future permission subject type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future permission subject type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ); /** * Represents an upsert for a tool call that the language model has requested. @@ -248,17 +277,30 @@ export type ToolCallContent = | (Terminal & { type: "terminal"; }) - | { - /** - * Custom or future tool call content type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future tool call content type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future tool call content type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ); /** * Content blocks represent displayable information in the Agent Client Protocol. @@ -293,17 +335,30 @@ export type ContentBlock = | (EmbeddedResource & { type: "resource"; }) - | { - /** - * Custom or future content block type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future content block type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future content block type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ); /** * Optional annotations for the client. The client can use annotations to inform how objects are used or displayed @@ -636,17 +691,30 @@ export type DiffChange = ( | (DiffPathPairChange & { operation: "copy"; }) - | { - /** - * Custom or future file operation. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - operation: string; - [key: string]: unknown; - } + | ( + | { + /** + * Custom or future file operation. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + operation: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future file operation. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + operation: string; + [key: string]: unknown; + }> + ) ) & { /** * File content kind. @@ -916,17 +984,31 @@ export type CreateElicitationRequest = ( | (ElicitationUrlMode & { mode: "url"; }) - | ((ElicitationSessionScope | ElicitationRequestScope) & { - /** - * Custom or future elicitation mode. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - mode: string; - [key: string]: unknown; - }) + | ((ElicitationSessionScope | ElicitationRequestScope) & + ( + | { + /** + * Custom or future elicitation mode. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + mode: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future elicitation mode. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + mode: string; + [key: string]: unknown; + }> + )) ) & { /** * A human-readable message describing what input is needed. @@ -1055,17 +1137,30 @@ export type ElicitationPropertySchema = | (MultiSelectPropertySchema & { type: "array"; }) - | { - /** - * Custom or future elicitation property schema type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future elicitation property schema type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future elicitation property schema type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ); /** * String format types for string properties in elicitation schemas. @@ -1320,17 +1415,30 @@ export type MultiSelectItems = | (StringMultiSelectItems & { type: "string"; }) - | { - /** - * Custom or future multi-select item type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - } + | ( + | { + /** + * Custom or future multi-select item type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future multi-select item type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ) | TitledMultiSelectItems; /** @@ -2460,39 +2568,74 @@ export type AuthMethod = | (AuthMethodAgent & { type: "agent"; }) - | { - /** - * Custom or future authentication method type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - /** - * Unique identifier for this authentication method. - */ - methodId: AuthMethodId; - /** - * Human-readable name of the authentication method. - */ - name: string; - /** - * Optional description providing more details about this authentication method. - */ - description?: string | null; - /** - * The _meta property is reserved by ACP to allow clients and agents to attach additional - * metadata to their interactions. Implementations MUST NOT make assumptions about values at - * these keys. - * - * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) - */ - _meta?: { - [key: string]: unknown; - } | null; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future authentication method type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + /** + * Unique identifier for this authentication method. + */ + methodId: AuthMethodId; + /** + * Human-readable name of the authentication method. + */ + name: string; + /** + * Optional description providing more details about this authentication method. + */ + description?: string | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future authentication method type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + /** + * Unique identifier for this authentication method. + */ + methodId: AuthMethodId; + /** + * Human-readable name of the authentication method. + */ + name: string; + /** + * Optional description providing more details about this authentication method. + */ + description?: string | null; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; + [key: string]: unknown; + }> + ); /** * Typed identifier used for auth method values on the wire. @@ -2843,17 +2986,30 @@ export type SessionConfigOption = ( | (SessionConfigBoolean & { type: "boolean"; }) - | { - /** - * Custom or future session configuration option type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - } + | ( + | { + /** + * Custom or future session configuration option type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future session configuration option type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ) ) & { /** * Unique identifier for the configuration option. @@ -3033,17 +3189,30 @@ export type AvailableCommandInput = | (TextCommandInput & { type: "text"; }) - | { - /** - * Custom or future command input type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future command input type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future command input type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ); /** * All text that was typed after the command name is provided as input. @@ -3346,21 +3515,38 @@ export type NesSuggestion = | (NesSearchAndReplaceSuggestion & { kind: "searchAndReplace"; }) - | { - /** - * Custom or future NES suggestion kind. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - kind: string; - /** - * Unique identifier for accept/reject tracking. - */ - suggestionId: NesSuggestionId; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future NES suggestion kind. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + kind: `_${string}`; + /** + * Unique identifier for accept/reject tracking. + */ + suggestionId: NesSuggestionId; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future NES suggestion kind. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + kind: string; + /** + * Unique identifier for accept/reject tracking. + */ + suggestionId: NesSuggestionId; + [key: string]: unknown; + }> + ); /** * **UNSTABLE** @@ -3766,17 +3952,30 @@ export type SessionUpdate = | (SessionMessageChunk & { sessionUpdate: "session_message_chunk"; }) - | { - /** - * Custom or future session update type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - sessionUpdate: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future session update type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + sessionUpdate: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future session update type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + sessionUpdate: string; + [key: string]: unknown; + }> + ); /** * A streamed item of message content. @@ -4087,17 +4286,30 @@ export type StateUpdate = | (UnknownStateUpdate & { state: "unknown"; }) - | { - /** - * Custom or future session state. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - state: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future session state. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + state: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future session state. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + state: string; + [key: string]: unknown; + }> + ); /** * A streamed item of tool-call content. @@ -4260,21 +4472,38 @@ export type PlanUpdateContent = | (PlanMarkdown & { type: "markdown"; }) - | { - /** - * Custom or future plan update content type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - /** - * The plan ID to update. - */ - planId: PlanId; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future plan update content type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + /** + * The plan ID to update. + */ + planId: PlanId; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future plan update content type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + /** + * The plan ID to update. + */ + planId: PlanId; + [key: string]: unknown; + }> + ); /** * Unique identifier for a plan within a session. @@ -5488,17 +5717,30 @@ export type McpServer = | (McpServerStdio & { type: "stdio"; }) - | { - /** - * Custom or future MCP server transport type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future MCP server transport type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future MCP server transport type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + [key: string]: unknown; + }> + ); /** * An HTTP header to set when making requests to the MCP server. @@ -5772,27 +6014,50 @@ export type ReplayFrom = | (ReplayFromStart & { type: "start"; }) - | { - /** - * Custom or future replay cursor type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - /** - * The _meta property is reserved by ACP to allow clients and agents to attach additional - * metadata to their interactions. Implementations MUST NOT make assumptions about values at - * these keys. - * - * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) - */ - _meta?: { - [key: string]: unknown; - } | null; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future replay cursor type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future replay cursor type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + /** + * The _meta property is reserved by ACP to allow clients and agents to attach additional + * metadata to their interactions. Implementations MUST NOT make assumptions about values at + * these keys. + * + * See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/v2/draft/extensibility) + */ + _meta?: { + [key: string]: unknown; + } | null; + [key: string]: unknown; + }> + ); /** * Inclusive replay cursor requesting replay from the start of retained conversation history. @@ -5852,21 +6117,38 @@ export type SetSessionConfigOptionRequest = ( value: boolean; type: "boolean"; } - | { - /** - * Custom or future session configuration option value type. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - type: string; - /** - * Raw value payload for the custom or future value type. - */ - value: unknown; - [key: string]: unknown; - } + | ( + | { + /** + * Custom or future session configuration option value type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: `_${string}`; + /** + * Raw value payload for the custom or future value type. + */ + value: unknown; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future session configuration option value type. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + type: string; + /** + * Raw value payload for the custom or future value type. + */ + value: unknown; + [key: string]: unknown; + }> + ) ) & { /** * The ID of the session to set the configuration option for. @@ -6385,17 +6667,30 @@ export type RequestPermissionOutcome = | (SelectedPermissionOutcome & { outcome: "selected"; }) - | { - /** - * Custom or future permission outcome. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - outcome: string; - [key: string]: unknown; - }; + | ( + | { + /** + * Custom or future permission outcome. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + outcome: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future permission outcome. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + outcome: string; + [key: string]: unknown; + }> + ); /** * The user selected one of the provided options. @@ -6430,17 +6725,30 @@ export type CreateElicitationResponse = ( | { action: "cancel"; } - | { - /** - * Custom or future elicitation action. - * - * Values beginning with `_` are reserved for implementation-specific - * extensions. Unknown values that do not begin with `_` are reserved for - * future ACP variants. - */ - action: string; - [key: string]: unknown; - } + | ( + | { + /** + * Custom or future elicitation action. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + action: `_${string}`; + [key: string]: unknown; + } + | UnknownVariant<{ + /** + * Custom or future elicitation action. + * + * Values beginning with `_` are reserved for implementation-specific + * extensions. Unknown values that do not begin with `_` are reserved for + * future ACP variants. + */ + action: string; + [key: string]: unknown; + }> + ) ) & { /** * The _meta property is reserved by ACP to allow clients and agents to attach additional