From 2e9d74167db87fd7f7f7b10b17815b79abefea0d Mon Sep 17 00:00:00 2001 From: murashit Date: Sun, 21 Jun 2026 15:55:02 +0900 Subject: [PATCH] Project pending requests into domain models --- src/app-server/protocol/server-requests.ts | 180 ++-------- src/domain/pending-requests/model.ts | 308 ++++++++++++++++++ .../chat/app-server/inbound/handler.ts | 2 +- .../chat/app-server/inbound/routing.ts | 2 +- .../chat/app-server/requests/approval.ts | 46 +-- .../app-server/requests/mcp-elicitation.ts | 22 +- .../chat/app-server/requests/user-input.ts | 10 +- .../application/pending-requests/block.ts | 4 +- .../pending-request-actions.ts | 2 +- .../application/pending-requests/state.ts | 2 +- .../chat/domain/pending-requests/approval.ts | 2 +- .../domain/pending-requests/result-items.ts | 2 +- .../domain/pending-requests/signatures.ts | 2 +- .../pending-requests/approval-view.ts | 2 +- .../presentation/pending-requests/snapshot.ts | 2 +- .../pending-requests/view-model.ts | 2 +- .../chat/ui/message-stream/context.ts | 2 +- .../message-stream/pending-request-block.tsx | 2 +- .../protocol/server-requests/approval.test.ts | 2 +- .../server-requests/mcp-elicitation.test.ts | 2 +- .../server-requests/user-input.test.ts | 2 +- .../message-stream/pending-requests.test.tsx | 6 +- .../chat/ui/message-stream/test-helpers.tsx | 2 +- 23 files changed, 363 insertions(+), 245 deletions(-) create mode 100644 src/domain/pending-requests/model.ts diff --git a/src/app-server/protocol/server-requests.ts b/src/app-server/protocol/server-requests.ts index 5b9ce43a..c53c3db3 100644 --- a/src/app-server/protocol/server-requests.ts +++ b/src/app-server/protocol/server-requests.ts @@ -1,159 +1,42 @@ -import type { RequestId } from "../../generated/app-server/RequestId"; import type { ServerRequest } from "../../generated/app-server/ServerRequest"; - -type SimpleApprovalDecision = "accept" | "acceptForSession" | "decline" | "cancel"; - -type AppServerCommandApprovalDecision = - | SimpleApprovalDecision - | { acceptWithExecpolicyAmendment: unknown } - | { applyNetworkPolicyAmendment: { network_policy_amendment: { action: "allow" | "deny"; [key: string]: unknown } } }; - -type AppServerApprovalAction = "accept" | "accept-session" | "decline" | "cancel" | AppServerCommandApprovalDecisionAction; - -interface AppServerCommandApprovalDecisionAction { - kind: "command-decision"; - decision: AppServerCommandApprovalDecision; -} +import type { + ApprovalAction, + CommandApprovalDecision, + McpElicitationAction, + McpElicitationContentValue, + PendingApproval, + PendingMcpElicitation, + PendingMcpElicitationField, + PendingMcpElicitationOption, + PendingUserInput, +} from "../../domain/pending-requests/model"; type AppServerRequestByMethod = Extract; -type AppServerRequestParams = AppServerRequestByMethod["params"]; - -type AppServerCommandApprovalParams = AppServerRequestParams<"item/commandExecution/requestApproval">; - -type AppServerFileChangeApprovalParams = Omit, "reason" | "grantRoot"> & { - reason: string | null; - grantRoot: string | null; -}; - -type AppServerPermissionsApprovalParams = AppServerRequestParams<"item/permissions/requestApproval">; - -export type AppServerApproval = - | { - requestId: RequestId; - method: "item/commandExecution/requestApproval"; - params: AppServerCommandApprovalParams; - } - | { - requestId: RequestId; - method: "item/fileChange/requestApproval"; - params: AppServerFileChangeApprovalParams; - } - | { - requestId: RequestId; - method: "item/permissions/requestApproval"; - params: AppServerPermissionsApprovalParams; - }; interface AppServerGrantedPermissionProfile { network?: unknown; fileSystem?: unknown; } -type AppServerApprovalResponseSource = - | { method: "item/commandExecution/requestApproval" } - | { method: "item/fileChange/requestApproval" } - | { method: "item/permissions/requestApproval"; params: { permissions: { network?: unknown; fileSystem?: unknown } } }; - export type AppServerApprovalResponse = - | { decision: AppServerCommandApprovalDecision } - | { decision: SimpleApprovalDecision } + | { decision: CommandApprovalDecision } | { scope: "session" | "turn"; permissions: AppServerGrantedPermissionProfile }; -type AppServerUserInputParams = AppServerRequestParams<"item/tool/requestUserInput">; - -export interface AppServerUserInput { - requestId: RequestId; - method: "item/tool/requestUserInput"; - params: AppServerUserInputParams; -} - export interface AppServerUserInputResponse { answers: Record; } -type AppServerMcpElicitationAction = "accept" | "decline" | "cancel"; -type AppServerMcpElicitationContentValue = string | number | boolean | readonly string[] | null; type AppServerMcpElicitationRequest = AppServerRequestByMethod<"mcpServer/elicitation/request">; type AppServerMcpElicitationFormParams = Extract; type AppServerMcpElicitationPrimitiveSchema = NonNullable; -interface AppServerMcpElicitationOption { - value: string; - label: string; -} - -interface AppServerMcpElicitationFieldBase { - id: string; - title: string; - description: string | null; - required: boolean; -} - -type AppServerMcpElicitationField = - | (AppServerMcpElicitationFieldBase & { - type: "string"; - format: string | null; - minLength: number | null; - maxLength: number | null; - defaultValue: string; - }) - | (AppServerMcpElicitationFieldBase & { - type: "number" | "integer"; - minimum: number | null; - maximum: number | null; - defaultValue: number | null; - }) - | (AppServerMcpElicitationFieldBase & { - type: "boolean"; - defaultValue: boolean; - }) - | (AppServerMcpElicitationFieldBase & { - type: "single-select"; - options: readonly AppServerMcpElicitationOption[]; - defaultValue: string; - }) - | (AppServerMcpElicitationFieldBase & { - type: "multi-select"; - options: readonly AppServerMcpElicitationOption[]; - minItems: number | null; - maxItems: number | null; - defaultValue: readonly string[]; - }); - -interface AppServerMcpElicitationNormalizedFormParams { - threadId: string; - turnId: string | null; - serverName: string; - mode: "form"; - message: string; - meta: unknown; - fields: readonly AppServerMcpElicitationField[]; -} - -interface AppServerMcpElicitationNormalizedUrlParams { - threadId: string; - turnId: string | null; - serverName: string; - mode: "url"; - message: string; - meta: unknown; - url: string; - elicitationId: string; -} - -export interface AppServerMcpElicitation { - requestId: RequestId; - method: "mcpServer/elicitation/request"; - params: AppServerMcpElicitationNormalizedFormParams | AppServerMcpElicitationNormalizedUrlParams; -} - export interface AppServerMcpElicitationResponse { - action: AppServerMcpElicitationAction; + action: McpElicitationAction; content: unknown; _meta: unknown; } -export function appServerApprovalRequest(request: ServerRequest): AppServerApproval | null { +export function appServerApprovalRequest(request: ServerRequest): PendingApproval | null { switch (request.method) { case "item/commandExecution/requestApproval": return { @@ -182,13 +65,10 @@ export function appServerApprovalRequest(request: ServerRequest): AppServerAppro } } -export function appServerApprovalResponse( - approval: AppServerApprovalResponseSource, - action: AppServerApprovalAction, -): AppServerApprovalResponse { +export function appServerApprovalResponse(approval: PendingApproval, action: ApprovalAction): AppServerApprovalResponse { if (approval.method === "item/commandExecution/requestApproval") { return { - decision: isAppServerCommandDecisionAction(action) ? action.decision : commandDecision(action), + decision: isCommandDecisionAction(action) ? action.decision : commandDecision(action), }; } @@ -204,7 +84,7 @@ export function appServerApprovalResponse( }; } -export function appServerUserInputRequest(request: ServerRequest): AppServerUserInput | null { +export function appServerUserInputRequest(request: ServerRequest): PendingUserInput | null { if (request.method !== "item/tool/requestUserInput") return null; return { requestId: request.id, @@ -229,7 +109,7 @@ export function appServerUserInputResponse( }; } -export function appServerMcpElicitationRequest(request: ServerRequest): AppServerMcpElicitation | null { +export function appServerMcpElicitationRequest(request: ServerRequest): PendingMcpElicitation | null { if (request.method !== "mcpServer/elicitation/request") return null; const params = request.params; if (params.mode === "url") { @@ -264,8 +144,8 @@ export function appServerMcpElicitationRequest(request: ServerRequest): AppServe } export function appServerMcpElicitationResponse( - action: AppServerMcpElicitationAction, - content: Record | null, + action: McpElicitationAction, + content: Record | null, ): AppServerMcpElicitationResponse { return { action, @@ -274,18 +154,18 @@ export function appServerMcpElicitationResponse( }; } -function isAppServerCommandDecisionAction(action: AppServerApprovalAction): action is AppServerCommandApprovalDecisionAction { +function isCommandDecisionAction(action: ApprovalAction): action is Extract { return typeof action === "object"; } -function commandDecision(action: AppServerApprovalAction): AppServerCommandApprovalDecision { +function commandDecision(action: ApprovalAction): CommandApprovalDecision { if (action === "accept") return "accept"; if (action === "accept-session") return "acceptForSession"; if (action === "cancel") return "cancel"; return "decline"; } -function fileChangeDecision(action: AppServerApprovalAction): SimpleApprovalDecision { +function fileChangeDecision(action: ApprovalAction): CommandApprovalDecision { if (action === "accept") return "accept"; if (action === "accept-session") return "acceptForSession"; if (action === "cancel") return "cancel"; @@ -302,7 +182,7 @@ function grantedPermissions(requested: { network?: unknown; fileSystem?: unknown function mcpElicitationFieldsFromSchema( properties: Record, required: ReadonlySet, -): AppServerMcpElicitationField[] { +): PendingMcpElicitationField[] { return Object.entries(properties).flatMap(([id, schema]) => { if (!schema) return []; return [mcpElicitationFieldFromSchema(id, schema, required.has(id))]; @@ -313,7 +193,7 @@ function mcpElicitationFieldFromSchema( id: string, schema: AppServerMcpElicitationPrimitiveSchema, required: boolean, -): AppServerMcpElicitationField { +): PendingMcpElicitationField { const base = { id, title: schema.title ?? id, @@ -362,7 +242,7 @@ function mcpElicitationFieldFromSchema( }; } -function singleSelectOptions(schema: Extract): AppServerMcpElicitationOption[] { +function singleSelectOptions(schema: Extract): PendingMcpElicitationOption[] { if ("oneOf" in schema) return schema.oneOf.map((option) => ({ value: option.const, label: option.title })); if ("enum" in schema) { const enumNames = "enumNames" in schema ? schema.enumNames : undefined; @@ -372,7 +252,7 @@ function singleSelectOptions(schema: Extract): AppServerMcpElicitationOption[] { +function multiSelectOptions(schema: Extract): PendingMcpElicitationOption[] { if ("anyOf" in schema.items) return schema.items.anyOf.map((option) => ({ value: option.const, label: option.title })); return schema.items.enum.map((value) => ({ value, label: value })); } @@ -382,16 +262,16 @@ function bigintToNumberOrNull(value: bigint | number | undefined): number | null return typeof value === "number" && Number.isFinite(value) ? value : null; } -function toJsonContent(content: Record | null): unknown { +function toJsonContent(content: Record | null): unknown { if (!content) return null; return Object.fromEntries(Object.entries(content).map(([key, value]) => [key, toJsonValue(value)])); } -function toJsonValue(value: AppServerMcpElicitationContentValue): unknown { +function toJsonValue(value: McpElicitationContentValue): unknown { if (isReadonlyStringArray(value)) return [...value]; return value; } -function isReadonlyStringArray(value: AppServerMcpElicitationContentValue): value is readonly string[] { +function isReadonlyStringArray(value: McpElicitationContentValue): value is readonly string[] { return Array.isArray(value); } diff --git a/src/domain/pending-requests/model.ts b/src/domain/pending-requests/model.ts new file mode 100644 index 00000000..6a4562f3 --- /dev/null +++ b/src/domain/pending-requests/model.ts @@ -0,0 +1,308 @@ +export type PendingRequestId = string | number; + +type SimpleApprovalDecision = "accept" | "acceptForSession" | "decline" | "cancel"; + +export type CommandApprovalDecision = + | SimpleApprovalDecision + | { acceptWithExecpolicyAmendment: unknown } + | { applyNetworkPolicyAmendment: { network_policy_amendment: { action: "allow" | "deny"; [key: string]: unknown } } }; + +interface CommandApprovalParams { + threadId: string; + turnId: string; + itemId: string; + startedAtMs: number; + approvalId?: string | null; + reason?: string | null; + networkApprovalContext?: unknown; + command?: string | null; + cwd?: string | null; + commandActions?: unknown[] | null; + additionalPermissions?: unknown; + proposedExecpolicyAmendment?: unknown; + proposedNetworkPolicyAmendments?: unknown[] | null; + availableDecisions?: CommandApprovalDecision[] | null; +} + +interface FileChangeApprovalParams { + threadId: string; + turnId: string; + itemId: string; + startedAtMs: number; + reason: string | null; + grantRoot: string | null; +} + +interface PermissionProfile { + network?: { enabled?: boolean | null } | null; + fileSystem?: { + entries?: readonly { path: unknown; access?: unknown }[] | null; + read?: unknown; + write?: unknown; + globScanMaxDepth?: unknown; + } | null; +} + +interface PermissionsApprovalParams { + threadId: string; + turnId: string; + itemId: string; + startedAtMs: number; + reason: string | null; + cwd: string; + environmentId: string | null; + permissions: PermissionProfile; +} + +export type ApprovalAction = "accept" | "accept-session" | "decline" | "cancel" | CommandApprovalDecisionAction; + +interface CommandApprovalDecisionAction { + kind: "command-decision"; + decision: CommandApprovalDecision; +} + +export type PendingApproval = + | { + requestId: PendingRequestId; + method: "item/commandExecution/requestApproval"; + params: CommandApprovalParams; + } + | { + requestId: PendingRequestId; + method: "item/fileChange/requestApproval"; + params: FileChangeApprovalParams; + } + | { + requestId: PendingRequestId; + method: "item/permissions/requestApproval"; + params: PermissionsApprovalParams; + }; + +interface PendingUserInputOption { + label: string; + description: string; +} + +export interface PendingUserInputQuestion { + id: string; + header: string; + question: string; + isOther: boolean; + isSecret: boolean; + options: PendingUserInputOption[] | null; +} + +interface PendingUserInputParams { + threadId: string; + turnId: string; + itemId: string; + questions: PendingUserInputQuestion[]; + autoResolutionMs: number | null; +} + +export interface PendingUserInput { + requestId: PendingRequestId; + method: "item/tool/requestUserInput"; + params: PendingUserInputParams; +} + +export type McpElicitationAction = "accept" | "decline" | "cancel"; + +export type McpElicitationContentValue = string | number | boolean | readonly string[] | null; + +export interface PendingMcpElicitationOption { + value: string; + label: string; +} + +interface PendingMcpElicitationFieldBase { + id: string; + title: string; + description: string | null; + required: boolean; +} + +export type PendingMcpElicitationField = + | (PendingMcpElicitationFieldBase & { + type: "string"; + format: string | null; + minLength: number | null; + maxLength: number | null; + defaultValue: string; + }) + | (PendingMcpElicitationFieldBase & { + type: "number" | "integer"; + minimum: number | null; + maximum: number | null; + defaultValue: number | null; + }) + | (PendingMcpElicitationFieldBase & { + type: "boolean"; + defaultValue: boolean; + }) + | (PendingMcpElicitationFieldBase & { + type: "single-select"; + options: readonly PendingMcpElicitationOption[]; + defaultValue: string; + }) + | (PendingMcpElicitationFieldBase & { + type: "multi-select"; + options: readonly PendingMcpElicitationOption[]; + minItems: number | null; + maxItems: number | null; + defaultValue: readonly string[]; + }); + +interface PendingMcpElicitationFormParams { + threadId: string; + turnId: string | null; + serverName: string; + mode: "form"; + message: string; + meta: unknown; + fields: readonly PendingMcpElicitationField[]; +} + +interface PendingMcpElicitationUrlParams { + threadId: string; + turnId: string | null; + serverName: string; + mode: "url"; + message: string; + meta: unknown; + url: string; + elicitationId: string; +} + +export interface PendingMcpElicitation { + requestId: PendingRequestId; + method: "mcpServer/elicitation/request"; + params: PendingMcpElicitationFormParams | PendingMcpElicitationUrlParams; +} + +export function approvalActionKind(action: ApprovalAction): "accept" | "accept-session" | "decline" | "cancel" { + if (!isCommandDecisionAction(action)) return action; + const decision = action.decision; + if (typeof decision === "string") return simpleApprovalActionKind(decision); + if ("acceptWithExecpolicyAmendment" in decision) return "accept-session"; + if ("applyNetworkPolicyAmendment" in decision) { + return decision.applyNetworkPolicyAmendment.network_policy_amendment.action === "allow" ? "accept-session" : "decline"; + } + return "decline"; +} + +function simpleApprovalActionKind(decision: string): "accept" | "accept-session" | "decline" | "cancel" { + if (decision === "accept") return "accept"; + if (decision === "acceptForSession") return "accept-session"; + if (decision === "cancel") return "cancel"; + return "decline"; +} + +function isCommandDecisionAction(action: ApprovalAction): action is CommandApprovalDecisionAction { + return typeof action === "object"; +} + +export function questionDefaultAnswer(question: PendingUserInputQuestion): string { + return question.options?.[0]?.label ?? ""; +} + +export function userInputDraftKey(requestId: PendingRequestId, questionId: string): string { + return pendingRequestDerivedKey(requestId, questionId); +} + +export function userInputOtherDraftKey(requestId: PendingRequestId, questionId: string): string { + return pendingRequestDerivedKey(requestId, `${questionId}:other`); +} + +export function mcpElicitationDraftKey(requestId: PendingRequestId, fieldId: string): string { + return pendingRequestDerivedKey(requestId, `mcp:${fieldId}`); +} + +export function approvalDetailsDisclosureId(requestId: PendingRequestId): string { + return pendingRequestDerivedKey(requestId, "details"); +} + +export function pendingRequestDerivedKeyPrefix(requestId: PendingRequestId): string { + return `${String(requestId)}:`; +} + +function pendingRequestDerivedKey(requestId: PendingRequestId, suffix: string): string { + return `${pendingRequestDerivedKeyPrefix(requestId)}${suffix}`; +} + +export function answersForPendingUserInput(input: PendingUserInput, drafts: ReadonlyMap): Record { + return Object.fromEntries( + input.params.questions.map((question) => [ + question.id, + drafts.get(userInputDraftKey(input.requestId, question.id)) ?? questionDefaultAnswer(question), + ]), + ); +} + +export function contentForPendingMcpElicitation( + elicitation: PendingMcpElicitation, + drafts: ReadonlyMap, +): Record | null { + if (elicitation.params.mode !== "form") return null; + return Object.fromEntries( + elicitation.params.fields.map((field) => [ + field.id, + mcpElicitationFieldValue(field, drafts.get(mcpElicitationDraftKey(elicitation.requestId, field.id))), + ]), + ); +} + +export function mcpElicitationFieldDefaultDraft(field: PendingMcpElicitationField): string { + switch (field.type) { + case "boolean": + return field.defaultValue ? "true" : "false"; + case "multi-select": + return JSON.stringify(field.defaultValue); + case "number": + case "integer": + return field.defaultValue === null ? "" : String(field.defaultValue); + default: + return field.defaultValue; + } +} + +function mcpElicitationFieldValue(field: PendingMcpElicitationField, draftValue: string | undefined): McpElicitationContentValue { + const draft = draftValue ?? mcpElicitationFieldDefaultDraft(field); + switch (field.type) { + case "boolean": + return draft === "true"; + case "number": + case "integer": + return numericMcpElicitationFieldValue(field, draft); + case "multi-select": + return multiSelectMcpElicitationFieldValue(field, draft); + default: + return draft; + } +} + +function numericMcpElicitationFieldValue( + field: Extract, + draft: string, +): number | null { + if (draft.trim() === "") return null; + const parsed = field.type === "integer" ? Number.parseInt(draft, 10) : Number(draft); + if (Number.isFinite(parsed)) return parsed; + return field.defaultValue; +} + +function multiSelectMcpElicitationFieldValue( + field: Extract, + draft: string, +): readonly string[] { + try { + const parsed = JSON.parse(draft) as unknown; + if (Array.isArray(parsed)) { + const allowed = new Set(field.options.map((option) => option.value)); + return parsed.filter((value): value is string => typeof value === "string" && allowed.has(value)); + } + } catch { + // Fall through to schema default when a stale draft cannot be parsed. + } + return field.defaultValue; +} diff --git a/src/features/chat/app-server/inbound/handler.ts b/src/features/chat/app-server/inbound/handler.ts index 8df19296..00bcb5d0 100644 --- a/src/features/chat/app-server/inbound/handler.ts +++ b/src/features/chat/app-server/inbound/handler.ts @@ -16,7 +16,7 @@ import { type PendingMcpElicitation, type PendingRequestId, type PendingUserInput, -} from "../../domain/pending-requests/model"; +} from "../../../../domain/pending-requests/model"; import { approvalResponse } from "../requests/approval"; import { mcpElicitationResponse } from "../requests/mcp-elicitation"; import { userInputResponse } from "../requests/user-input"; diff --git a/src/features/chat/app-server/inbound/routing.ts b/src/features/chat/app-server/inbound/routing.ts index 9d44cad0..d099398f 100644 --- a/src/features/chat/app-server/inbound/routing.ts +++ b/src/features/chat/app-server/inbound/routing.ts @@ -1,5 +1,5 @@ import type { ServerNotification, ServerRequest } from "../../../../app-server/connection/rpc-messages"; -import type { PendingApproval, PendingMcpElicitation, PendingUserInput } from "../../domain/pending-requests/model"; +import type { PendingApproval, PendingMcpElicitation, PendingUserInput } from "../../../../domain/pending-requests/model"; import { toPendingApproval } from "../requests/approval"; import { toPendingMcpElicitation } from "../requests/mcp-elicitation"; import { toPendingUserInput } from "../requests/user-input"; diff --git a/src/features/chat/app-server/requests/approval.ts b/src/features/chat/app-server/requests/approval.ts index 05f3b21d..d7236277 100644 --- a/src/features/chat/app-server/requests/approval.ts +++ b/src/features/chat/app-server/requests/approval.ts @@ -2,54 +2,12 @@ import type { ServerRequest } from "../../../../app-server/connection/rpc-messag import { appServerApprovalRequest, appServerApprovalResponse, - type AppServerApproval, type AppServerApprovalResponse, } from "../../../../app-server/protocol/server-requests"; -import type { ApprovalAction, PendingApproval } from "../../domain/pending-requests/model"; +import type { ApprovalAction, PendingApproval } from "../../../../domain/pending-requests/model"; export function toPendingApproval(request: ServerRequest): PendingApproval | null { - const approval = appServerApprovalRequest(request); - if (!approval) return null; - switch (approval.method) { - case "item/commandExecution/requestApproval": - return pendingApproval(approval); - case "item/fileChange/requestApproval": - return pendingApproval(approval); - case "item/permissions/requestApproval": - return pendingApproval(approval); - } -} - -function pendingApproval( - request: Extract, -): Extract; -function pendingApproval( - request: Extract, -): Extract; -function pendingApproval( - request: Extract, -): Extract; -function pendingApproval(request: AppServerApproval): PendingApproval { - switch (request.method) { - case "item/commandExecution/requestApproval": - return { - requestId: request.requestId, - method: request.method, - params: request.params, - }; - case "item/fileChange/requestApproval": - return { - requestId: request.requestId, - method: request.method, - params: request.params, - }; - case "item/permissions/requestApproval": - return { - requestId: request.requestId, - method: request.method, - params: request.params, - }; - } + return appServerApprovalRequest(request); } export function approvalResponse(approval: PendingApproval, action: ApprovalAction): AppServerApprovalResponse { diff --git a/src/features/chat/app-server/requests/mcp-elicitation.ts b/src/features/chat/app-server/requests/mcp-elicitation.ts index 9abdf53c..f3612463 100644 --- a/src/features/chat/app-server/requests/mcp-elicitation.ts +++ b/src/features/chat/app-server/requests/mcp-elicitation.ts @@ -4,28 +4,10 @@ import { appServerMcpElicitationResponse, type AppServerMcpElicitationResponse, } from "../../../../app-server/protocol/server-requests"; -import type { McpElicitationAction, McpElicitationContentValue, PendingMcpElicitation } from "../../domain/pending-requests/model"; +import type { McpElicitationAction, McpElicitationContentValue, PendingMcpElicitation } from "../../../../domain/pending-requests/model"; export function toPendingMcpElicitation(request: ServerRequest): PendingMcpElicitation | null { - const elicitation = appServerMcpElicitationRequest(request); - if (!elicitation) return null; - if (elicitation.params.mode === "url") { - return { - requestId: elicitation.requestId, - method: elicitation.method, - params: { - ...elicitation.params, - }, - }; - } - return { - requestId: elicitation.requestId, - method: elicitation.method, - params: { - ...elicitation.params, - fields: elicitation.params.fields, - }, - }; + return appServerMcpElicitationRequest(request); } export function mcpElicitationResponse( diff --git a/src/features/chat/app-server/requests/user-input.ts b/src/features/chat/app-server/requests/user-input.ts index 0b29a6a1..8c372fb2 100644 --- a/src/features/chat/app-server/requests/user-input.ts +++ b/src/features/chat/app-server/requests/user-input.ts @@ -4,16 +4,10 @@ import { appServerUserInputResponse, type AppServerUserInputResponse, } from "../../../../app-server/protocol/server-requests"; -import type { PendingUserInput } from "../../domain/pending-requests/model"; +import type { PendingUserInput } from "../../../../domain/pending-requests/model"; export function toPendingUserInput(request: ServerRequest): PendingUserInput | null { - const userInputRequest = appServerUserInputRequest(request); - if (!userInputRequest) return null; - return { - requestId: userInputRequest.requestId, - method: userInputRequest.method, - params: userInputRequest.params, - }; + return appServerUserInputRequest(request); } export function userInputResponse(input: PendingUserInput, answers: Record): AppServerUserInputResponse { diff --git a/src/features/chat/application/pending-requests/block.ts b/src/features/chat/application/pending-requests/block.ts index 9b622d9e..b33b77f2 100644 --- a/src/features/chat/application/pending-requests/block.ts +++ b/src/features/chat/application/pending-requests/block.ts @@ -5,9 +5,9 @@ import type { PendingMcpElicitation, PendingRequestId, PendingUserInput, -} from "../../domain/pending-requests/model"; +} from "../../../../domain/pending-requests/model"; -export type { PendingRequestId } from "../../domain/pending-requests/model"; +export type { PendingRequestId } from "../../../../domain/pending-requests/model"; export interface PendingRequestBlockState { approvals: readonly PendingApproval[]; diff --git a/src/features/chat/application/pending-requests/pending-request-actions.ts b/src/features/chat/application/pending-requests/pending-request-actions.ts index 4f0051eb..4984a090 100644 --- a/src/features/chat/application/pending-requests/pending-request-actions.ts +++ b/src/features/chat/application/pending-requests/pending-request-actions.ts @@ -8,7 +8,7 @@ import { type PendingApproval, type PendingMcpElicitation, type PendingUserInput, -} from "../../domain/pending-requests/model"; +} from "../../../../domain/pending-requests/model"; import type { PendingRequestBlockActions, PendingRequestBlockState, PendingRequestId } from "./block"; import type { ChatState } from "../state/root-reducer"; diff --git a/src/features/chat/application/pending-requests/state.ts b/src/features/chat/application/pending-requests/state.ts index 2244106a..39b7f752 100644 --- a/src/features/chat/application/pending-requests/state.ts +++ b/src/features/chat/application/pending-requests/state.ts @@ -6,7 +6,7 @@ import { type PendingMcpElicitation, type PendingRequestId, type PendingUserInput, -} from "../../domain/pending-requests/model"; +} from "../../../../domain/pending-requests/model"; export interface ChatRequestState { readonly approvals: readonly PendingApproval[]; diff --git a/src/features/chat/domain/pending-requests/approval.ts b/src/features/chat/domain/pending-requests/approval.ts index c60f7ca6..cd0d8daa 100644 --- a/src/features/chat/domain/pending-requests/approval.ts +++ b/src/features/chat/domain/pending-requests/approval.ts @@ -1,7 +1,7 @@ import { jsonPreview } from "../../../../utils"; import { pathRelativeToRoot } from "../message-stream/format/path-labels"; import { permissionRows, type MessageStreamPermissionProfile } from "../message-stream/format/permission-rows"; -import type { PendingApproval } from "./model"; +import type { PendingApproval } from "../../../../domain/pending-requests/model"; interface ApprovalSummaryParts { reason: string | null; diff --git a/src/features/chat/domain/pending-requests/result-items.ts b/src/features/chat/domain/pending-requests/result-items.ts index ec46c7c8..0fec5063 100644 --- a/src/features/chat/domain/pending-requests/result-items.ts +++ b/src/features/chat/domain/pending-requests/result-items.ts @@ -6,7 +6,7 @@ import { type PendingApproval, type PendingMcpElicitation, type PendingUserInput, -} from "./model"; +} from "../../../../domain/pending-requests/model"; import { approvalDetails, approvalResultSummary, approvalTitle } from "./approval"; import type { MessageStreamItem, MessageStreamUserInputQuestionResult } from "../message-stream/items"; import { definedProp } from "../../../../utils"; diff --git a/src/features/chat/domain/pending-requests/signatures.ts b/src/features/chat/domain/pending-requests/signatures.ts index 30a5986c..83f2e7e5 100644 --- a/src/features/chat/domain/pending-requests/signatures.ts +++ b/src/features/chat/domain/pending-requests/signatures.ts @@ -1,4 +1,4 @@ -import type { PendingApproval, PendingMcpElicitation, PendingUserInput } from "./model"; +import type { PendingApproval, PendingMcpElicitation, PendingUserInput } from "../../../../domain/pending-requests/model"; export function pendingRequestsSignature( approvals: readonly PendingApproval[], diff --git a/src/features/chat/presentation/pending-requests/approval-view.ts b/src/features/chat/presentation/pending-requests/approval-view.ts index 475fb4ea..04ad8953 100644 --- a/src/features/chat/presentation/pending-requests/approval-view.ts +++ b/src/features/chat/presentation/pending-requests/approval-view.ts @@ -3,7 +3,7 @@ import { type ApprovalAction, type CommandApprovalDecision, type PendingApproval, -} from "../../domain/pending-requests/model"; +} from "../../../../domain/pending-requests/model"; export interface ApprovalActionOption { id: string; diff --git a/src/features/chat/presentation/pending-requests/snapshot.ts b/src/features/chat/presentation/pending-requests/snapshot.ts index 14f66884..b4e2d22f 100644 --- a/src/features/chat/presentation/pending-requests/snapshot.ts +++ b/src/features/chat/presentation/pending-requests/snapshot.ts @@ -1,4 +1,4 @@ -import type { PendingApproval, PendingMcpElicitation, PendingUserInput } from "../../domain/pending-requests/model"; +import type { PendingApproval, PendingMcpElicitation, PendingUserInput } from "../../../../domain/pending-requests/model"; import { pendingApprovalViewModel, pendingMcpElicitationViewModel, diff --git a/src/features/chat/presentation/pending-requests/view-model.ts b/src/features/chat/presentation/pending-requests/view-model.ts index 4ed93035..e5f2545b 100644 --- a/src/features/chat/presentation/pending-requests/view-model.ts +++ b/src/features/chat/presentation/pending-requests/view-model.ts @@ -11,7 +11,7 @@ import { questionDefaultAnswer, userInputDraftKey, userInputOtherDraftKey, -} from "../../domain/pending-requests/model"; +} from "../../../../domain/pending-requests/model"; type PendingRequestId = DomainPendingRequestId; diff --git a/src/features/chat/ui/message-stream/context.ts b/src/features/chat/ui/message-stream/context.ts index 296a094e..da6f6deb 100644 --- a/src/features/chat/ui/message-stream/context.ts +++ b/src/features/chat/ui/message-stream/context.ts @@ -1,6 +1,6 @@ import type { ComponentChild as UiNode } from "preact"; -import type { ApprovalAction, McpElicitationAction, PendingRequestId } from "../../domain/pending-requests/model"; +import type { ApprovalAction, McpElicitationAction, PendingRequestId } from "../../../../domain/pending-requests/model"; import type { PendingRequestBlockSnapshot } from "../../presentation/pending-requests/snapshot"; import type { MessageStreamForkTarget } from "../../presentation/message-stream/text-view"; import type { PlanImplementationTarget } from "../../domain/message-stream/selectors"; diff --git a/src/features/chat/ui/message-stream/pending-request-block.tsx b/src/features/chat/ui/message-stream/pending-request-block.tsx index 04768846..a2d8d524 100644 --- a/src/features/chat/ui/message-stream/pending-request-block.tsx +++ b/src/features/chat/ui/message-stream/pending-request-block.tsx @@ -1,7 +1,7 @@ import type { ComponentChild as UiNode } from "preact"; import { useEffect, useLayoutEffect, useRef, useState } from "preact/hooks"; -import { approvalDetailsDisclosureId } from "../../domain/pending-requests/model"; +import { approvalDetailsDisclosureId } from "../../../../domain/pending-requests/model"; import { type PendingApprovalViewModel, type PendingMcpElicitationFieldViewModel, diff --git a/tests/features/chat/protocol/server-requests/approval.test.ts b/tests/features/chat/protocol/server-requests/approval.test.ts index eacc1715..83383dd6 100644 --- a/tests/features/chat/protocol/server-requests/approval.test.ts +++ b/tests/features/chat/protocol/server-requests/approval.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { approvalResponse, toPendingApproval } from "../../../../../src/features/chat/app-server/requests/approval"; import { approvalDetails, approvalSummary, approvalTitle } from "../../../../../src/features/chat/domain/pending-requests/approval"; -import type { CommandApprovalDecision } from "../../../../../src/features/chat/domain/pending-requests/model"; +import type { CommandApprovalDecision } from "../../../../../src/domain/pending-requests/model"; import { approvalActionOptions } from "../../../../../src/features/chat/presentation/pending-requests/approval-view"; import type { ServerRequest } from "../../../../../src/app-server/connection/rpc-messages"; diff --git a/tests/features/chat/protocol/server-requests/mcp-elicitation.test.ts b/tests/features/chat/protocol/server-requests/mcp-elicitation.test.ts index b6b706c1..f41784ea 100644 --- a/tests/features/chat/protocol/server-requests/mcp-elicitation.test.ts +++ b/tests/features/chat/protocol/server-requests/mcp-elicitation.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import type { ServerRequest } from "../../../../../src/app-server/connection/rpc-messages"; import { toPendingMcpElicitation, mcpElicitationResponse } from "../../../../../src/features/chat/app-server/requests/mcp-elicitation"; -import { contentForPendingMcpElicitation, mcpElicitationDraftKey } from "../../../../../src/features/chat/domain/pending-requests/model"; +import { contentForPendingMcpElicitation, mcpElicitationDraftKey } from "../../../../../src/domain/pending-requests/model"; function expectPresent(value: T | null | undefined): T { if (value === null || value === undefined) throw new Error("Expected value to be present"); diff --git a/tests/features/chat/protocol/server-requests/user-input.test.ts b/tests/features/chat/protocol/server-requests/user-input.test.ts index b2d47aa1..9a72cae9 100644 --- a/tests/features/chat/protocol/server-requests/user-input.test.ts +++ b/tests/features/chat/protocol/server-requests/user-input.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { answersForPendingUserInput, questionDefaultAnswer } from "../../../../../src/features/chat/domain/pending-requests/model"; +import { answersForPendingUserInput, questionDefaultAnswer } from "../../../../../src/domain/pending-requests/model"; import { toPendingUserInput, userInputResponse } from "../../../../../src/features/chat/app-server/requests/user-input"; import { pendingRequestFocusSignature, diff --git a/tests/features/chat/ui/message-stream/pending-requests.test.tsx b/tests/features/chat/ui/message-stream/pending-requests.test.tsx index 12aaf38a..39590105 100644 --- a/tests/features/chat/ui/message-stream/pending-requests.test.tsx +++ b/tests/features/chat/ui/message-stream/pending-requests.test.tsx @@ -7,11 +7,7 @@ import { pendingApprovalViewModel, pendingMcpElicitationViewModel, } from "../../../../../src/features/chat/presentation/pending-requests/view-model"; -import type { - PendingApproval, - PendingMcpElicitation, - PendingUserInput, -} from "../../../../../src/features/chat/domain/pending-requests/model"; +import type { PendingApproval, PendingMcpElicitation, PendingUserInput } from "../../../../../src/domain/pending-requests/model"; import type { PendingRequestBlockContext } from "../../../../../src/features/chat/ui/message-stream/context"; import type { MessageStreamItem } from "../../../../../src/features/chat/domain/message-stream/items"; import { changeInputValue } from "../../../../support/dom"; diff --git a/tests/features/chat/ui/message-stream/test-helpers.tsx b/tests/features/chat/ui/message-stream/test-helpers.tsx index 30c38da3..37ef3aeb 100644 --- a/tests/features/chat/ui/message-stream/test-helpers.tsx +++ b/tests/features/chat/ui/message-stream/test-helpers.tsx @@ -2,7 +2,7 @@ import { vi } from "vitest"; import type { ComponentChild as UiNode } from "preact"; import { act } from "preact/test-utils"; -import type { PendingApproval, PendingUserInput } from "../../../../../src/features/chat/domain/pending-requests/model"; +import type { PendingApproval, PendingUserInput } from "../../../../../src/domain/pending-requests/model"; import { pendingRequestBlockSnapshotFromState } from "../../../../../src/features/chat/presentation/pending-requests/snapshot"; import { pendingRequestBlockNode } from "../../../../../src/features/chat/ui/message-stream/pending-request-block"; import { messageStreamBlocks as rawMessageStreamBlocks } from "../../../../../src/features/chat/ui/message-stream/stream-blocks";