diff --git a/apps/cli/src/ai/cross-chat-tool-adapter.ts b/apps/cli/src/ai/cross-chat-tool-adapter.ts index fb4a64f1..e64a00f9 100644 --- a/apps/cli/src/ai/cross-chat-tool-adapter.ts +++ b/apps/cli/src/ai/cross-chat-tool-adapter.ts @@ -5,12 +5,7 @@ import type { SessionRuntimeAdapter, } from '@openchatlab/node-runtime' import { countTokens, preprocessCrossChatMessages } from '@openchatlab/node-runtime' -import { - CROSS_CHAT_AGENT_TOOL_REGISTRY, - executeToolForAgent, - toAgentToolParameters, - type CrossChatToolExecutionContext, -} from '@openchatlab/tools' +import { createCrossChatAgentToolAdapters, type CrossChatToolExecutionContext } from '@openchatlab/tools' export function createCliCrossChatTools(options: { analysisService: CrossChatAnalysisService @@ -33,17 +28,5 @@ export function createCliCrossChatTools(options: { ), } - return CROSS_CHAT_AGENT_TOOL_REGISTRY.map((tool) => ({ - name: tool.name, - label: tool.name, - description: tool.description, - parameters: toAgentToolParameters(tool.inputSchema) as any, - executionMode: tool.executionMode, - execute: async (_toolCallId: string, params: unknown, signal, onUpdate) => - executeToolForAgent(tool, params, { - ...context, - abortSignal: signal, - reportProgress: (progress) => onUpdate?.({ content: [], details: { progress } }), - }), - })) + return createCrossChatAgentToolAdapters(context) as AgentTool[] } diff --git a/apps/desktop/main/ai/cross-chat-tool-adapter.ts b/apps/desktop/main/ai/cross-chat-tool-adapter.ts index b3e71008..f75e3bc8 100644 --- a/apps/desktop/main/ai/cross-chat-tool-adapter.ts +++ b/apps/desktop/main/ai/cross-chat-tool-adapter.ts @@ -5,12 +5,7 @@ import type { SessionRuntimeAdapter, } from '@openchatlab/node-runtime' import { countTokens, preprocessCrossChatMessages } from '@openchatlab/node-runtime' -import { - CROSS_CHAT_AGENT_TOOL_REGISTRY, - executeToolForAgent, - toAgentToolParameters, - type CrossChatToolExecutionContext, -} from '@openchatlab/tools' +import { createCrossChatAgentToolAdapters, type CrossChatToolExecutionContext } from '@openchatlab/tools' export function createElectronCrossChatTools(options: { analysisService: CrossChatAnalysisService @@ -33,17 +28,5 @@ export function createElectronCrossChatTools(options: { ), } - return CROSS_CHAT_AGENT_TOOL_REGISTRY.map((tool) => ({ - name: tool.name, - label: tool.name, - description: tool.description, - parameters: toAgentToolParameters(tool.inputSchema) as any, - executionMode: tool.executionMode, - execute: async (_toolCallId: string, params: unknown, signal, onUpdate) => - executeToolForAgent(tool, params, { - ...context, - abortSignal: signal, - reportProgress: (progress) => onUpdate?.({ content: [], details: { progress } }), - }), - })) + return createCrossChatAgentToolAdapters(context) as AgentTool[] } diff --git a/packages/tools/src/agent-adapter.test.ts b/packages/tools/src/agent-adapter.test.ts new file mode 100644 index 00000000..6a7d7338 --- /dev/null +++ b/packages/tools/src/agent-adapter.test.ts @@ -0,0 +1,75 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import type { + CrossChatGlobalActivitySummaryRequest, + CrossChatGlobalActivitySummaryResult, +} from '@openchatlab/shared-types' +import { createCrossChatAgentToolAdapters } from './agent-adapter' +import type { CrossChatAnalysisToolService } from './types' + +function globalActivitySummary(): CrossChatGlobalActivitySummaryResult { + return { + mode: 'year', + dataState: 'fresh', + summary: { + range: { mode: 'year', year: 2026, startTs: 1, endTs: 2 }, + availableDataYears: [2026], + latestDataYear: 2026, + metrics: { + sentMessageCount: 100, + activeDayCount: 10, + directContactCount: 5, + averageMessagesPerDay: 10, + averageDirectContactsPerDay: 1, + }, + monthlyActivity: [], + monthlyDirectContacts: [], + dailyActivity: [], + messageTypes: [], + textLength: null, + coverage: { + totalSessions: 2, + analyzedSessions: 2, + missingOwnerSessions: 0, + unresolvedOwnerSessions: 0, + failedSessions: 0, + }, + cache: { status: 'fresh', computedAt: 3 }, + task: { + id: null, + status: 'idle', + startedAt: null, + finishedAt: null, + processedSessions: 0, + totalSessions: 0, + }, + }, + } +} + +test('the shared Desktop and CLI Web adapter exposes and executes all ten cross-chat tools', async () => { + const requests: CrossChatGlobalActivitySummaryRequest[] = [] + const analysisService = { + getGlobalActivitySummary(request: CrossChatGlobalActivitySummaryRequest) { + requests.push(request) + return globalActivitySummary() + }, + } as unknown as CrossChatAnalysisToolService + const tools = createCrossChatAgentToolAdapters({ + locale: 'zh-CN', + analysisService, + maxToolResultTokens: 16_000, + preprocessMessagesBySession: async (_sessionId, messages) => messages, + }) + + assert.equal(tools.length, 10) + const summaryTool = tools.find((tool) => tool.name === 'get_global_activity_summary') + assert.ok(summaryTool) + assert.deepEqual(Object.keys(summaryTool.parameters.properties), ['mode', 'year']) + + const result = await summaryTool.execute('summary-call', { mode: 'year', year: 2026 }, new AbortController().signal) + + assert.deepEqual(requests, [{ mode: 'year', year: 2026 }]) + assert.equal((result.details as CrossChatGlobalActivitySummaryResult).dataState, 'fresh') + assert.equal(JSON.parse(result.content[0]?.text ?? '{}').metrics.sentMessageCount, 100) +}) diff --git a/packages/tools/src/agent-adapter.ts b/packages/tools/src/agent-adapter.ts index c2cc7ee9..1b13c3b8 100644 --- a/packages/tools/src/agent-adapter.ts +++ b/packages/tools/src/agent-adapter.ts @@ -1,4 +1,5 @@ -import type { JsonSchema, ToolDefinition, ToolResult } from './types' +import { CROSS_CHAT_AGENT_TOOL_REGISTRY } from './registry' +import type { CrossChatToolExecutionContext, JsonSchema, ToolDefinition, ToolProgress, ToolResult } from './types' interface AgentToolParameters { type: 'object' @@ -64,3 +65,27 @@ export async function executeToolForAgent( return { content: [{ type: 'text', text: `Error: ${message}` }], details: null, isError: true } } } + +/** Desktop 与 CLI Web 共用同一套跨会话工具装配,避免平台 wrapper 漏注册或产生 schema 漂移。 */ +export function createCrossChatAgentToolAdapters( + context: Omit +) { + return CROSS_CHAT_AGENT_TOOL_REGISTRY.map((tool) => ({ + name: tool.name, + label: tool.name, + description: tool.description, + parameters: toAgentToolParameters(tool.inputSchema), + executionMode: tool.executionMode, + execute: async ( + _toolCallId: string, + params: unknown, + signal: AbortSignal, + onUpdate?: (update: { content: []; details: { progress: ToolProgress } }) => void + ) => + executeToolForAgent(tool, params, { + ...context, + abortSignal: signal, + reportProgress: (progress) => onUpdate?.({ content: [], details: { progress } }), + }), + })) +} diff --git a/packages/tools/src/index.ts b/packages/tools/src/index.ts index f934473e..76331064 100644 --- a/packages/tools/src/index.ts +++ b/packages/tools/src/index.ts @@ -19,7 +19,7 @@ export { export { CoreDataProvider } from './providers/core-data-provider' // === Agent Adapter === -export { executeToolForAgent, toAgentToolParameters } from './agent-adapter' +export { createCrossChatAgentToolAdapters, executeToolForAgent, toAgentToolParameters } from './agent-adapter' export { getLocalizedToolMetadata } from './tool-metadata' // === Tool Definitions ===