1
>
/*---------------------------------------------------------------------------------------------
claudeElicitation.ts
2
>
* Copyright (c) Microsoft Corporation. All rights reserved.
3
>
* Licensed under the MIT License. See License.txt in the project root for license information.
4
>
*--------------------------------------------------------------------------------------------*/
5
>
6
>
import type { ElicitationRequest, ElicitationResult } from '@anthropic-ai/claude-agent-sdk';
7
>
import type { PrimitiveSchemaDefinition } from '@modelcontextprotocol/sdk/types.js';
8
>
import { isObject, isString } from '../../../../base/common/types.js';
9
>
import { vArray, vNumber, vObj, vOptionalProp, vString, vUnknown, type ValidatorType } from '../../../../base/common/validation.js';
10
>
import { ChatInputAnswerState, ChatInputAnswerValueKind, ChatInputQuestionKind, ChatInputResponseKind, type ChatInputAnswer, type ChatInputOption, type ChatInputQuestion, type ChatInputRequest } from '../../common/state/sessionState.js';
11
>
12
>
/**
13
>
* Pure projections between the Claude SDK's MCP elicitation request/response
14
>
* and the agentHost workbench protocol.
15
>
*
16
>
* When an MCP server calls `elicit/create`, the SDK invokes
17
>
* `Options.onElicitation` with an {@link ElicitationRequest}. The agent surfaces
18
>
* it as structured user input (a {@link ChatInputRequest}, the same channel
19
>
* `AskUserQuestion` uses — NOT the permission gate) and translates the user's
20
>
* answer back into the SDK's {@link ElicitationResult}. This module owns those
21
>
* projections so they can be unit-tested without standing up an agent.
22
>
*
23
>
* Unlike the Codex provider — whose `requestedSchema` is a strongly-typed
24
>
* generated schema — the Claude SDK delivers `requestedSchema` as an untyped
25
>
* `Record<string, unknown>`. Each field is runtime-validated with the base-layer
26
>
* {@link vObj} validator ({@link vElicitationField}), which drops malformed
27
>
* fields instead of mis-projecting or throwing. The field type is *derived* from
28
>
* that validator (not hand-rolled) and cross-checked against the MCP SDK's
29
>
* authoritative {@link PrimitiveSchemaDefinition} by
30
>
* {@link _assertElicitationFieldCoversSchema} (which catches an incompatible
31
>
* reshape of a covered field, though not a purely additive new variant). The
32
>
* base-layer validator is used rather than the SDK's own zod schema because this
33
>
* module is loaded by the unit-test renderer, where a runtime
34
>
* `@modelcontextprotocol/sdk` import does not resolve (all SDK runtime access
35
>
* goes through `IClaudeAgentSdkService`).
36
>
*/
37
>
38
>
/** Value the SDK accepts back for a single elicited field. */
39
>
type ElicitationFieldValue = NonNullable<ElicitationResult['content']>[string];
40
>
41
>
/** A `{ const, title? }` option, shared by `oneOf` and array `items.anyOf`. */
42
>
const vTitledOption = vObj({ const: vString(), title: vOptionalProp(vString()) });
43
>
44
>
/**
45
>
* Lenient runtime validator for a single elicitation schema field. Structure is
46
>
* validated (a present `enum` must be a string array, `minimum` a number, …) so
47
>
* a malformed field is dropped rather than mis-projected; value-level
48
>
* constraints (e.g. `format`, `type`) stay permissive so real-world schema
49
>
* variation still renders. {@link IElicitationField} is derived from this, and
50
>
* {@link _assertElicitationFieldCoversSchema} pins it to the MCP SDK's
51
>
* {@link PrimitiveSchemaDefinition}.
52
>
*/
53
>
const vElicitationField = vObj({
54
>
type: vOptionalProp(vString()),
55
>
title: vOptionalProp(vString()),
56
>
description: vOptionalProp(vString()),
57
>
format: vOptionalProp(vString()),
58
>
default: vOptionalProp(vUnknown()),
59
>
minimum: vOptionalProp(vNumber()),
60
>
maximum: vOptionalProp(vNumber()),
61
>
minLength: vOptionalProp(vNumber()),
62
>
maxLength: vOptionalProp(vNumber()),
63
>
minItems: vOptionalProp(vNumber()),
64
>
maxItems: vOptionalProp(vNumber()),
65
>
enum: vOptionalProp(vArray(vString())),
66
>
enumNames: vOptionalProp(vArray(vString())),
67
>
oneOf: vOptionalProp(vArray(vTitledOption)),
68
>
items: vOptionalProp(vObj({
69
>
enum: vOptionalProp(vArray(vString())),
70
>
anyOf: vOptionalProp(vArray(vTitledOption)),
71
>
})),
72
>
});
73
>
74
>
type IElicitationField = ValidatorType<typeof vElicitationField>;
75
>
76
>
/**
77
>
* Compile-time guard: every member of the MCP SDK's authoritative
78
>
* {@link PrimitiveSchemaDefinition} union must be assignable to
79
>
* {@link IElicitationField}. This catches an *incompatible reshape* of a field
80
>
* we already project (e.g. the SDK retyping `enum` from `string[]` to
81
>
* `number[]`) by failing to compile. It does NOT catch purely additive changes
82
>
* — a brand-new union member or keyword stays structurally assignable to this
83
>
* all-optional view and would be silently ignored by the projection until a
84
>
* human notices the new shape. It is type-only: never called, erased at runtime.
85
>
*/
86
function _assertElicitationFieldCoversSchema(field: PrimitiveSchemaDefinition): IElicitationField {
87
return field;
88
}
90
>
/**
91
>
* Reshaped, validated view of the `form`-mode `requestedSchema`: the schema's
92
>
* `properties` record flattened into ordered `[name, field]` tuples, plus its
93
>
* `required` list as a set for O(1) per-field lookup during projection.
94
>
*/
95
>
interface IParsedElicitationSchema {
96
>
readonly fields: ReadonlyArray<readonly [string, IElicitationField]>;
97
>
readonly required: ReadonlySet<string>;
98
>
}
99
>
100
>
/**
101
>
* Narrow the untyped `requestedSchema` into an ordered list of runtime-validated
102
>
* fields plus the required set. Fields that fail {@link vElicitationField}
103
>
* validation are dropped. Returns `undefined` when the schema is absent or has no
104
>
* usable `properties` object, so the caller can fall back to a message-only
105
>
* request.
106
>
*/
107
function parseElicitationSchema(schema: unknown): IParsedElicitationSchema | undefined {
108
if (!isObject(schema)) {