继续多语言处理
This commit is contained in:
@@ -15,8 +15,16 @@ export const LiveMessageBlock = memo(function LiveMessageBlock({
|
||||
message,
|
||||
}: LiveMessageBlockProps) {
|
||||
const t = useTranslations("Folder.chat.liveMessageBlock")
|
||||
const sharedT = useTranslations("Folder.chat.shared")
|
||||
const hasContent = message.content.length > 0
|
||||
const adapted = useMemo(() => adaptLiveMessageFromAcp(message), [message])
|
||||
const adapted = useMemo(
|
||||
() =>
|
||||
adaptLiveMessageFromAcp(message, {
|
||||
toolCallFailedText: sharedT("toolCallFailed"),
|
||||
planUpdatedText: sharedT("planUpdated"),
|
||||
}),
|
||||
[message, sharedT]
|
||||
)
|
||||
|
||||
return (
|
||||
<Message from="assistant">
|
||||
|
||||
@@ -87,6 +87,8 @@ export function WelcomeInputPanel({
|
||||
isActive = true,
|
||||
}: WelcomeInputPanelProps) {
|
||||
const t = useTranslations("Folder.chat.welcomeInputPanel")
|
||||
const tabT = useTranslations("Folder.tabContext")
|
||||
const sharedT = useTranslations("Folder.chat.shared")
|
||||
const fallbackContextId = useMemo(() => crypto.randomUUID(), [])
|
||||
const contextKey = tabId ?? `new-${fallbackContextId}`
|
||||
|
||||
@@ -159,7 +161,10 @@ export function WelcomeInputPanel({
|
||||
const detail = await getFolderConversation(conversationId)
|
||||
if (refreshSeq !== statsRefreshSeqRef.current) return
|
||||
|
||||
const messages = adaptMessageTurns(detail.turns)
|
||||
const messages = adaptMessageTurns(detail.turns, {
|
||||
attachedResources: sharedT("attachedResources"),
|
||||
toolCallFailed: sharedT("toolCallFailed"),
|
||||
})
|
||||
const stats = detail.session_stats ?? null
|
||||
latestMessages = messages
|
||||
latestStats = stats
|
||||
@@ -196,7 +201,7 @@ export function WelcomeInputPanel({
|
||||
applySessionStats(latestStats)
|
||||
}
|
||||
},
|
||||
[applySessionStats, hasAssistantUsage, hasTokenStats]
|
||||
[applySessionStats, hasAssistantUsage, hasTokenStats, sharedT]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -354,6 +359,8 @@ export function WelcomeInputPanel({
|
||||
if (conn.liveMessage && conn.liveMessage.content.length > 0) {
|
||||
const adapted = adaptLiveMessageFromAcp(conn.liveMessage, {
|
||||
isLiveStreaming: false,
|
||||
toolCallFailedText: sharedT("toolCallFailed"),
|
||||
planUpdatedText: sharedT("planUpdated"),
|
||||
})
|
||||
|
||||
setHistory((h) => [...h, adapted])
|
||||
@@ -376,7 +383,7 @@ export function WelcomeInputPanel({
|
||||
)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- conn.liveMessage, lifecycleSend intentionally omitted: effect only fires on status transitions
|
||||
}, [connStatus, refreshConversations, refreshConversationFromDb])
|
||||
}, [connStatus, refreshConversations, refreshConversationFromDb, sharedT])
|
||||
|
||||
// When connection becomes "connected" and we have a pending prompt, send it
|
||||
useEffect(() => {
|
||||
@@ -394,7 +401,7 @@ export function WelcomeInputPanel({
|
||||
const tid = tabIdRef.current
|
||||
const convId = dbConvIdRef.current
|
||||
const agent = selectedAgentRef.current
|
||||
const title = convTitleRef.current || "Untitled"
|
||||
const title = convTitleRef.current || tabT("untitledConversation")
|
||||
const canonicalContextKey = `conv-${agent}-${convId}`
|
||||
|
||||
// Keep in-flight stream/state attached when this new-conversation view
|
||||
@@ -410,6 +417,7 @@ export function WelcomeInputPanel({
|
||||
refreshConversations,
|
||||
migrateContextKey,
|
||||
contextKey,
|
||||
tabT,
|
||||
])
|
||||
|
||||
// Update conversation status on disconnect/error + promote tab
|
||||
@@ -466,11 +474,17 @@ export function WelcomeInputPanel({
|
||||
// Welcome phase: submit first message.
|
||||
const handleWelcomeSend = useCallback(
|
||||
(draft: PromptDraft, selectedModeId?: string | null) => {
|
||||
const displayText = getPromptDraftDisplayText(draft)
|
||||
const displayText = getPromptDraftDisplayText(
|
||||
draft,
|
||||
sharedT("attachedResources")
|
||||
)
|
||||
const userMsg: AdaptedMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: "user",
|
||||
content: buildUserMessageTextPartsFromDraft(draft),
|
||||
content: buildUserMessageTextPartsFromDraft(
|
||||
draft,
|
||||
sharedT("attachedResources")
|
||||
),
|
||||
userResources: extractUserResourcesFromDraft(draft),
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
@@ -536,6 +550,7 @@ export function WelcomeInputPanel({
|
||||
trySaveExternalId,
|
||||
applySessionStats,
|
||||
newConversationDraftStorageKey,
|
||||
sharedT,
|
||||
]
|
||||
)
|
||||
|
||||
@@ -545,7 +560,10 @@ export function WelcomeInputPanel({
|
||||
const userMsg: AdaptedMessage = {
|
||||
id: crypto.randomUUID(),
|
||||
role: "user",
|
||||
content: buildUserMessageTextPartsFromDraft(draft),
|
||||
content: buildUserMessageTextPartsFromDraft(
|
||||
draft,
|
||||
sharedT("attachedResources")
|
||||
),
|
||||
userResources: extractUserResourcesFromDraft(draft),
|
||||
timestamp: new Date().toISOString(),
|
||||
}
|
||||
@@ -562,7 +580,7 @@ export function WelcomeInputPanel({
|
||||
statusUpdatedRef.current = false
|
||||
}
|
||||
},
|
||||
[lifecycleSend, refreshConversations]
|
||||
[lifecycleSend, refreshConversations, sharedT]
|
||||
)
|
||||
|
||||
const handleOpenAgentsSettings = useCallback(() => {
|
||||
|
||||
@@ -43,6 +43,7 @@ const ExistingConversationView = memo(function ExistingConversationView({
|
||||
reloadSignal,
|
||||
}: ExistingConversationViewProps) {
|
||||
const t = useTranslations("Folder.conversation")
|
||||
const sharedT = useTranslations("Folder.chat.shared")
|
||||
const { refreshConversations, folder } = useFolderContext()
|
||||
const contextKey = `conv-${agentType}-${conversationId}`
|
||||
|
||||
@@ -114,7 +115,10 @@ const ExistingConversationView = memo(function ExistingConversationView({
|
||||
{
|
||||
id: `pending-${Date.now()}`,
|
||||
role: "user",
|
||||
content: buildUserMessageTextPartsFromDraft(draft),
|
||||
content: buildUserMessageTextPartsFromDraft(
|
||||
draft,
|
||||
sharedT("attachedResources")
|
||||
),
|
||||
userResources: extractUserResourcesFromDraft(draft),
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
@@ -125,7 +129,7 @@ const ExistingConversationView = memo(function ExistingConversationView({
|
||||
statusUpdatedRef.current = false
|
||||
handleSend(draft, selectedModeId)
|
||||
},
|
||||
[conversationId, handleSend, refreshConversations]
|
||||
[conversationId, handleSend, refreshConversations, sharedT]
|
||||
)
|
||||
|
||||
// Update status on turn complete
|
||||
|
||||
@@ -1660,6 +1660,7 @@ const CODE_FIELDS = new Set([
|
||||
const HIDDEN_FIELDS = new Set(["dangerouslyDisableSandbox"])
|
||||
|
||||
function GenericToolInput({ input }: { input: string }) {
|
||||
const t = useTranslations("Folder.chat.contentParts")
|
||||
const parsed = tryParseJson(input)
|
||||
|
||||
if (!parsed) {
|
||||
@@ -1677,6 +1678,9 @@ function GenericToolInput({ input }: { input: string }) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
{entries.map(([key, value]) => {
|
||||
const labelKey = fieldLabelKey(key)
|
||||
const label = labelKey ? t(labelKey) : key
|
||||
|
||||
if (CODE_FIELDS.has(key) && typeof value === "string") {
|
||||
const lang =
|
||||
key === "command"
|
||||
@@ -1685,7 +1689,7 @@ function GenericToolInput({ input }: { input: string }) {
|
||||
? ("log" as const)
|
||||
: ("log" as const)
|
||||
return (
|
||||
<FieldBlock key={key} label={fieldLabel(key)}>
|
||||
<FieldBlock key={key} label={label}>
|
||||
<CodeBlock code={value} language={lang} />
|
||||
</FieldBlock>
|
||||
)
|
||||
@@ -1694,29 +1698,23 @@ function GenericToolInput({ input }: { input: string }) {
|
||||
if (typeof value === "string") {
|
||||
if (value.length > 200) {
|
||||
return (
|
||||
<FieldBlock key={key} label={fieldLabel(key)}>
|
||||
<FieldBlock key={key} label={label}>
|
||||
<pre className="whitespace-pre-wrap break-all rounded-md bg-muted/50 p-3 text-xs">
|
||||
{value}
|
||||
</pre>
|
||||
</FieldBlock>
|
||||
)
|
||||
}
|
||||
return <FieldInline key={key} label={fieldLabel(key)} value={value} />
|
||||
return <FieldInline key={key} label={label} value={value} />
|
||||
}
|
||||
|
||||
if (typeof value === "number" || typeof value === "boolean") {
|
||||
return (
|
||||
<FieldInline
|
||||
key={key}
|
||||
label={fieldLabel(key)}
|
||||
value={String(value)}
|
||||
/>
|
||||
)
|
||||
return <FieldInline key={key} label={label} value={String(value)} />
|
||||
}
|
||||
|
||||
if (value !== null && value !== undefined) {
|
||||
return (
|
||||
<FieldBlock key={key} label={fieldLabel(key)}>
|
||||
<FieldBlock key={key} label={label}>
|
||||
<CodeBlock
|
||||
code={JSON.stringify(value, null, 2)}
|
||||
language="json"
|
||||
@@ -1836,41 +1834,45 @@ function FieldBlock({
|
||||
)
|
||||
}
|
||||
|
||||
function fieldLabel(key: string): string {
|
||||
const map: Record<string, string> = {
|
||||
file_path: "File",
|
||||
notebook_path: "Notebook",
|
||||
command: "Command",
|
||||
cmd: "Command",
|
||||
old_string: "Old",
|
||||
new_string: "New",
|
||||
pattern: "Pattern",
|
||||
path: "Path",
|
||||
query: "Query",
|
||||
url: "URL",
|
||||
description: "Description",
|
||||
content: "Content",
|
||||
new_source: "Source",
|
||||
prompt: "Prompt",
|
||||
subject: "Subject",
|
||||
taskId: "Task ID",
|
||||
status: "Status",
|
||||
skill: "Skill",
|
||||
args: "Args",
|
||||
offset: "Offset",
|
||||
limit: "Limit",
|
||||
glob: "Glob",
|
||||
type: "Type",
|
||||
output_mode: "Output",
|
||||
replace_all: "Replace All",
|
||||
language: "Language",
|
||||
timeout: "Timeout",
|
||||
run_in_background: "Background",
|
||||
subagent_type: "Agent Type",
|
||||
libraryName: "Library",
|
||||
libraryId: "Library ID",
|
||||
}
|
||||
return map[key] ?? key
|
||||
const FIELD_LABEL_KEYS = {
|
||||
file_path: "field.file",
|
||||
notebook_path: "field.notebook",
|
||||
command: "field.command",
|
||||
cmd: "field.command",
|
||||
old_string: "field.old",
|
||||
new_string: "field.new",
|
||||
pattern: "field.pattern",
|
||||
path: "field.path",
|
||||
query: "field.query",
|
||||
url: "field.url",
|
||||
description: "field.description",
|
||||
content: "field.content",
|
||||
new_source: "field.source",
|
||||
prompt: "field.prompt",
|
||||
subject: "field.subject",
|
||||
taskId: "field.taskId",
|
||||
status: "field.status",
|
||||
skill: "field.skill",
|
||||
args: "field.args",
|
||||
offset: "field.offset",
|
||||
limit: "field.limit",
|
||||
glob: "field.glob",
|
||||
type: "field.type",
|
||||
output_mode: "field.output",
|
||||
replace_all: "field.replaceAll",
|
||||
language: "field.language",
|
||||
timeout: "field.timeout",
|
||||
run_in_background: "field.background",
|
||||
subagent_type: "field.agentType",
|
||||
libraryName: "field.library",
|
||||
libraryId: "field.libraryId",
|
||||
} as const
|
||||
|
||||
function fieldLabelKey(
|
||||
key: string
|
||||
): (typeof FIELD_LABEL_KEYS)[keyof typeof FIELD_LABEL_KEYS] | null {
|
||||
const translationKey = FIELD_LABEL_KEYS[key as keyof typeof FIELD_LABEL_KEYS]
|
||||
return translationKey ?? null
|
||||
}
|
||||
|
||||
function commandOutputFromJsonString(output: string): string | null {
|
||||
|
||||
@@ -169,6 +169,7 @@ export function MessageListView({
|
||||
isActive = true,
|
||||
}: MessageListViewProps) {
|
||||
const t = useTranslations("Folder.chat.messageList")
|
||||
const sharedT = useTranslations("Folder.chat.shared")
|
||||
const { detail, loading, error, refetch } = useDbMessageDetail(conversationId)
|
||||
const turnCount = detail?.turns.length ?? 0
|
||||
|
||||
@@ -213,8 +214,14 @@ export function MessageListView({
|
||||
const shouldUseSmoothResize = !(isActive && !loading && detail)
|
||||
|
||||
const messages = useMemo(
|
||||
() => (detail ? adaptMessageTurns(detail.turns) : []),
|
||||
[detail]
|
||||
() =>
|
||||
detail
|
||||
? adaptMessageTurns(detail.turns, {
|
||||
attachedResources: sharedT("attachedResources"),
|
||||
toolCallFailed: sharedT("toolCallFailed"),
|
||||
})
|
||||
: [],
|
||||
[detail, sharedT]
|
||||
)
|
||||
|
||||
const groups = useMemo(() => groupAdaptedMessages(messages), [messages])
|
||||
@@ -234,15 +241,17 @@ export function MessageListView({
|
||||
)
|
||||
const resolvedGroups = useMemo(
|
||||
() =>
|
||||
groups.map((group) => resolveMessageGroup(group, t("attachedResources"))),
|
||||
[groups, t]
|
||||
groups.map((group) =>
|
||||
resolveMessageGroup(group, sharedT("attachedResources"))
|
||||
),
|
||||
[groups, sharedT]
|
||||
)
|
||||
const resolvedPendingGroups = useMemo(
|
||||
() =>
|
||||
pendingGroups.map((group) =>
|
||||
resolveMessageGroup(group, t("attachedResources"))
|
||||
resolveMessageGroup(group, sharedT("attachedResources"))
|
||||
),
|
||||
[pendingGroups, t]
|
||||
[pendingGroups, sharedT]
|
||||
)
|
||||
|
||||
const showLiveMessage = Boolean(
|
||||
|
||||
@@ -33,6 +33,8 @@ function stripClonePrefix(message: string): string {
|
||||
function mapCommonCodeToKey(code: string): WelcomeErrorKey {
|
||||
switch (code) {
|
||||
case "invalid_input":
|
||||
case "configuration_missing":
|
||||
case "configuration_invalid":
|
||||
return "errors.invalidInput"
|
||||
case "not_found":
|
||||
return "errors.notFound"
|
||||
|
||||
@@ -124,6 +124,8 @@ type Action =
|
||||
contextKey: string
|
||||
tool_call_id: string
|
||||
title: string | null
|
||||
fallback_title: string
|
||||
fallback_kind: string
|
||||
status: string | null
|
||||
content: string | null
|
||||
raw_input: string | null
|
||||
@@ -135,6 +137,8 @@ type Action =
|
||||
contextKey: string
|
||||
request_id: string
|
||||
tool_call: unknown
|
||||
fallback_title: string
|
||||
fallback_kind: string
|
||||
options: PermissionOptionInfo[]
|
||||
}
|
||||
| { type: "PERMISSION_CLEARED"; contextKey: string }
|
||||
@@ -216,28 +220,28 @@ function serializePermissionToolCall(toolCall: unknown): string | null {
|
||||
}
|
||||
}
|
||||
|
||||
function extractPermissionToolTitle(toolCall: unknown): string {
|
||||
function extractPermissionToolTitle(toolCall: unknown): string | null {
|
||||
const record = asRecord(toolCall)
|
||||
if (!record) return "Tool"
|
||||
if (!record) return null
|
||||
const candidates = [record.title, record.tool_name, record.name, record.type]
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return "Tool"
|
||||
return null
|
||||
}
|
||||
|
||||
function extractPermissionToolKind(toolCall: unknown): string {
|
||||
function extractPermissionToolKind(toolCall: unknown): string | null {
|
||||
const record = asRecord(toolCall)
|
||||
if (!record) return "tool"
|
||||
if (!record) return null
|
||||
const candidates = [record.kind, record.tool_name, record.name, record.type]
|
||||
for (const candidate of candidates) {
|
||||
if (typeof candidate === "string" && candidate.trim().length > 0) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
return "tool"
|
||||
return null
|
||||
}
|
||||
|
||||
function sameModes(
|
||||
@@ -572,8 +576,8 @@ function connectionsReducer(
|
||||
type: "tool_call",
|
||||
info: {
|
||||
tool_call_id: action.tool_call_id,
|
||||
title: action.title ?? "Tool",
|
||||
kind: "tool",
|
||||
title: action.title ?? action.fallback_title,
|
||||
kind: action.fallback_kind,
|
||||
status:
|
||||
action.status ??
|
||||
(normalizedRawOutput ? "in_progress" : "pending"),
|
||||
@@ -665,8 +669,12 @@ function connectionsReducer(
|
||||
type: "tool_call",
|
||||
info: {
|
||||
tool_call_id: permissionCallId,
|
||||
title: extractPermissionToolTitle(action.tool_call),
|
||||
kind: extractPermissionToolKind(action.tool_call),
|
||||
title:
|
||||
extractPermissionToolTitle(action.tool_call) ??
|
||||
action.fallback_title,
|
||||
kind:
|
||||
extractPermissionToolKind(action.tool_call) ??
|
||||
action.fallback_kind,
|
||||
status: "pending",
|
||||
content: null,
|
||||
raw_input: permissionToolInput,
|
||||
@@ -1247,6 +1255,8 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
|
||||
contextKey,
|
||||
tool_call_id: e.tool_call_id,
|
||||
title: e.title,
|
||||
fallback_title: t("toolFallbackTitle"),
|
||||
fallback_kind: "tool",
|
||||
status: e.status,
|
||||
content: e.content,
|
||||
raw_input: e.raw_input,
|
||||
@@ -1261,6 +1271,8 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
|
||||
contextKey,
|
||||
request_id: e.request_id,
|
||||
tool_call: e.tool_call,
|
||||
fallback_title: t("toolFallbackTitle"),
|
||||
fallback_kind: "tool",
|
||||
options: e.options,
|
||||
})
|
||||
break
|
||||
@@ -1322,7 +1334,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
|
||||
case "error":
|
||||
flushStreamingQueue()
|
||||
dispatch({ type: "ERROR", contextKey, message: e.message })
|
||||
pushAlertRef.current("error", "Agent 错误", e.message)
|
||||
pushAlertRef.current("error", t("eventErrorTitle"), e.message)
|
||||
break
|
||||
case "available_commands":
|
||||
flushStreamingQueue()
|
||||
@@ -1334,7 +1346,7 @@ export function AcpConnectionsProvider({ children }: { children: ReactNode }) {
|
||||
break
|
||||
}
|
||||
},
|
||||
[dispatch, enqueueStreamingAction, flushStreamingQueue]
|
||||
[dispatch, enqueueStreamingAction, flushStreamingQueue, t]
|
||||
)
|
||||
|
||||
// Single global event listener
|
||||
|
||||
@@ -50,6 +50,7 @@ export function useConnectionLifecycle({
|
||||
sessionId,
|
||||
}: UseConnectionLifecycleOptions): UseConnectionLifecycleReturn {
|
||||
const t = useTranslations("Folder.chat.connectionLifecycle")
|
||||
const sharedT = useTranslations("Folder.chat.shared")
|
||||
const { setActiveKey, touchActivity } = useAcpActions()
|
||||
const { addTask, updateTask, removeTask } = useTaskContext()
|
||||
const conn = useConnection(contextKey)
|
||||
@@ -312,7 +313,10 @@ export function useConnectionLifecycle({
|
||||
const handleSend = useCallback(
|
||||
(draft: PromptDraft, modeId?: string | null) => {
|
||||
touchActivity(contextKey)
|
||||
setPendingPromptText(contextKey, getPromptDraftDisplayText(draft))
|
||||
setPendingPromptText(
|
||||
contextKey,
|
||||
getPromptDraftDisplayText(draft, sharedT("attachedResources"))
|
||||
)
|
||||
void (async () => {
|
||||
const currentModeId = modeIdRef.current
|
||||
if (modeId && modeId !== currentModeId) {
|
||||
@@ -326,7 +330,7 @@ export function useConnectionLifecycle({
|
||||
console.error("[ConnLifecycle] sendPrompt:", e)
|
||||
)
|
||||
},
|
||||
[connSetMode, sendPrompt, contextKey, touchActivity]
|
||||
[connSetMode, sendPrompt, contextKey, touchActivity, sharedT]
|
||||
)
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"preflightCheckFailedDefault": "Preflight checks failed. Check Agent settings.",
|
||||
"preflightFailedTitle": "{agent} preflight failed",
|
||||
"autoLinkPreflightFailed": "Auto-link preflight failed: {message}",
|
||||
"connectFailedTitle": "{agent} connection failed"
|
||||
"connectFailedTitle": "{agent} connection failed",
|
||||
"toolFallbackTitle": "Tool",
|
||||
"eventErrorTitle": "Agent Error"
|
||||
},
|
||||
"connectionLifecycle": {
|
||||
"tasks": {
|
||||
@@ -1092,6 +1094,11 @@
|
||||
"connectionFailed": "Connection failed"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"attachedResources": "Attached resources",
|
||||
"toolCallFailed": "Tool call failed",
|
||||
"planUpdated": "Plan updated"
|
||||
},
|
||||
"messageThread": {
|
||||
"emptyTitle": "No messages yet",
|
||||
"emptyDescription": "Start a conversation to see messages here"
|
||||
@@ -1222,6 +1229,38 @@
|
||||
"subjectLabel": "Subject",
|
||||
"taskLabel": "Task",
|
||||
"nameLabel": "Name:",
|
||||
"field": {
|
||||
"file": "File",
|
||||
"notebook": "Notebook",
|
||||
"command": "Command",
|
||||
"old": "Old",
|
||||
"new": "New",
|
||||
"pattern": "Pattern",
|
||||
"path": "Path",
|
||||
"query": "Query",
|
||||
"url": "URL",
|
||||
"description": "Description",
|
||||
"content": "Content",
|
||||
"source": "Source",
|
||||
"prompt": "Prompt",
|
||||
"subject": "Subject",
|
||||
"taskId": "Task ID",
|
||||
"status": "Status",
|
||||
"skill": "Skill",
|
||||
"args": "Args",
|
||||
"offset": "Offset",
|
||||
"limit": "Limit",
|
||||
"glob": "Glob",
|
||||
"type": "Type",
|
||||
"output": "Output",
|
||||
"replaceAll": "Replace All",
|
||||
"language": "Language",
|
||||
"timeout": "Timeout",
|
||||
"background": "Background",
|
||||
"agentType": "Agent Type",
|
||||
"library": "Library",
|
||||
"libraryId": "Library ID"
|
||||
},
|
||||
"title": {
|
||||
"edit": "Edit",
|
||||
"command": "Command",
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"preflightCheckFailedDefault": "预检查未通过,请检查 Agent 配置。",
|
||||
"preflightFailedTitle": "{agent} 预检查失败",
|
||||
"autoLinkPreflightFailed": "自动链接预检查失败:{message}",
|
||||
"connectFailedTitle": "{agent} 连接失败"
|
||||
"connectFailedTitle": "{agent} 连接失败",
|
||||
"toolFallbackTitle": "工具",
|
||||
"eventErrorTitle": "Agent 错误"
|
||||
},
|
||||
"connectionLifecycle": {
|
||||
"tasks": {
|
||||
@@ -1092,6 +1094,11 @@
|
||||
"connectionFailed": "连接失败"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"attachedResources": "附加资源",
|
||||
"toolCallFailed": "工具调用失败",
|
||||
"planUpdated": "计划已更新"
|
||||
},
|
||||
"messageThread": {
|
||||
"emptyTitle": "暂无消息",
|
||||
"emptyDescription": "开始一个会话后,消息会显示在这里"
|
||||
@@ -1222,6 +1229,38 @@
|
||||
"subjectLabel": "主题",
|
||||
"taskLabel": "任务",
|
||||
"nameLabel": "名称:",
|
||||
"field": {
|
||||
"file": "文件",
|
||||
"notebook": "Notebook",
|
||||
"command": "命令",
|
||||
"old": "旧内容",
|
||||
"new": "新内容",
|
||||
"pattern": "模式",
|
||||
"path": "路径",
|
||||
"query": "查询",
|
||||
"url": "URL",
|
||||
"description": "描述",
|
||||
"content": "内容",
|
||||
"source": "源内容",
|
||||
"prompt": "提示词",
|
||||
"subject": "主题",
|
||||
"taskId": "任务 ID",
|
||||
"status": "状态",
|
||||
"skill": "Skill",
|
||||
"args": "参数",
|
||||
"offset": "偏移",
|
||||
"limit": "限制",
|
||||
"glob": "Glob",
|
||||
"type": "类型",
|
||||
"output": "输出",
|
||||
"replaceAll": "全部替换",
|
||||
"language": "语言",
|
||||
"timeout": "超时",
|
||||
"background": "后台",
|
||||
"agentType": "Agent 类型",
|
||||
"library": "库",
|
||||
"libraryId": "库 ID"
|
||||
},
|
||||
"title": {
|
||||
"edit": "编辑",
|
||||
"command": "命令",
|
||||
|
||||
@@ -1079,7 +1079,9 @@
|
||||
"preflightCheckFailedDefault": "預檢查未通過,請檢查 Agent 設定。",
|
||||
"preflightFailedTitle": "{agent} 預檢查失敗",
|
||||
"autoLinkPreflightFailed": "自動連結預檢查失敗:{message}",
|
||||
"connectFailedTitle": "{agent} 連線失敗"
|
||||
"connectFailedTitle": "{agent} 連線失敗",
|
||||
"toolFallbackTitle": "工具",
|
||||
"eventErrorTitle": "Agent 錯誤"
|
||||
},
|
||||
"connectionLifecycle": {
|
||||
"tasks": {
|
||||
@@ -1092,6 +1094,11 @@
|
||||
"connectionFailed": "連線失敗"
|
||||
}
|
||||
},
|
||||
"shared": {
|
||||
"attachedResources": "附加資源",
|
||||
"toolCallFailed": "工具呼叫失敗",
|
||||
"planUpdated": "計畫已更新"
|
||||
},
|
||||
"messageThread": {
|
||||
"emptyTitle": "暫無訊息",
|
||||
"emptyDescription": "開始一個會話後,訊息會顯示在這裡"
|
||||
@@ -1222,6 +1229,38 @@
|
||||
"subjectLabel": "主題",
|
||||
"taskLabel": "任務",
|
||||
"nameLabel": "名稱:",
|
||||
"field": {
|
||||
"file": "檔案",
|
||||
"notebook": "Notebook",
|
||||
"command": "命令",
|
||||
"old": "舊內容",
|
||||
"new": "新內容",
|
||||
"pattern": "模式",
|
||||
"path": "路徑",
|
||||
"query": "查詢",
|
||||
"url": "URL",
|
||||
"description": "描述",
|
||||
"content": "內容",
|
||||
"source": "來源內容",
|
||||
"prompt": "提示詞",
|
||||
"subject": "主題",
|
||||
"taskId": "任務 ID",
|
||||
"status": "狀態",
|
||||
"skill": "Skill",
|
||||
"args": "參數",
|
||||
"offset": "位移",
|
||||
"limit": "限制",
|
||||
"glob": "Glob",
|
||||
"type": "類型",
|
||||
"output": "輸出",
|
||||
"replaceAll": "全部替換",
|
||||
"language": "語言",
|
||||
"timeout": "逾時",
|
||||
"background": "背景",
|
||||
"agentType": "Agent 類型",
|
||||
"library": "函式庫",
|
||||
"libraryId": "函式庫 ID"
|
||||
},
|
||||
"title": {
|
||||
"edit": "編輯",
|
||||
"command": "命令",
|
||||
|
||||
@@ -60,6 +60,12 @@ export interface AdaptedMessage {
|
||||
model?: string | null
|
||||
}
|
||||
|
||||
export interface AdapterMessageText {
|
||||
attachedResources: string
|
||||
toolCallFailed: string
|
||||
planUpdated: string
|
||||
}
|
||||
|
||||
type InlineToolSegment =
|
||||
| { kind: "text"; value: string }
|
||||
| { kind: "tool_call" | "tool_result"; value: string }
|
||||
@@ -271,7 +277,8 @@ function parseInlineToolResultPayload(payload: string): {
|
||||
function expandInlineToolText(
|
||||
text: string,
|
||||
messageId: string,
|
||||
blockIndex: number
|
||||
blockIndex: number,
|
||||
toolCallFailedText: string
|
||||
): AdaptedContentPart[] | null {
|
||||
const segments = splitInlineToolSegments(text)
|
||||
if (!segments) return null
|
||||
@@ -320,7 +327,7 @@ function expandInlineToolText(
|
||||
output = parsedResult.output
|
||||
if (parsedResult.isError) {
|
||||
state = "output-error"
|
||||
errorText = output ?? "Tool call failed"
|
||||
errorText = output ?? toolCallFailedText
|
||||
}
|
||||
index = lookahead
|
||||
}
|
||||
@@ -345,7 +352,7 @@ function expandInlineToolText(
|
||||
toolCallId,
|
||||
output: parsedResult.output,
|
||||
errorText: parsedResult.isError
|
||||
? (parsedResult.output ?? "Tool call failed")
|
||||
? (parsedResult.output ?? toolCallFailedText)
|
||||
: undefined,
|
||||
state: parsedResult.isError ? "output-error" : "output-available",
|
||||
})
|
||||
@@ -440,7 +447,10 @@ export function extractUserResourcesFromText(text: string): {
|
||||
}
|
||||
}
|
||||
|
||||
function splitUserTextAndResources(parts: AdaptedContentPart[]): {
|
||||
function splitUserTextAndResources(
|
||||
parts: AdaptedContentPart[],
|
||||
attachedResourcesText: string
|
||||
): {
|
||||
parts: AdaptedContentPart[]
|
||||
resources: UserResourceDisplay[]
|
||||
} {
|
||||
@@ -464,7 +474,7 @@ function splitUserTextAndResources(parts: AdaptedContentPart[]): {
|
||||
}
|
||||
|
||||
if (nextParts.length === 0 && resources.length > 0) {
|
||||
nextParts.push({ type: "text", text: "Attached resources" })
|
||||
nextParts.push({ type: "text", text: attachedResourcesText })
|
||||
}
|
||||
|
||||
return { parts: nextParts, resources }
|
||||
@@ -545,7 +555,10 @@ function buildToolResultMap(
|
||||
* Transform a MessageTurn (from backend) to AdaptedMessage format.
|
||||
* Same correlation logic as adaptUnifiedMessage but operates on turn.blocks.
|
||||
*/
|
||||
export function adaptMessageTurn(turn: MessageTurn): AdaptedMessage {
|
||||
export function adaptMessageTurn(
|
||||
turn: MessageTurn,
|
||||
text: Pick<AdapterMessageText, "attachedResources" | "toolCallFailed">
|
||||
): AdaptedMessage {
|
||||
const adaptedContent: AdaptedContentPart[] = []
|
||||
const resultMap = buildToolResultMap(turn.blocks)
|
||||
const matchedResultIds = new Set<string>()
|
||||
@@ -557,7 +570,12 @@ export function adaptMessageTurn(turn: MessageTurn): AdaptedMessage {
|
||||
const block = turn.blocks[index]
|
||||
|
||||
if (turn.role === "assistant" && block.type === "text") {
|
||||
const expandedParts = expandInlineToolText(block.text, turn.id, index)
|
||||
const expandedParts = expandInlineToolText(
|
||||
block.text,
|
||||
turn.id,
|
||||
index,
|
||||
text.toolCallFailed
|
||||
)
|
||||
if (expandedParts) {
|
||||
adaptedContent.push(...expandedParts)
|
||||
continue
|
||||
@@ -641,7 +659,7 @@ export function adaptMessageTurn(turn: MessageTurn): AdaptedMessage {
|
||||
|
||||
const userSplit =
|
||||
turn.role === "user"
|
||||
? splitUserTextAndResources(adaptedContent)
|
||||
? splitUserTextAndResources(adaptedContent, text.attachedResources)
|
||||
: { parts: adaptedContent, resources: [] as UserResourceDisplay[] }
|
||||
|
||||
return {
|
||||
@@ -661,8 +679,11 @@ export function adaptMessageTurn(turn: MessageTurn): AdaptedMessage {
|
||||
* Transform all turns in a conversation to AdaptedMessage[].
|
||||
* Internally computes completedToolIds so callers don't need to.
|
||||
*/
|
||||
export function adaptMessageTurns(turns: MessageTurn[]): AdaptedMessage[] {
|
||||
return turns.map((turn) => adaptMessageTurn(turn))
|
||||
export function adaptMessageTurns(
|
||||
turns: MessageTurn[],
|
||||
text: Pick<AdapterMessageText, "attachedResources" | "toolCallFailed">
|
||||
): AdaptedMessage[] {
|
||||
return turns.map((turn) => adaptMessageTurn(turn, text))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -942,19 +963,22 @@ function selectLiveToolOutput(params: {
|
||||
}
|
||||
|
||||
function formatPlanEntries(
|
||||
entries: Array<{ content: string; priority: string; status: string }>
|
||||
entries: Array<{ content: string; priority: string; status: string }>,
|
||||
planUpdatedText: string
|
||||
): string {
|
||||
if (entries.length === 0) {
|
||||
return "Plan updated"
|
||||
return planUpdatedText
|
||||
}
|
||||
const lines = entries.map(
|
||||
(entry) => `- [${entry.status}] ${entry.content} (${entry.priority})`
|
||||
)
|
||||
return `Plan updated:\n${lines.join("\n")}`
|
||||
return `${planUpdatedText}:\n${lines.join("\n")}`
|
||||
}
|
||||
|
||||
interface AdaptLiveMessageOptions {
|
||||
isLiveStreaming?: boolean
|
||||
toolCallFailedText: string
|
||||
planUpdatedText: string
|
||||
}
|
||||
|
||||
function isReasoningBlock(block: LiveMessage["content"][number]): boolean {
|
||||
@@ -976,7 +1000,7 @@ function findLastReasoningIndex(message: LiveMessage): number {
|
||||
*/
|
||||
export function adaptLiveMessageFromAcp(
|
||||
message: LiveMessage,
|
||||
options: AdaptLiveMessageOptions = {}
|
||||
options: AdaptLiveMessageOptions
|
||||
): AdaptedMessage {
|
||||
const isLiveStreaming = options.isLiveStreaming ?? true
|
||||
const adaptedContent: AdaptedContentPart[] = []
|
||||
@@ -1034,7 +1058,7 @@ export function adaptLiveMessageFromAcp(
|
||||
output,
|
||||
errorText:
|
||||
state === "output-error"
|
||||
? selectedOutput || "Tool call failed"
|
||||
? selectedOutput || options.toolCallFailedText
|
||||
: undefined,
|
||||
})
|
||||
break
|
||||
@@ -1043,7 +1067,7 @@ export function adaptLiveMessageFromAcp(
|
||||
case "plan":
|
||||
adaptedContent.push({
|
||||
type: "reasoning",
|
||||
content: formatPlanEntries(block.entries),
|
||||
content: formatPlanEntries(block.entries, options.planUpdatedText),
|
||||
isStreaming: index === lastStreamingReasoningIndex,
|
||||
})
|
||||
break
|
||||
|
||||
@@ -10,15 +10,24 @@ function isResourceLinkBlock(
|
||||
return block.type === "resource_link"
|
||||
}
|
||||
|
||||
export function getPromptDraftDisplayText(draft: PromptDraft): string {
|
||||
export function getPromptDraftDisplayText(
|
||||
draft: PromptDraft,
|
||||
attachedResourcesFallback: string
|
||||
): string {
|
||||
const trimmed = draft.displayText.trim()
|
||||
return trimmed || "Attached resources"
|
||||
return trimmed || attachedResourcesFallback
|
||||
}
|
||||
|
||||
export function buildUserMessageTextPartsFromDraft(
|
||||
draft: PromptDraft
|
||||
draft: PromptDraft,
|
||||
attachedResourcesFallback: string
|
||||
): AdaptedContentPart[] {
|
||||
return [{ type: "text", text: getPromptDraftDisplayText(draft) }]
|
||||
return [
|
||||
{
|
||||
type: "text",
|
||||
text: getPromptDraftDisplayText(draft, attachedResourcesFallback),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export function extractUserResourcesFromDraft(
|
||||
|
||||
@@ -23,6 +23,8 @@ export type AgentType =
|
||||
export type AppErrorCode =
|
||||
| "unknown"
|
||||
| "invalid_input"
|
||||
| "configuration_missing"
|
||||
| "configuration_invalid"
|
||||
| "not_found"
|
||||
| "already_exists"
|
||||
| "permission_denied"
|
||||
|
||||
Reference in New Issue
Block a user