Skip to content

Commit e2e10ff

Browse files
authored
Merge pull request #43 from xuzeyu91/feature_AskUserQuestion
Feature ask user question
2 parents 62a8f5c + d9fe780 commit e2e10ff

20 files changed

Lines changed: 956 additions & 57 deletions

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22
<!-- See https://aka.ms/dotnet/msbuild/customize for more details on customizing your build -->
33
<PropertyGroup>
4-
<Version>0.2.0.1</Version>
4+
<Version>0.2.1</Version>
55
<SKVersion>1.48.0</SKVersion>
66
<NetVersion>10.0.1</NetVersion>
77
</PropertyGroup>

WebCodeCli.Domain/Domain/Model/OutputPanelState.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,79 @@ public class OutputEvent
8181
public string? Name { get; set; }
8282
public string? ItemType { get; set; }
8383
public TokenUsage? Usage { get; set; }
84+
85+
/// <summary>
86+
/// 用户问题(用于 AskUserQuestion 工具)
87+
/// </summary>
88+
public UserQuestion? UserQuestion { get; set; }
89+
}
90+
91+
/// <summary>
92+
/// 用户问题(用于 AskUserQuestion 工具)
93+
/// </summary>
94+
public class UserQuestion
95+
{
96+
/// <summary>
97+
/// 工具调用 ID(用于发送响应)
98+
/// </summary>
99+
public string? ToolUseId { get; set; }
100+
101+
/// <summary>
102+
/// 问题列表
103+
/// </summary>
104+
public List<QuestionItem> Questions { get; set; } = new();
105+
106+
/// <summary>
107+
/// 是否已回答
108+
/// </summary>
109+
public bool IsAnswered { get; set; }
110+
}
111+
112+
/// <summary>
113+
/// 单个问题项
114+
/// </summary>
115+
public class QuestionItem
116+
{
117+
/// <summary>
118+
/// 问题标题/头部
119+
/// </summary>
120+
public string? Header { get; set; }
121+
122+
/// <summary>
123+
/// 问题内容
124+
/// </summary>
125+
public string? Question { get; set; }
126+
127+
/// <summary>
128+
/// 是否允许多选
129+
/// </summary>
130+
public bool MultiSelect { get; set; }
131+
132+
/// <summary>
133+
/// 选项列表
134+
/// </summary>
135+
public List<QuestionOption> Options { get; set; } = new();
136+
137+
/// <summary>
138+
/// 已选择的选项索引
139+
/// </summary>
140+
public List<int> SelectedIndexes { get; set; } = new();
141+
}
142+
143+
/// <summary>
144+
/// 问题选项
145+
/// </summary>
146+
public class QuestionOption
147+
{
148+
/// <summary>
149+
/// 选项标签
150+
/// </summary>
151+
public string? Label { get; set; }
152+
153+
/// <summary>
154+
/// 选项描述
155+
/// </summary>
156+
public string? Description { get; set; }
84157
}
85158

86159
/// <summary>

