Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
NodeId,
type OrchestrationV2AppThread,
type OrchestrationV2ProviderThread,
OrchestrationV2UserInputQuestion,
ProjectId,
ProviderInstanceId,
type ProviderInstanceEnvironment,
Expand Down Expand Up @@ -102,6 +103,10 @@ import {
projectionFor,
} from "../testkit/fixtures/shared.ts";

const decodeUserInputQuestions = Schema.decodeUnknownSync(
Schema.Array(OrchestrationV2UserInputQuestion),
);

const DEFAULT_CLAUDE_SETTINGS = Schema.decodeSync(ClaudeSettings)({});
const AUTO_COMPACT_CLAUDE_SETTINGS = Schema.decodeSync(ClaudeSettings)({
autoCompactWindow: "300000",
Expand Down Expand Up @@ -332,6 +337,31 @@ describe("ClaudeAdapterV2 runtime query policy", () => {
);
});

it("fills blank AskUserQuestion descriptions from trimmed labels", () => {
const questions = decodeUserInputQuestions(
ClaudeAdapterV2.claudeUserInputQuestions({
questions: [
{
header: "Naming",
question: "Which name?",
options: [
{ label: "Recorder", description: " Matches the package name. " },
{ label: " Keep ", description: "" },
{ label: " Other ", description: " \t\n " },
{ label: " Ask later " },
],
},
],
}),
);
assert.deepEqual(questions[0]?.options, [
{ label: "Recorder", description: "Matches the package name." },
{ label: "Keep", description: "Keep" },
{ label: "Other", description: "Other" },
{ label: "Ask later", description: "Ask later" },
]);
});

it("normalizes Claude todo and proposed-plan tool input", () => {
assert.deepEqual(
ClaudeAdapterV2.claudeTodoSteps({
Expand Down
10 changes: 3 additions & 7 deletions apps/server/src/orchestration-v2/Adapters/ClaudeAdapterV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3354,13 +3354,9 @@ export function claudeUserInputQuestions(
const optionRecord = option as Record<string, unknown>;
const label = typeof optionRecord.label === "string" ? optionRecord.label.trim() : "";
if (label.length === 0) return [];
return [
{
label,
description:
typeof optionRecord.description === "string" ? optionRecord.description.trim() : "",
},
];
const description =
typeof optionRecord.description === "string" ? optionRecord.description.trim() : "";
return [{ label, description: description || label }];
})
: [];
return [
Expand Down
46 changes: 46 additions & 0 deletions packages/provider-pi/src/server/adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
type ModelSelection,
type OrchestrationV2AppThread,
type OrchestrationV2ProviderThread,
OrchestrationV2UserInputQuestion,
type OrchestrationV2ProviderTurn,
} from "@t3tools/contracts";
import * as Cause from "effect/Cause";
Expand Down Expand Up @@ -57,6 +58,10 @@ const layerTest = Layer.mergeAll(
TestProviderHost.layer().pipe(Layer.provide(NodeServices.layer)),
);

const decodeUserInputQuestions = Schema.decodeUnknownSync(
Schema.Array(OrchestrationV2UserInputQuestion),
);

const decodeJsonLine = Schema.decodeSync(Schema.fromJsonString(Schema.Unknown));
const encodeJsonLine = Schema.encodeSync(Schema.fromJsonString(Schema.Unknown));

Expand Down Expand Up @@ -3102,6 +3107,47 @@ describe("PiAdapterV2", () => {
}).pipe(Effect.scoped, Effect.provide(layerTest)),
);

it.effect("emits a valid empty select option and returns its original value", () =>
Effect.gen(function* () {
const fake = yield* makeFakePi;
const { runtime, takeEvent } = yield* openRuntime(fake);
yield* runtime.ensureThread({
threadId: THREAD_ID,
modelSelection: modelSelection("default"),
runtimePolicy,
});
yield* fake.emit({
type: "extension_ui_request",
id: "ui-select",
method: "select",
title: "Pick a tag",
options: ["stable", ""],
});
const event = yield* takeEvent(
(event) =>
event.type === "turn_item.updated" && event.turnItem.type === "user_input_request",
);
if (event.type !== "turn_item.updated" || event.turnItem.type !== "user_input_request") {
return assert.fail("expected a user input request");
}
const questions = decodeUserInputQuestions(event.turnItem.questions);
assert.deepEqual(
questions[0]?.options.map((option) => [option.label, option.value]),
[
["stable", "stable"],
["Empty value", ""],
],
);
yield* runtime.respondToRuntimeRequest({
requestId: event.turnItem.requestId,
answers: { "ui-select": "" },
});
const response = yield* fake.takeRequest("extension_ui_response");
assert.equal(response["value"], "");
assert.isUndefined(response["cancelled"]);
}).pipe(Effect.scoped, Effect.provide(layerTest)),
);

it.effect("keeps the complete editor prefill editable and returns exact edited text", () =>
Effect.gen(function* () {
const fake = yield* makeFakePi;
Expand Down
6 changes: 5 additions & 1 deletion packages/provider-pi/src/server/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3191,7 +3191,11 @@ function piQuestion(
method === "select" && Array.isArray(event["options"])
? event["options"]
.filter((option): option is string => typeof option === "string")
.map((option) => ({ label: option || "Empty value", description: option, value: option }))
.map((option) => ({
label: option || "Empty value",
description: option || "Empty value",
value: option,
}))
Comment thread
juliusmarminge marked this conversation as resolved.
: method === "editor"
? []
: [
Expand Down
Loading