@@ -9,6 +9,7 @@ import * as executableUtils from "../../../../utils/executable.js";
|
|||||||
import {
|
import {
|
||||||
ClaudeAgentClient,
|
ClaudeAgentClient,
|
||||||
convertClaudeHistoryEntry,
|
convertClaudeHistoryEntry,
|
||||||
|
normalizeClaudeAskUserQuestionRequestInput,
|
||||||
normalizeClaudeAskUserQuestionUpdatedInput,
|
normalizeClaudeAskUserQuestionUpdatedInput,
|
||||||
toClaudeSdkMcpConfig,
|
toClaudeSdkMcpConfig,
|
||||||
} from "./agent.js";
|
} from "./agent.js";
|
||||||
@@ -614,6 +615,37 @@ describe("ClaudeAgentSession features", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("normalizeClaudeAskUserQuestionUpdatedInput", () => {
|
describe("normalizeClaudeAskUserQuestionUpdatedInput", () => {
|
||||||
|
test("marks Claude AskUserQuestion options as allowing other answers", () => {
|
||||||
|
expect(
|
||||||
|
normalizeClaudeAskUserQuestionRequestInput("AskUserQuestion", {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "Which provider should I use?",
|
||||||
|
header: "Provider",
|
||||||
|
options: [
|
||||||
|
{ label: "Claude", description: "Use Claude Code" },
|
||||||
|
{ label: "Codex", description: "Use Codex" },
|
||||||
|
],
|
||||||
|
multiSelect: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
).toEqual({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "Which provider should I use?",
|
||||||
|
header: "Provider",
|
||||||
|
options: [
|
||||||
|
{ label: "Claude", description: "Use Claude Code" },
|
||||||
|
{ label: "Codex", description: "Use Codex" },
|
||||||
|
],
|
||||||
|
multiSelect: false,
|
||||||
|
allowOther: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("maps frontend header-keyed answers to Claude question text keys", () => {
|
test("maps frontend header-keyed answers to Claude question text keys", () => {
|
||||||
expect(
|
expect(
|
||||||
normalizeClaudeAskUserQuestionUpdatedInput(
|
normalizeClaudeAskUserQuestionUpdatedInput(
|
||||||
@@ -746,6 +778,86 @@ describe("normalizeClaudeAskUserQuestionUpdatedInput", () => {
|
|||||||
await session.close();
|
await session.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("respondToPermission maps other answer text back to Claude question keys", async () => {
|
||||||
|
const client = new ClaudeAgentClient({
|
||||||
|
logger: createTestLogger(),
|
||||||
|
resolveBinary: async () => "/test/claude/bin",
|
||||||
|
});
|
||||||
|
const session = await client.createSession({
|
||||||
|
provider: "claude",
|
||||||
|
cwd: process.cwd(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const request = {
|
||||||
|
id: "permission-question-2",
|
||||||
|
provider: "claude",
|
||||||
|
name: "AskUserQuestion",
|
||||||
|
kind: "question",
|
||||||
|
input: normalizeClaudeAskUserQuestionRequestInput("AskUserQuestion", {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "Which provider should I use?",
|
||||||
|
header: "Provider",
|
||||||
|
options: [
|
||||||
|
{ label: "Claude", description: "Use Claude Code" },
|
||||||
|
{ label: "Codex", description: "Use Codex" },
|
||||||
|
],
|
||||||
|
multiSelect: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultPromise = new Promise<unknown>((resolve, reject) => {
|
||||||
|
(
|
||||||
|
session as unknown as {
|
||||||
|
pendingPermissions: Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
request: typeof request;
|
||||||
|
resolve: (value: unknown) => void;
|
||||||
|
reject: (error: Error) => void;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
).pendingPermissions.set(request.id, {
|
||||||
|
request,
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await session.respondToPermission(request.id, {
|
||||||
|
behavior: "allow",
|
||||||
|
updatedInput: {
|
||||||
|
answers: { Provider: "Use both" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await expect(resultPromise).resolves.toEqual({
|
||||||
|
behavior: "allow",
|
||||||
|
updatedInput: {
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "Which provider should I use?",
|
||||||
|
header: "Provider",
|
||||||
|
options: [
|
||||||
|
{ label: "Claude", description: "Use Claude Code" },
|
||||||
|
{ label: "Codex", description: "Use Codex" },
|
||||||
|
],
|
||||||
|
multiSelect: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
answers: { "Which provider should I use?": "Use both" },
|
||||||
|
},
|
||||||
|
updatedPermissions: undefined,
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await session.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("ClaudeAgentSession context window usage", () => {
|
describe("ClaudeAgentSession context window usage", () => {
|
||||||
|
|||||||
@@ -104,6 +104,49 @@ function readNonEmptyString(value: unknown): string | null {
|
|||||||
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function normalizeClaudeAskUserQuestionRequestInput(
|
||||||
|
toolName: string,
|
||||||
|
input: AgentMetadata,
|
||||||
|
): AgentMetadata {
|
||||||
|
if (toolName !== "AskUserQuestion" || !Array.isArray(input.questions)) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Claude Code's AskUserQuestion schema says "Other" is host-provided, not a
|
||||||
|
// model-supplied option. Paseo's shared question UI uses allowOther for that
|
||||||
|
// freeform answer path.
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
questions: input.questions.map((item) => {
|
||||||
|
if (!isMetadata(item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
allowOther: true,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripClaudeAskUserQuestionUiMetadata(input: AgentMetadata): AgentMetadata {
|
||||||
|
if (!Array.isArray(input.questions)) {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...input,
|
||||||
|
questions: input.questions.map((item) => {
|
||||||
|
if (!isMetadata(item) || !("allowOther" in item)) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
const itemForClaude: AgentMetadata = { ...item };
|
||||||
|
delete itemForClaude.allowOther;
|
||||||
|
return itemForClaude;
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function normalizeClaudeAskUserQuestionUpdatedInput(
|
export function normalizeClaudeAskUserQuestionUpdatedInput(
|
||||||
updatedInput: AgentMetadata | undefined,
|
updatedInput: AgentMetadata | undefined,
|
||||||
fallbackInput: AgentMetadata | undefined,
|
fallbackInput: AgentMetadata | undefined,
|
||||||
@@ -114,7 +157,7 @@ export function normalizeClaudeAskUserQuestionUpdatedInput(
|
|||||||
// AskUserQuestion tool expects answer keys to match the full question text. Merge
|
// AskUserQuestion tool expects answer keys to match the full question text. Merge
|
||||||
// the original request payload back in so provider callbacks that only return
|
// the original request payload back in so provider callbacks that only return
|
||||||
// `{ answers }` still satisfy Claude's full tool input schema.
|
// `{ answers }` still satisfy Claude's full tool input schema.
|
||||||
const merged = { ...fallback, ...base };
|
const merged = stripClaudeAskUserQuestionUiMetadata({ ...fallback, ...base });
|
||||||
const questions =
|
const questions =
|
||||||
(Array.isArray(base.questions) ? base.questions : null) ??
|
(Array.isArray(base.questions) ? base.questions : null) ??
|
||||||
(Array.isArray(fallback.questions) ? fallback.questions : null);
|
(Array.isArray(fallback.questions) ? fallback.questions : null);
|
||||||
@@ -3760,6 +3803,7 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
): Promise<PermissionResult> => {
|
): Promise<PermissionResult> => {
|
||||||
const requestId = `permission-${randomUUID()}`;
|
const requestId = `permission-${randomUUID()}`;
|
||||||
const kind = resolvePermissionKind(toolName, input);
|
const kind = resolvePermissionKind(toolName, input);
|
||||||
|
const requestInput = normalizeClaudeAskUserQuestionRequestInput(toolName, input);
|
||||||
const metadata: AgentMetadata = {};
|
const metadata: AgentMetadata = {};
|
||||||
if (options.toolUseID) {
|
if (options.toolUseID) {
|
||||||
metadata.toolUseId = options.toolUseID;
|
metadata.toolUseId = options.toolUseID;
|
||||||
@@ -3782,7 +3826,7 @@ class ClaudeAgentSession implements AgentSession {
|
|||||||
provider: "claude",
|
provider: "claude",
|
||||||
name: toolName,
|
name: toolName,
|
||||||
kind,
|
kind,
|
||||||
input,
|
input: requestInput,
|
||||||
detail: toolDetail,
|
detail: toolDetail,
|
||||||
suggestions: options.suggestions?.map((suggestion) => ({
|
suggestions: options.suggestions?.map((suggestion) => ({
|
||||||
...suggestion,
|
...suggestion,
|
||||||
|
|||||||
Reference in New Issue
Block a user