初步支持AskUserQuestion交互
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
import { useTranslations } from "next-intl"
|
||||
import { listen, type UnlistenFn } from "@tauri-apps/api/event"
|
||||
import { disposeTauriListener } from "@/lib/tauri-listener"
|
||||
import { inferLiveToolName } from "@/lib/tool-call-normalization"
|
||||
import {
|
||||
acpConnect,
|
||||
acpListAgents,
|
||||
@@ -63,6 +64,11 @@ export interface PendingPermission {
|
||||
options: PermissionOptionInfo[]
|
||||
}
|
||||
|
||||
export interface PendingQuestion {
|
||||
tool_call_id: string
|
||||
question: string
|
||||
}
|
||||
|
||||
export type LiveContentBlock =
|
||||
| { type: "text"; text: string }
|
||||
| { type: "thinking"; text: string }
|
||||
@@ -92,6 +98,7 @@ export interface ConnectionState {
|
||||
usage: SessionUsageUpdateInfo | null
|
||||
liveMessage: LiveMessage | null
|
||||
pendingPermission: PendingPermission | null
|
||||
pendingQuestion: PendingQuestion | null
|
||||
error: string | null
|
||||
}
|
||||
|
||||
@@ -147,6 +154,12 @@ type Action =
|
||||
options: PermissionOptionInfo[]
|
||||
}
|
||||
| { type: "PERMISSION_CLEARED"; contextKey: string }
|
||||
| {
|
||||
type: "SET_PENDING_QUESTION"
|
||||
contextKey: string
|
||||
pendingQuestion: PendingQuestion
|
||||
}
|
||||
| { type: "CLEAR_PENDING_QUESTION"; contextKey: string }
|
||||
| { type: "SESSION_STARTED"; contextKey: string; sessionId: string }
|
||||
| {
|
||||
type: "SESSION_MODES"
|
||||
@@ -259,6 +272,23 @@ function extractPermissionToolKind(toolCall: unknown): string | null {
|
||||
return null
|
||||
}
|
||||
|
||||
function extractQuestionText(rawInput: string | null): string | null {
|
||||
if (!rawInput) return null
|
||||
try {
|
||||
const parsed = JSON.parse(rawInput)
|
||||
if (
|
||||
parsed &&
|
||||
typeof parsed === "object" &&
|
||||
typeof parsed.question === "string"
|
||||
) {
|
||||
return parsed.question
|
||||
}
|
||||
} catch {
|
||||
// not JSON, try using rawInput as-is if it looks like a question
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function sameModes(
|
||||
a: SessionModeStateInfo | null,
|
||||
b: SessionModeStateInfo
|
||||
@@ -451,6 +481,7 @@ function connectionsReducer(
|
||||
usage: null,
|
||||
liveMessage: null,
|
||||
pendingPermission: null,
|
||||
pendingQuestion: null,
|
||||
error: null,
|
||||
})
|
||||
return next
|
||||
@@ -477,6 +508,7 @@ function connectionsReducer(
|
||||
content: [],
|
||||
startedAt: Date.now(),
|
||||
}
|
||||
updated.pendingQuestion = null
|
||||
updated.error = null
|
||||
}
|
||||
next.set(action.contextKey, updated)
|
||||
@@ -741,6 +773,28 @@ function connectionsReducer(
|
||||
return next
|
||||
}
|
||||
|
||||
case "SET_PENDING_QUESTION": {
|
||||
const conn = state.get(action.contextKey)
|
||||
if (!conn) return state
|
||||
const next = new Map(state)
|
||||
next.set(action.contextKey, {
|
||||
...conn,
|
||||
pendingQuestion: action.pendingQuestion,
|
||||
})
|
||||
return next
|
||||
}
|
||||
|
||||
case "CLEAR_PENDING_QUESTION": {
|
||||
const conn = state.get(action.contextKey)
|
||||
if (!conn) return state
|
||||
const next = new Map(state)
|
||||
next.set(action.contextKey, {
|
||||
...conn,
|
||||
pendingQuestion: null,
|
||||
})
|
||||
return next
|
||||
}
|
||||
|
||||
case "SESSION_STARTED": {
|
||||
const conn = state.get(action.contextKey)
|
||||
if (!conn) return state
|
||||
@@ -1398,14 +1452,43 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
|
||||
entries: e.entries,
|
||||
})
|
||||
break
|
||||
case "turn_complete":
|
||||
case "turn_complete": {
|
||||
flushStreamingQueue()
|
||||
dispatch({
|
||||
type: "STATUS_CHANGED",
|
||||
contextKey,
|
||||
status: "connected",
|
||||
})
|
||||
// Detect pending question from tool calls in the completed turn
|
||||
const turnConn = storeRef.current.connections.get(contextKey)
|
||||
if (turnConn?.liveMessage) {
|
||||
const blocks = turnConn.liveMessage.content
|
||||
for (let i = blocks.length - 1; i >= 0; i--) {
|
||||
const block = blocks[i]
|
||||
if (block.type !== "tool_call") continue
|
||||
const normalized = inferLiveToolName({
|
||||
title: block.info.title,
|
||||
kind: block.info.kind,
|
||||
rawInput: block.info.raw_input,
|
||||
})
|
||||
if (normalized === "question") {
|
||||
const questionText = extractQuestionText(block.info.raw_input)
|
||||
if (questionText) {
|
||||
dispatch({
|
||||
type: "SET_PENDING_QUESTION",
|
||||
contextKey,
|
||||
pendingQuestion: {
|
||||
tool_call_id: block.info.tool_call_id,
|
||||
question: questionText,
|
||||
},
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case "error":
|
||||
flushStreamingQueue()
|
||||
dispatch({ type: "ERROR", contextKey, message: e.message })
|
||||
|
||||
Reference in New Issue
Block a user