WebCodeCli.Domain/Domain/Service/Adapters/ClaudeCodeAdapter.cs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@ private void ParseToolUseFromContentItem(JsonElement item, CliOutputEvent output
479479
outputEvent.ItemType = "command_execution";
480480

481481
var toolName = GetStringProperty(item, "name") ?? GetStringProperty(item, "tool");
482+
var toolUseId = GetStringProperty(item, "id");
482483
var contentBuilder = new StringBuilder();
483484
if (!string.IsNullOrWhiteSpace(toolName))
484485
{
@@ -501,6 +502,18 @@ private void ParseToolUseFromContentItem(JsonElement item, CliOutputEvent output
501502
return;
502503
}
503504

505+
// Claude Code 的 AskUserQuestion 工具:展示问题让用户选择
506+
if (!string.IsNullOrWhiteSpace(toolName) &&
507+
string.Equals(toolName, "AskUserQuestion", StringComparison.OrdinalIgnoreCase))
508+
{
509+
outputEvent.ItemType = "user_question";
510+
outputEvent.Title = "请回答问题";
511+
outputEvent.Content = FormatAskUserQuestionInput(inputEl, outputEvent, toolUseId);
512+
outputEvent.CommandExecution = null;
513+
outputEvent.IsUnknown = false;
514+
return;
515+
}
516+
504517
var command = GetStringProperty(inputEl, "command");
505518
if (!string.IsNullOrWhiteSpace(command))
506519
{
@@ -518,6 +531,91 @@ private void ParseToolUseFromContentItem(JsonElement item, CliOutputEvent output
518531
outputEvent.IsUnknown = false;
519532
}
520533

534+
private static string FormatAskUserQuestionInput(JsonElement inputEl, CliOutputEvent outputEvent, string? toolUseId)
535+
{
536+
outputEvent.UserQuestion = new CliUserQuestion
537+
{
538+
ToolUseId = toolUseId,
539+
Questions = new List<CliQuestionItem>()
540+
};
541+
var sb = new StringBuilder();
542+
543+
// AskUserQuestion 结构:{ questions: [ { header, question, multiSelect, options: [ { label, description } ] } ] }
544+
if (inputEl.TryGetProperty("questions", out var questionsEl) && questionsEl.ValueKind == JsonValueKind.Array)
545+
{
546+
foreach (var question in questionsEl.EnumerateArray())
547+
{
548+
if (question.ValueKind != JsonValueKind.Object)
549+
{
550+
continue;
551+
}
552+
553+
var questionItem = new CliQuestionItem
554+
{
555+
Header = GetStringProperty(question, "header"),
556+
Question = GetStringProperty(question, "question"),
557+
MultiSelect = false,
558+
Options = new List<CliQuestionOption>()
559+
};
560+
561+
// 解析 multiSelect
562+
if (question.TryGetProperty("multiSelect", out var multiSelectEl) &&
563+
multiSelectEl.ValueKind == JsonValueKind.True)
564+
{
565+
questionItem.MultiSelect = true;
566+
}
567+
568+
// 解析选项
569+
if (question.TryGetProperty("options", out var optionsEl) && optionsEl.ValueKind == JsonValueKind.Array)
570+
{
571+
foreach (var option in optionsEl.EnumerateArray())
572+
{
573+
if (option.ValueKind != JsonValueKind.Object)
574+
{
575+
continue;
576+
}
577+
578+
questionItem.Options.Add(new CliQuestionOption
579+
{
580+
Label = GetStringProperty(option, "label"),
581+
Description = GetStringProperty(option, "description")
582+
});
583+
}
584+
}
585+
586+
outputEvent.UserQuestion.Questions.Add(questionItem);
587+
588+
// 构建显示文本
589+
if (!string.IsNullOrWhiteSpace(questionItem.Header))
590+
{
591+
sb.AppendLine($"**{questionItem.Header}**");
592+
}
593+
if (!string.IsNullOrWhiteSpace(questionItem.Question))
594+
{
595+
sb.AppendLine(questionItem.Question);
596+
}
597+
if (questionItem.Options.Count > 0)
598+
{
599+
sb.AppendLine();
600+
foreach (var opt in questionItem.Options)
601+
{
602+
var optText = $"- {opt.Label}";
603+
if (!string.IsNullOrWhiteSpace(opt.Description))
604+
{
605+
optText += $": {opt.Description}";
606+
}
607+
sb.AppendLine(optText);
608+
}
609+
}
610+
sb.AppendLine();
611+
}
612+
613+
return sb.Length > 0 ? sb.ToString().TrimEnd() : "请选择一个选项";
614+
}
615+
616+
return inputEl.GetRawText();
617+
}
618+
521619
private static string FormatTodoToolInput(JsonElement inputEl, CliOutputEvent outputEvent)
522620
{
523621
outputEvent.TodoItems = new List<CliTodoItem>();

WebCodeCli.Domain/Domain/Service/Adapters/CliOutputEvent.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ public class CliOutputEvent
6161
/// </summary>
6262
public List<CliTodoItem>? TodoItems { get; set; }
6363

64+
/// <summary>
65+
/// 用户问题(用于 AskUserQuestion 工具)
66+
/// </summary>
67+
public CliUserQuestion? UserQuestion { get; set; }
68+
6469
/// <summary>
6570
/// 额外的元数据
6671
/// </summary>
@@ -103,6 +108,64 @@ public class CliTodoItem
103108
public string? Status { get; set; }
104109
}
105110

111+
/// <summary>
112+
/// 用户问题(用于 AskUserQuestion 工具)
113+
/// </summary>
114+
public class CliUserQuestion
115+
{
116+
/// <summary>
117+
/// 工具调用 ID(用于发送响应)
118+
/// </summary>
119+
public string? ToolUseId { get; set; }
120+
121+
/// <summary>
122+
/// 问题列表
123+
/// </summary>
124+
public List<CliQuestionItem> Questions { get; set; } = new();
125+
}
126+
127+
/// <summary>
128+
/// 单个问题项
129+
/// </summary>
130+
public class CliQuestionItem
131+
{
132+
/// <summary>
133+
/// 问题标题/头部
134+
/// </summary>
135+
public string? Header { get; set; }
136+
137+
/// <summary>
138+
/// 问题内容
139+
/// </summary>
140+
public string? Question { get; set; }
141+
142+
/// <summary>
143+
/// 是否允许多选
144+
/// </summary>
145+
public bool MultiSelect { get; set; }
146+
147+
/// <summary>
148+
/// 选项列表
149+
/// </summary>
150+
public List<CliQuestionOption> Options { get; set; } = new();
151+
}
152+
153+
/// <summary>
154+
/// 问题选项
155+
/// </summary>
156+
public class CliQuestionOption
157+
{
158+
/// <summary>
159+
/// 选项标签
160+
/// </summary>
161+
public string? Label { get; set; }
162+
163+
/// <summary>
164+
/// 选项描述
165+
/// </summary>
166+
public string? Description { get; set; }
167+
}
168+
106169
/// <summary>
107170
/// CLI会话上下文,用于会话恢复
108171
/// </summary>

0 commit comments

Comments
 (0)