mirror of
https://github.com/agentclientprotocol/claude-agent-acp.git
synced 2026-10-27 04:30:59 +00:00
chore(deps): bump the minor group with 2 updates (#840)
Bumps the minor group with 2 updates: [@agentclientprotocol/sdk](https://github.com/agentclientprotocol/typescript-sdk) and [hono](https://github.com/honojs/hono). Updates `@agentclientprotocol/sdk` from 1.1.0 to 1.2.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/agentclientprotocol/typescript-sdk/releases">@agentclientprotocol/sdk's releases</a>.</em></p> <blockquote> <h2>v1.2.0</h2> <h2><a href="https://github.com/agentclientprotocol/typescript-sdk/compare/v1.1.0...v1.2.0">1.2.0</a> (2026-07-06)</h2> <h3>Features</h3> <ul> <li>Update schema to 1.17.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/204">#204</a>) (<a href="fa8b5bd426">fa8b5bd</a>)</li> <li>Update schema to 1.19.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/213">#213</a>) (<a href="6599433640">6599433</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li>make ndJsonStream receive path linear in message size (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/210">#210</a>) (<a href="2fc41d2822">2fc41d2</a>)</li> <li>unify JSON-RPC message validation policy across transports (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/212">#212</a>) (<a href="5e01eb2ac2">5e01eb2</a>), closes <a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/211">#211</a></li> </ul> </blockquote> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/agentclientprotocol/typescript-sdk/blob/main/CHANGELOG.md">@agentclientprotocol/sdk's changelog</a>.</em></p> <blockquote> <h2><a href="https://github.com/agentclientprotocol/typescript-sdk/compare/v1.1.0...v1.2.0">1.2.0</a> (2026-07-06)</h2> <h3>Features</h3> <ul> <li>Update schema to 1.17.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/204">#204</a>) (<a href="fa8b5bd426">fa8b5bd</a>)</li> <li>Update schema to 1.19.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/213">#213</a>) (<a href="6599433640">6599433</a>)</li> </ul> <h3>Bug Fixes</h3> <ul> <li>make ndJsonStream receive path linear in message size (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/210">#210</a>) (<a href="2fc41d2822">2fc41d2</a>)</li> <li>unify JSON-RPC message validation policy across transports (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/212">#212</a>) (<a href="5e01eb2ac2">5e01eb2</a>), closes <a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/211">#211</a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="f587c0be56"><code>f587c0b</code></a> chore(main): release 1.2.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/205">#205</a>)</li> <li><a href="6599433640"><code>6599433</code></a> feat: Update schema to 1.19.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/213">#213</a>)</li> <li><a href="5e01eb2ac2"><code>5e01eb2</code></a> fix: unify JSON-RPC message validation policy across transports (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/212">#212</a>)</li> <li><a href="2fc41d2822"><code>2fc41d2</code></a> fix: make ndJsonStream receive path linear in message size (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/210">#210</a>)</li> <li><a href="e73e42af1f"><code>e73e42a</code></a> chore(deps): bump crate-ci/typos from 1.47.2 to 1.48.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/207">#207</a>)</li> <li><a href="f733390ebe"><code>f733390</code></a> chore(deps-dev): bump the minor group with 8 updates (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/208">#208</a>)</li> <li><a href="fa8b5bd426"><code>fa8b5bd</code></a> feat: Update schema to 1.17.0 (<a href="https://redirect.github.com/agentclientprotocol/typescript-sdk/issues/204">#204</a>)</li> <li>See full diff in <a href="https://github.com/agentclientprotocol/typescript-sdk/compare/v1.1.0...v1.2.0">compare view</a></li> </ul> </details> <br /> Updates `hono` from 4.12.27 to 4.12.28 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/honojs/hono/releases">hono's releases</a>.</em></p> <blockquote> <h2>v4.12.28</h2> <h2>What's Changed</h2> <ul> <li>fix(serve-static): treat empty string content as found by <a href="https://github.com/yusukebe"><code>@yusukebe</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5062">honojs/hono#5062</a></li> <li>docs(MIGRATION): fix req.raw.headers reference (property, not method) by <a href="https://github.com/EduardF1"><code>@EduardF1</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5047">honojs/hono#5047</a></li> <li>chore: don't publish <code>*.tsbuildinfo</code> by <a href="https://github.com/yusukebe"><code>@yusukebe</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5066">honojs/hono#5066</a></li> <li>fix(utils/body,validator): normalize Content-Type media type for case-insensitive matching by <a href="https://github.com/yusukebe"><code>@yusukebe</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5067">honojs/hono#5067</a></li> <li>fix: avoid circular dependency between body.ts and request.ts by <a href="https://github.com/usualoma"><code>@usualoma</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5071">honojs/hono#5071</a></li> <li>fix(bun): report the requested subprotocol on WSContext.protocol by <a href="https://github.com/greymoth-jp"><code>@greymoth-jp</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5059">honojs/hono#5059</a></li> <li>chore: bump <code>devDependencies</code> by <a href="https://github.com/yusukebe"><code>@yusukebe</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5085">honojs/hono#5085</a></li> <li>fix(aws-lambda): detect V2 events by request context, not rawPath alone by <a href="https://github.com/VihaanAgarwal"><code>@VihaanAgarwal</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5033">honojs/hono#5033</a></li> <li>docs(context-storage): fix JSDoc by <a href="https://github.com/yusukebe"><code>@yusukebe</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/5086">honojs/hono#5086</a></li> </ul> <h2>New Contributors</h2> <ul> <li><a href="https://github.com/EduardF1"><code>@EduardF1</code></a> made their first contribution in <a href="https://redirect.github.com/honojs/hono/pull/5047">honojs/hono#5047</a></li> <li><a href="https://github.com/greymoth-jp"><code>@greymoth-jp</code></a> made their first contribution in <a href="https://redirect.github.com/honojs/hono/pull/5059">honojs/hono#5059</a></li> <li><a href="https://github.com/VihaanAgarwal"><code>@VihaanAgarwal</code></a> made their first contribution in <a href="https://redirect.github.com/honojs/hono/pull/5033">honojs/hono#5033</a></li> </ul> <p><strong>Full Changelog</strong>: <a href="https://github.com/honojs/hono/compare/v4.12.27...v4.12.28">https://github.com/honojs/hono/compare/v4.12.27...v4.12.28</a></p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="626b185d0e"><code>626b185</code></a> 4.12.28</li> <li><a href="d6b1d32a69"><code>d6b1d32</code></a> docs(context-storage): fix JSDoc (<a href="https://redirect.github.com/honojs/hono/issues/5086">#5086</a>)</li> <li><a href="45b081b9b9"><code>45b081b</code></a> fix(aws-lambda): detect V2 events by request context, not rawPath alone (<a href="https://redirect.github.com/honojs/hono/issues/5033">#5033</a>)</li> <li><a href="a05813c058"><code>a05813c</code></a> chore: bump <code>devDependencies</code> (<a href="https://redirect.github.com/honojs/hono/issues/5085">#5085</a>)</li> <li><a href="872997d283"><code>872997d</code></a> fix(bun): report the requested subprotocol on WSContext.protocol (<a href="https://redirect.github.com/honojs/hono/issues/5059">#5059</a>)</li> <li><a href="82b321b5b0"><code>82b321b</code></a> fix: avoid circular dependency between body.ts and request.ts (<a href="https://redirect.github.com/honojs/hono/issues/5071">#5071</a>)</li> <li><a href="b20d4225c9"><code>b20d422</code></a> fix(utils/body,validator): normalize Content-Type media type for case-insensi...</li> <li><a href="9728702911"><code>9728702</code></a> chore: don't publish <code>*.tsbuildinfo</code> (<a href="https://redirect.github.com/honojs/hono/issues/5066">#5066</a>)</li> <li><a href="ed8795e5cc"><code>ed8795e</code></a> docs(MIGRATION): fix req.raw.headers reference (property, not method) (<a href="https://redirect.github.com/honojs/hono/issues/5047">#5047</a>)</li> <li><a href="03a9416a13"><code>03a9416</code></a> fix(serve-static): treat empty string content as found (<a href="https://redirect.github.com/honojs/hono/issues/5062">#5062</a>)</li> <li>See full diff in <a href="https://github.com/honojs/hono/compare/v4.12.27...v4.12.28">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Ben Brandt <benjamin.j.brandt@gmail.com>
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "0.56.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.1.0",
|
||||
"@agentclientprotocol/sdk": "1.2.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.201",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
@@ -36,9 +36,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@agentclientprotocol/sdk": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.1.0.tgz",
|
||||
"integrity": "sha512-NT2KqphUJ3w6EksUL51ZhJgIYgq/ZLGcBPkyMKgRSO5PMVwe9DnKKX+Htnvk6KHh6dUuh34UHK4gKp+4te1Mdg==",
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@agentclientprotocol/sdk/-/sdk-1.2.0.tgz",
|
||||
"integrity": "sha512-rJ3766M2eouZsNCaK21JbPD++JLBZ0PWq38dzj1sA4/i0njaQEkiC2F5v7MlizFW6akX+qu5Ks8R00Xw+YMHKA==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
@@ -2371,9 +2371,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.12.27",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.27.tgz",
|
||||
"integrity": "sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==",
|
||||
"version": "4.12.28",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.12.28.tgz",
|
||||
"integrity": "sha512-YwUvVpSF7m1yOblFPrU3Hbo8XhPheBoiyfGuII6z19LnOr6JpDnyyp7LFNrfV56wS8tpvtBFGRISHN02pDdLOA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"node": ">=22"
|
||||
},
|
||||
"dependencies": {
|
||||
"@agentclientprotocol/sdk": "1.1.0",
|
||||
"@agentclientprotocol/sdk": "1.2.0",
|
||||
"@anthropic-ai/claude-agent-sdk": "0.3.201",
|
||||
"zod": "^3.25.0 || ^4.0.0"
|
||||
},
|
||||
|
||||
@@ -2,6 +2,8 @@ import { randomUUID } from "node:crypto";
|
||||
import type {
|
||||
CreateElicitationRequest,
|
||||
CreateElicitationResponse,
|
||||
ElicitationAcceptAction,
|
||||
ElicitationContentValue,
|
||||
ElicitationPropertySchema,
|
||||
ElicitationSchema,
|
||||
EnumOption,
|
||||
@@ -68,6 +70,45 @@ export function mcpElicitationToCreateRequest(
|
||||
};
|
||||
}
|
||||
|
||||
/** A wire value allowed in elicitation form content. */
|
||||
function isElicitationContentValue(value: unknown): value is ElicitationContentValue {
|
||||
return (
|
||||
typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "boolean" ||
|
||||
(Array.isArray(value) && value.every((item) => typeof item === "string"))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Content of an accepted elicitation response.
|
||||
*
|
||||
* The response union includes a custom/future-action variant whose `action` is
|
||||
* plain `string`, so a positive `=== "accept"` check can never narrow it away
|
||||
* and `content` reads as `unknown`. Rather than asserting which union member
|
||||
* this is, validate the values: the SDK's own wire validation has the same
|
||||
* blind spot (an accept response with malformed content parses via the
|
||||
* catch-all variant), so ill-typed values are dropped here either way.
|
||||
*/
|
||||
function acceptedElicitationContent(
|
||||
response: CreateElicitationResponse,
|
||||
): NonNullable<ElicitationAcceptAction["content"]> {
|
||||
if (response.action !== "accept") {
|
||||
return {};
|
||||
}
|
||||
const { content } = response;
|
||||
if (!content || typeof content !== "object") {
|
||||
return {};
|
||||
}
|
||||
const validated: NonNullable<ElicitationAcceptAction["content"]> = {};
|
||||
for (const [key, value] of Object.entries(content)) {
|
||||
if (isElicitationContentValue(value)) {
|
||||
validated[key] = value;
|
||||
}
|
||||
}
|
||||
return validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an ACP elicitation response back to the MCP `ElicitResult` the SDK expects
|
||||
* to hand back to the requesting server.
|
||||
@@ -77,7 +118,7 @@ export function createElicitationResponseToElicitResult(
|
||||
): ElicitationResult {
|
||||
switch (response.action) {
|
||||
case "accept":
|
||||
return { action: "accept", content: response.content ?? {} };
|
||||
return { action: "accept", content: acceptedElicitationContent(response) };
|
||||
case "decline":
|
||||
return { action: "decline" };
|
||||
case "cancel":
|
||||
@@ -126,33 +167,13 @@ function questionCustomFieldKey(index: number): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* `_meta` key under which a bridged enum option carries its structured
|
||||
* `description`/`preview`, since ACP's `EnumOption` has no field for either.
|
||||
* Namespaced like the agent's other `_meta` extensions (`_claude/...`).
|
||||
* `_meta` key under which a bridged enum option carries its `preview`, the one
|
||||
* option field ACP's `EnumOption` still has no slot for (descriptions are
|
||||
* first-class as of schema 1.19). Namespaced like the agent's other `_meta`
|
||||
* extensions (`_claude/...`).
|
||||
*/
|
||||
const OPTION_META_KEY = "_claude/askUserQuestionOption";
|
||||
|
||||
/** A single option as supplied by the AskUserQuestion tool. */
|
||||
type AskUserQuestionOption = AskUserQuestion["options"][number];
|
||||
|
||||
/**
|
||||
* Build the `_meta` payload that carries an option's structured `description`
|
||||
* and optional `preview` for clients that can render them as distinct fields.
|
||||
* Returns `undefined` when neither is present, so we don't emit an empty `_meta`.
|
||||
*/
|
||||
function askUserQuestionOptionMeta(
|
||||
option: AskUserQuestionOption,
|
||||
): NonNullable<EnumOption["_meta"]> | undefined {
|
||||
const detail: { description?: string; preview?: string } = {};
|
||||
if (option.description) {
|
||||
detail.description = option.description;
|
||||
}
|
||||
if (option.preview) {
|
||||
detail.preview = option.preview;
|
||||
}
|
||||
return Object.keys(detail).length > 0 ? { [OPTION_META_KEY]: detail } : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the AskUserQuestion tool's questions as an ACP form elicitation.
|
||||
*
|
||||
@@ -160,7 +181,8 @@ function askUserQuestionOptionMeta(
|
||||
* question text, so the question text appears in exactly one place per field.
|
||||
* Single-select questions use a titled `oneOf` enum; multi-select questions use
|
||||
* an array with a titled `anyOf` item enum. The enum `const` is always the
|
||||
* option label, since that is what the tool records as the answer.
|
||||
* option label, since that is what the tool records as the answer; an option's
|
||||
* secondary text travels in the enum option's own `description` field.
|
||||
*
|
||||
* Each question is followed by its own optional free-text "custom answer" field
|
||||
* (`question_<n>_custom`), mirroring the CLI's per-question "Other" box: the
|
||||
@@ -180,19 +202,16 @@ export function askUserQuestionsToCreateRequest(
|
||||
const options: EnumOption[] = question.options.map((option) => {
|
||||
const enumOption: EnumOption = {
|
||||
const: option.label,
|
||||
// `title` stays a flattened "label — description" string so clients that
|
||||
// only read `const`/`title` still surface the description. Clients that
|
||||
// understand the `_meta` below should prefer its structured fields (and
|
||||
// treat `const` as the clean label) rather than parsing this title.
|
||||
title: option.description ? `${option.label} — ${option.description}` : option.label,
|
||||
title: option.label,
|
||||
};
|
||||
// The SDK option carries a `description` (secondary text) and an optional
|
||||
// `preview` (mockups, code snippets, comparisons shown on focus). Neither
|
||||
// has a structural slot in `EnumOption`, so forward them under ACP's
|
||||
// reserved `_meta` extension point for clients that can render them.
|
||||
const meta = askUserQuestionOptionMeta(option);
|
||||
if (meta) {
|
||||
enumOption._meta = meta;
|
||||
if (option.description) {
|
||||
enumOption.description = option.description;
|
||||
}
|
||||
// The SDK option's `preview` (mockups, code snippets, comparisons shown
|
||||
// on focus) still has no structural slot in `EnumOption`, so forward it
|
||||
// under ACP's reserved `_meta` extension point for clients that render it.
|
||||
if (option.preview) {
|
||||
enumOption._meta = { [OPTION_META_KEY]: { preview: option.preview } };
|
||||
}
|
||||
return enumOption;
|
||||
});
|
||||
@@ -243,22 +262,23 @@ export type AskUserQuestionOutcome =
|
||||
* custom-answer field (`question_<n>_custom`) takes precedence over that
|
||||
* question's selection, since the user typed their own answer instead of
|
||||
* picking one. Decline yields empty answers (the model is told the user skipped
|
||||
* rather than the turn aborting); cancel aborts the tool call.
|
||||
* rather than the turn aborting); cancel — and any custom/future action we
|
||||
* don't understand — aborts the tool call.
|
||||
*/
|
||||
export function applyAskElicitationResponse(
|
||||
response: CreateElicitationResponse,
|
||||
toolInput: Record<string, unknown>,
|
||||
questions: AskUserQuestion[],
|
||||
): AskUserQuestionOutcome {
|
||||
if (response.action === "cancel") {
|
||||
return { action: "cancel" };
|
||||
}
|
||||
|
||||
if (response.action === "decline") {
|
||||
return { action: "answered", updatedInput: { ...toolInput, answers: {} } };
|
||||
}
|
||||
|
||||
const content = response.content ?? {};
|
||||
if (response.action !== "accept") {
|
||||
return { action: "cancel" };
|
||||
}
|
||||
|
||||
const content = acceptedElicitationContent(response);
|
||||
// Typed against the tool's own output schema so the answer/response shapes
|
||||
// stay in sync with what the built-in tool's call() expects to read back.
|
||||
const answers: AskUserQuestionOutput["answers"] = {};
|
||||
@@ -372,7 +392,7 @@ export function refusalFallbackToCreateRequest(
|
||||
sessionId,
|
||||
message:
|
||||
`${prompt.originalModel} declined this request${category}. ` +
|
||||
`Retry with ${prompt.fallbackModel}? The session would continue on ${prompt.fallbackModel}.` +
|
||||
`Retry with ${prompt.fallbackModel}?` +
|
||||
guidance,
|
||||
requestedSchema: {
|
||||
type: "object",
|
||||
@@ -380,8 +400,16 @@ export function refusalFallbackToCreateRequest(
|
||||
[REFUSAL_FALLBACK_CHOICE_KEY]: {
|
||||
type: "string",
|
||||
oneOf: [
|
||||
{ const: RETRY_FALLBACK_RESULT, title: `Retry with ${prompt.fallbackModel}` },
|
||||
{ const: KEEP_REFUSAL_RESULT, title: "Keep the refusal" },
|
||||
{
|
||||
const: RETRY_FALLBACK_RESULT,
|
||||
title: `Retry with ${prompt.fallbackModel}`,
|
||||
description: `The session continues on ${prompt.fallbackModel}.`,
|
||||
},
|
||||
{
|
||||
const: KEEP_REFUSAL_RESULT,
|
||||
title: "Keep the refusal",
|
||||
description: "You can send a new message.",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -397,9 +425,6 @@ export function refusalFallbackToCreateRequest(
|
||||
* switch the user didn't ask for.
|
||||
*/
|
||||
export function refusalFallbackResultFromResponse(response: CreateElicitationResponse): string {
|
||||
if (response.action !== "accept") {
|
||||
return KEEP_REFUSAL_RESULT;
|
||||
}
|
||||
const choice = response.content?.[REFUSAL_FALLBACK_CHOICE_KEY];
|
||||
const choice = acceptedElicitationContent(response)[REFUSAL_FALLBACK_CHOICE_KEY];
|
||||
return choice === RETRY_FALLBACK_RESULT ? RETRY_FALLBACK_RESULT : KEEP_REFUSAL_RESULT;
|
||||
}
|
||||
|
||||
@@ -204,7 +204,9 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
params: CreateElicitationRequest,
|
||||
): Promise<CreateElicitationResponse> {
|
||||
this.elicitations.push(params);
|
||||
if (params.mode !== "form") {
|
||||
// The `in` check also excludes the custom/future-mode variant, whose
|
||||
// `mode: string` would otherwise survive the literal comparison.
|
||||
if (params.mode !== "form" || !("requestedSchema" in params)) {
|
||||
return { action: "decline" };
|
||||
}
|
||||
// Accept the first option of every choice field (skip the free-text one).
|
||||
@@ -436,7 +438,9 @@ describe.skipIf(!process.env.RUN_INTEGRATION_TESTS)("ACP subprocess integration"
|
||||
// which confirms our interception path produced it rather than some other
|
||||
// mechanism.
|
||||
const properties =
|
||||
elicitation.mode === "form" ? Object.keys(elicitation.requestedSchema.properties ?? {}) : [];
|
||||
elicitation.mode === "form" && "requestedSchema" in elicitation
|
||||
? Object.keys(elicitation.requestedSchema.properties ?? {})
|
||||
: [];
|
||||
expect(properties).toContain("question_0");
|
||||
expect(properties).toContain("question_0_custom");
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ const SESSION_ID = "session-1";
|
||||
/**
|
||||
* Build a question matching the SDK's (strict) AskUserQuestion schema while
|
||||
* keeping test sites terse. Option descriptions default to "" (the tool always
|
||||
* provides one); pass a description to exercise the "label — description" title.
|
||||
* provides one); pass a description to exercise the enum option's description.
|
||||
*/
|
||||
function mkQuestion(
|
||||
question: string,
|
||||
@@ -127,6 +127,14 @@ describe("createElicitationResponseToElicitResult", () => {
|
||||
createElicitationResponseToElicitResult({ action: "cancel" } as CreateElicitationResponse),
|
||||
).toEqual({ action: "cancel" });
|
||||
});
|
||||
|
||||
it("maps a custom/future action to cancel", () => {
|
||||
expect(
|
||||
createElicitationResponseToElicitResult({
|
||||
action: "_zed/snooze",
|
||||
} as CreateElicitationResponse),
|
||||
).toEqual({ action: "cancel" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("extractAskUserQuestions", () => {
|
||||
@@ -190,14 +198,10 @@ describe("askUserQuestionsToCreateRequest", () => {
|
||||
title: "Library",
|
||||
description: undefined,
|
||||
oneOf: [
|
||||
{
|
||||
const: "date-fns",
|
||||
title: "date-fns — Lightweight",
|
||||
// Structured description forwarded under `_meta` so clients can render
|
||||
// it as secondary text instead of parsing it out of the title.
|
||||
_meta: { "_claude/askUserQuestionOption": { description: "Lightweight" } },
|
||||
},
|
||||
// No description → no `_meta` emitted.
|
||||
// The description travels in the enum option's first-class field; the
|
||||
// title stays the clean label (no "label — description" flattening).
|
||||
{ const: "date-fns", title: "date-fns", description: "Lightweight" },
|
||||
// No description → the field is omitted entirely.
|
||||
{ const: "luxon", title: "luxon" },
|
||||
],
|
||||
});
|
||||
@@ -205,7 +209,7 @@ describe("askUserQuestionsToCreateRequest", () => {
|
||||
expect(schema.properties?.["Which library?"]).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards option description and preview under _meta for rich clients", () => {
|
||||
it("carries descriptions structurally and forwards previews under _meta", () => {
|
||||
const questions = [
|
||||
mkQuestion("Which layout?", [
|
||||
{
|
||||
@@ -226,24 +230,22 @@ describe("askUserQuestionsToCreateRequest", () => {
|
||||
).requestedSchema;
|
||||
const oneOf = (schema.properties?.["question_0"] as { oneOf: EnumOption[] }).oneOf;
|
||||
|
||||
// Both description and preview travel structurally; the title still flattens
|
||||
// the description for clients that only read const/title.
|
||||
// The description uses the enum option's own field; only the preview —
|
||||
// which still has no structural slot — rides under `_meta`.
|
||||
expect(oneOf[0]).toEqual({
|
||||
const: "Grid",
|
||||
title: "Grid — Cards in a responsive grid",
|
||||
title: "Grid",
|
||||
description: "Cards in a responsive grid",
|
||||
_meta: {
|
||||
"_claude/askUserQuestionOption": {
|
||||
description: "Cards in a responsive grid",
|
||||
preview: "```\n[ ] [ ] [ ]\n[ ] [ ] [ ]\n```",
|
||||
},
|
||||
},
|
||||
});
|
||||
// Description only → preview omitted from _meta.
|
||||
expect(oneOf[1]._meta).toEqual({
|
||||
"_claude/askUserQuestionOption": { description: "Stacked rows" },
|
||||
});
|
||||
// Neither → no _meta at all.
|
||||
expect(oneOf[2]._meta).toBeUndefined();
|
||||
// Description only → no _meta needed.
|
||||
expect(oneOf[1]).toEqual({ const: "List", title: "List", description: "Stacked rows" });
|
||||
// Neither → just the label.
|
||||
expect(oneOf[2]).toEqual({ const: "Plain", title: "Plain" });
|
||||
});
|
||||
|
||||
it("includes a per-question optional free-text custom-answer field", () => {
|
||||
@@ -379,6 +381,13 @@ describe("applyAskElicitationResponse", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("returns cancel for a custom/future action it does not understand", () => {
|
||||
const response = { action: "_zed/snooze" } as CreateElicitationResponse;
|
||||
expect(applyAskElicitationResponse(response, toolInput, questions)).toEqual({
|
||||
action: "cancel",
|
||||
});
|
||||
});
|
||||
|
||||
it("omits answers for questions the user left unanswered", () => {
|
||||
const response = {
|
||||
action: "accept",
|
||||
@@ -450,11 +459,14 @@ describe("refusalFallbackToCreateRequest", () => {
|
||||
|
||||
const choice = request.requestedSchema.properties?.choice as {
|
||||
type: string;
|
||||
oneOf: Array<{ const: string; title: string }>;
|
||||
oneOf: Array<{ const: string; title: string; description?: string }>;
|
||||
};
|
||||
expect(choice.type).toBe("string");
|
||||
expect(choice.oneOf.map((o) => o.const)).toEqual(["retry_fallback", "cancelled"]);
|
||||
expect(choice.oneOf[0].title).toContain("claude-opus-4-8");
|
||||
// Each option explains its consequence in the description field.
|
||||
expect(choice.oneOf[0].description).toContain("claude-opus-4-8");
|
||||
expect(choice.oneOf[1].description).toBeTruthy();
|
||||
});
|
||||
|
||||
it("omits the category marker and appends guidance when provided", () => {
|
||||
|
||||
Reference in New Issue
Block a user