Files
zn-ai/electron/gateway/browser-shortcut.ts
DEV_DSW 4c61e93c3e Add unit tests for skill capabilities, skill planner, and UV setup
- Implement tests for random ID generation, ensuring preference for crypto.randomUUID.
- Create tests for runtime context capabilities, validating the injection of enabled skill capabilities.
- Add tests for skill capability parsing, including classification and command example extraction.
- Introduce tests for the skill planner, verifying tool call planning based on user requests and attachment requirements.
- Establish tests for UV setup, ensuring proper handling of Python installation scenarios and environment checks.
2026-04-24 17:02:59 +08:00

187 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logManager from '@electron/service/logger';
import { extractBrowserOpenIntent, openUrlInBrowser } from '@electron/service/browser-open-service';
import { appendTranscriptLine } from '@electron/utils/token-usage-writer';
import type { RawMessage, ToolResultPayload, ToolStatus } from '@runtime/shared/chat-model';
import { sessionStore } from './session-store';
import type { GatewayEvent } from './types';
function buildBrowserOpenResponseText(result: { pageUrl: string; title?: string }): string {
const suffix = result.title ? `${result.title}` : '';
return `已为你打开 ${result.pageUrl}${suffix}`;
}
function buildBrowserOpenErrorText(error: unknown): string {
return `打开失败:${error instanceof Error ? error.message : String(error)}`;
}
async function processBrowserOpen(
sessionKey: string,
runId: string,
url: string,
signal: AbortSignal,
broadcast: (event: GatewayEvent) => void,
) {
let assistantText = '';
const toolCallId = `browser.open_url:${runId}`;
const startedAt = Date.now();
let finalToolStatus: ToolStatus | null = null;
let finalToolResult: ToolResultPayload | null = null;
broadcast({
type: 'tool:status',
sessionKey,
runId,
toolCallId,
toolName: 'browser.open_url',
status: 'running',
updatedAt: startedAt,
summary: `Opening ${url}`,
input: { url },
});
try {
const result = await openUrlInBrowser(url, { signal });
if (signal.aborted) {
return;
}
assistantText = buildBrowserOpenResponseText(result);
finalToolResult = {
ok: true,
summary: assistantText,
structuredData: result,
renderHints: {
card: 'browser-step',
},
raw: result,
};
finalToolStatus = {
id: toolCallId,
toolCallId,
name: 'browser.open_url',
status: 'completed',
updatedAt: Date.now(),
durationMs: Date.now() - startedAt,
summary: assistantText,
input: { url },
result,
};
broadcast({
type: 'tool:status',
sessionKey,
runId,
toolCallId,
toolName: 'browser.open_url',
status: finalToolStatus.status,
updatedAt: finalToolStatus.updatedAt,
durationMs: finalToolStatus.durationMs,
summary: finalToolStatus.summary,
input: finalToolStatus.input,
result: finalToolStatus.result,
});
} catch (error) {
if (signal.aborted) {
return;
}
assistantText = buildBrowserOpenErrorText(error);
finalToolResult = {
ok: false,
summary: assistantText,
error: error instanceof Error ? error.message : String(error),
retryable: true,
renderHints: {
card: 'browser-step',
},
raw: {
error: error instanceof Error ? error.message : String(error),
},
};
finalToolStatus = {
id: toolCallId,
toolCallId,
name: 'browser.open_url',
status: 'error',
updatedAt: Date.now(),
durationMs: Date.now() - startedAt,
summary: assistantText,
input: { url },
result: {
error: error instanceof Error ? error.message : String(error),
},
};
broadcast({
type: 'tool:status',
sessionKey,
runId,
toolCallId,
toolName: 'browser.open_url',
status: finalToolStatus.status,
updatedAt: finalToolStatus.updatedAt,
durationMs: finalToolStatus.durationMs,
summary: finalToolStatus.summary,
input: finalToolStatus.input,
result: finalToolStatus.result,
});
}
sessionStore.clearActiveRun(sessionKey);
const finalMessage: RawMessage = {
role: 'assistant',
content: assistantText,
timestamp: Date.now(),
toolResult: finalToolResult,
_toolStatuses: finalToolStatus ? [finalToolStatus] : undefined,
};
sessionStore.appendMessage(sessionKey, finalMessage);
appendTranscriptLine(sessionKey, {
type: 'message',
timestamp: new Date().toISOString(),
message: {
role: 'assistant',
content: assistantText,
tool: 'browser.open_url',
},
});
broadcast({
type: 'chat:final',
sessionKey,
runId,
message: finalMessage,
});
}
export function maybeHandleBrowserOpenMessage(
sessionKey: string,
runId: string,
message: RawMessage,
broadcast: (event: GatewayEvent) => void,
): boolean {
const browserIntent = typeof message.content === 'string'
? extractBrowserOpenIntent(message.content)
: null;
if (!browserIntent) {
return false;
}
const abortController = new AbortController();
sessionStore.setActiveRun(sessionKey, runId, abortController);
processBrowserOpen(sessionKey, runId, browserIntent.url, abortController.signal, broadcast).catch(
(error) => {
logManager.error('Unexpected error in processBrowserOpen:', error);
sessionStore.clearActiveRun(sessionKey);
broadcast({
type: 'chat:error',
sessionKey,
runId,
error: error instanceof Error ? error.message : String(error),
});
},
);
return true;
}