mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
@@ -9,6 +9,7 @@ import * as executableUtils from "../../../../utils/executable.js";
|
||||
import {
|
||||
ClaudeAgentClient,
|
||||
convertClaudeHistoryEntry,
|
||||
normalizeClaudeAskUserQuestionRequestInput,
|
||||
normalizeClaudeAskUserQuestionUpdatedInput,
|
||||
toClaudeSdkMcpConfig,
|
||||
} from "./agent.js";
|
||||
@@ -614,6 +615,37 @@ describe("ClaudeAgentSession features", () => {
|
||||
});
|
||||
|
||||
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", () => {
|
||||
expect(
|
||||
normalizeClaudeAskUserQuestionUpdatedInput(
|
||||
@@ -746,6 +778,86 @@ describe("normalizeClaudeAskUserQuestionUpdatedInput", () => {
|
||||
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", () => {
|
||||
|
||||
@@ -104,6 +104,49 @@ function readNonEmptyString(value: unknown): string | 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(
|
||||
updatedInput: AgentMetadata | undefined,
|
||||
fallbackInput: AgentMetadata | undefined,
|
||||
@@ -114,7 +157,7 @@ export function normalizeClaudeAskUserQuestionUpdatedInput(
|
||||
// AskUserQuestion tool expects answer keys to match the full question text. Merge
|
||||
// the original request payload back in so provider callbacks that only return
|
||||
// `{ answers }` still satisfy Claude's full tool input schema.
|
||||
const merged = { ...fallback, ...base };
|
||||
const merged = stripClaudeAskUserQuestionUiMetadata({ ...fallback, ...base });
|
||||
const questions =
|
||||
(Array.isArray(base.questions) ? base.questions : null) ??
|
||||
(Array.isArray(fallback.questions) ? fallback.questions : null);
|
||||
@@ -3760,6 +3803,7 @@ class ClaudeAgentSession implements AgentSession {
|
||||
): Promise<PermissionResult> => {
|
||||
const requestId = `permission-${randomUUID()}`;
|
||||
const kind = resolvePermissionKind(toolName, input);
|
||||
const requestInput = normalizeClaudeAskUserQuestionRequestInput(toolName, input);
|
||||
const metadata: AgentMetadata = {};
|
||||
if (options.toolUseID) {
|
||||
metadata.toolUseId = options.toolUseID;
|
||||
@@ -3782,7 +3826,7 @@ class ClaudeAgentSession implements AgentSession {
|
||||
provider: "claude",
|
||||
name: toolName,
|
||||
kind,
|
||||
input,
|
||||
input: requestInput,
|
||||
detail: toolDetail,
|
||||
suggestions: options.suggestions?.map((suggestion) => ({
|
||||
...suggestion,
|
||||
|
||||
Reference in New Issue
Block a user