@huyooo/ai-chat-bridge-electron 0.3.29 → 0.3.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,68 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 流式语音识别 WebSocket 客户端
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 仅支持双向流式优化版 bigmodel_async。
|
|
5
|
+
* 连接 ai-server 平台 WS 网关(Bearer + ticket),不持有火山密钥。
|
|
5
6
|
*/
|
|
6
7
|
import { type AsrResult } from './protocol';
|
|
7
|
-
/**
|
|
8
|
+
/** 平台网关鉴权 */
|
|
9
|
+
export interface AsrPlatformAuth {
|
|
10
|
+
/** 完整 wss URL(含 sessionId/ticket/appId query)或 path */
|
|
11
|
+
wsUrl: string;
|
|
12
|
+
/** 用户登录 Bearer token */
|
|
13
|
+
authorization: string;
|
|
14
|
+
/** 可选覆盖 app-id header */
|
|
15
|
+
appId?: string;
|
|
16
|
+
}
|
|
17
|
+
/** ASR 客户端配置(仅平台网关) */
|
|
8
18
|
export interface AsrClientConfig {
|
|
9
|
-
|
|
10
|
-
appId: string;
|
|
11
|
-
/** 火山引擎 Access Key */
|
|
12
|
-
accessKey: string;
|
|
13
|
-
/** 资源 ID */
|
|
14
|
-
resourceId?: string;
|
|
15
|
-
/** 使用双向流式优化版 */
|
|
16
|
-
useAsyncMode?: boolean;
|
|
19
|
+
auth: AsrPlatformAuth;
|
|
17
20
|
}
|
|
18
21
|
/** ASR 事件回调 */
|
|
19
22
|
export interface AsrCallbacks {
|
|
20
|
-
/** 连接成功 */
|
|
21
23
|
onConnected?: () => void;
|
|
22
|
-
/** 收到识别结果 */
|
|
23
24
|
onResult?: (result: AsrResult, isLast: boolean) => void;
|
|
24
|
-
/** 发生错误 */
|
|
25
25
|
onError?: (error: Error) => void;
|
|
26
|
-
/** 连接关闭 */
|
|
27
26
|
onClose?: () => void;
|
|
28
27
|
}
|
|
29
28
|
/** ASR 会话配置 */
|
|
30
29
|
export interface AsrSessionConfig {
|
|
31
|
-
/** 音频格式 */
|
|
32
30
|
format?: 'pcm' | 'wav';
|
|
33
|
-
/** 采样率 */
|
|
34
31
|
sampleRate?: number;
|
|
35
|
-
/** 启用标点 */
|
|
36
32
|
enablePunc?: boolean;
|
|
37
|
-
/** 启用 ITN(数字规整) */
|
|
38
33
|
enableItn?: boolean;
|
|
39
|
-
/** 启用语义顺滑 */
|
|
40
34
|
enableDdc?: boolean;
|
|
41
|
-
/** 输出分句信息 */
|
|
42
35
|
showUtterances?: boolean;
|
|
43
36
|
}
|
|
44
37
|
/**
|
|
45
|
-
* ASR WebSocket
|
|
46
|
-
*
|
|
47
|
-
* @example
|
|
48
|
-
* ```ts
|
|
49
|
-
* const client = new AsrClient({
|
|
50
|
-
* appId: 'your-app-id',
|
|
51
|
-
* accessKey: 'your-access-key',
|
|
52
|
-
* });
|
|
53
|
-
*
|
|
54
|
-
* await client.connect({
|
|
55
|
-
* onResult: (result, isLast) => {
|
|
56
|
-
* console.log('识别结果:', result.result?.text);
|
|
57
|
-
* },
|
|
58
|
-
* onError: (error) => {
|
|
59
|
-
* console.error('错误:', error);
|
|
60
|
-
* },
|
|
61
|
-
* });
|
|
62
|
-
*
|
|
63
|
-
* client.sendAudio(audioBuffer);
|
|
64
|
-
* client.finish(); // 发送最后一包
|
|
65
|
-
* ```
|
|
38
|
+
* ASR WebSocket 客户端(仅 bigmodel_async,经平台网关)
|
|
66
39
|
*/
|
|
67
40
|
export declare class AsrClient {
|
|
68
41
|
private config;
|
|
@@ -72,41 +45,11 @@ export declare class AsrClient {
|
|
|
72
45
|
private isConnected;
|
|
73
46
|
private sessionConfig;
|
|
74
47
|
constructor(config: AsrClientConfig);
|
|
75
|
-
/**
|
|
76
|
-
* 获取 WebSocket 连接地址
|
|
77
|
-
*/
|
|
78
|
-
private getWsUrl;
|
|
79
|
-
/**
|
|
80
|
-
* 获取资源 ID
|
|
81
|
-
*/
|
|
82
|
-
private getResourceId;
|
|
83
|
-
/**
|
|
84
|
-
* 连接到 ASR 服务
|
|
85
|
-
*/
|
|
86
48
|
connect(callbacks: AsrCallbacks, sessionConfig?: AsrSessionConfig): Promise<void>;
|
|
87
|
-
/**
|
|
88
|
-
* 发送首包(Full Client Request)
|
|
89
|
-
*/
|
|
90
49
|
private sendFullClientRequest;
|
|
91
|
-
/**
|
|
92
|
-
* 发送音频数据
|
|
93
|
-
* @param audioData PCM 音频数据(16bit, 16kHz, 单声道)
|
|
94
|
-
*/
|
|
95
50
|
sendAudio(audioData: Buffer): void;
|
|
96
|
-
/**
|
|
97
|
-
* 发送最后一包并结束识别
|
|
98
|
-
*/
|
|
99
51
|
finish(): void;
|
|
100
|
-
/**
|
|
101
|
-
* 断开连接
|
|
102
|
-
*/
|
|
103
52
|
disconnect(): void;
|
|
104
|
-
/**
|
|
105
|
-
* 检查是否已连接
|
|
106
|
-
*/
|
|
107
53
|
get connected(): boolean;
|
|
108
54
|
}
|
|
109
|
-
/**
|
|
110
|
-
* 创建 ASR 客户端实例
|
|
111
|
-
*/
|
|
112
55
|
export declare function createAsrClient(config: AsrClientConfig): AsrClient;
|
package/dist/main/asr/index.d.ts
CHANGED
|
@@ -3,33 +3,26 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export * from './protocol';
|
|
5
5
|
export * from './client';
|
|
6
|
-
/**
|
|
6
|
+
/** 每次 start 时解析平台网关鉴权(create session + ticket) */
|
|
7
|
+
export type ResolveAsrPlatformAuth = () => Promise<{
|
|
8
|
+
wsUrl: string;
|
|
9
|
+
authorization: string;
|
|
10
|
+
appId?: string;
|
|
11
|
+
billingSessionId?: string;
|
|
12
|
+
}>;
|
|
13
|
+
/** ASR 桥接配置:仅平台网关(无火山密钥) */
|
|
7
14
|
export interface AsrBridgeConfig {
|
|
8
|
-
/** IPC channel 前缀 */
|
|
9
15
|
channelPrefix?: string;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
/** 解析平台 WS 鉴权 */
|
|
17
|
+
resolvePlatformAuth: ResolveAsrPlatformAuth;
|
|
18
|
+
onAfterFinish?: (info: {
|
|
19
|
+
billingSessionId?: string;
|
|
20
|
+
durationMs: number;
|
|
21
|
+
}) => Promise<void>;
|
|
16
22
|
}
|
|
17
23
|
/**
|
|
18
|
-
* 创建 ASR Electron
|
|
19
|
-
*
|
|
20
|
-
* 在主进程中调用,注册 ASR 相关的 IPC handlers
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* ```ts
|
|
24
|
-
* import { createAsrBridge } from '@huyooo/ai-chat-bridge-electron/main';
|
|
25
|
-
*
|
|
26
|
-
* createAsrBridge({
|
|
27
|
-
* appId: process.env.VOLC_APP_ID!,
|
|
28
|
-
* accessKey: process.env.VOLC_ACCESS_KEY!,
|
|
29
|
-
* });
|
|
30
|
-
* ```
|
|
24
|
+
* 创建 ASR Electron 桥接(仅 bigmodel_async,经平台 WS 网关)
|
|
31
25
|
*/
|
|
32
26
|
export declare function createAsrBridge(config: AsrBridgeConfig): {
|
|
33
|
-
/** 清理所有会话 */
|
|
34
27
|
cleanup: () => void;
|
|
35
28
|
};
|
|
@@ -16,19 +16,16 @@
|
|
|
16
16
|
* tavilySearchTool(API_KEY),
|
|
17
17
|
* getWeatherTool(),
|
|
18
18
|
* ],
|
|
19
|
-
* asr: {
|
|
19
|
+
* asr: { resolvePlatformAuth: async () => ({ wsUrl, authorization }) },
|
|
20
20
|
* });
|
|
21
21
|
* ```
|
|
22
22
|
*/
|
|
23
23
|
import type { LLMConfig, Tool, ToolPlugin, ChatOptions, DataEngineContext } from '@huyooo/ai-chat-core';
|
|
24
24
|
import type { StorageContext, StorageAdapter, SqliteDatabaseFactory } from '@huyooo/ai-chat-storage';
|
|
25
25
|
import { type AssetManager, type AssetMetadataStore } from '@huyooo/ai-chat-host-node';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
accessKey: string;
|
|
30
|
-
resourceId?: string;
|
|
31
|
-
}
|
|
26
|
+
import { type AsrBridgeConfig } from './asr';
|
|
27
|
+
/** ASR 语音识别配置(透传 createAsrBridge) */
|
|
28
|
+
export type AsrConfig = Omit<AsrBridgeConfig, 'channelPrefix'>;
|
|
32
29
|
/**
|
|
33
30
|
* Electron AI Chat 配置
|
|
34
31
|
*
|
package/dist/main/index.d.ts
CHANGED
|
@@ -134,7 +134,7 @@ export declare function createElectronBridge(options: ElectronBridgeOptions): Pr
|
|
|
134
134
|
}>;
|
|
135
135
|
export type { LLMConfig, ChatEvent, ChatOptions, ChatMode, ProtocolId, ModelOption, StorageAdapter, StorageContext, SessionRecord, MessageRecord, };
|
|
136
136
|
export { buildModelOptions };
|
|
137
|
-
export { createAsrBridge, type AsrBridgeConfig } from './asr';
|
|
137
|
+
export { createAsrBridge, type AsrBridgeConfig, type ResolveAsrPlatformAuth } from './asr';
|
|
138
138
|
export type { AsrClientConfig, AsrSessionConfig, AsrCallbacks } from './asr/client';
|
|
139
139
|
export type { AsrResult, AsrRequestConfig } from './asr/protocol';
|
|
140
140
|
export { createElectronChat } from './create-electron-chat';
|
package/dist/main/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ipcMain as e,shell as t}from"electron";import*as n from"fs";import*as r from"path";import{ChatRuntime as i,buildModelOptions as a,isToolError as o,normalizeToolResult as s,resolveToolGovernanceSnapshot as c,resolveToolResultUi as l,serializeToolResult as u}from"@huyooo/ai-chat-core";import{assetManager as d,createDefaultToolExecutor as f}from"@huyooo/ai-chat-host-node";import{createStorage as p}from"@huyooo/ai-chat-storage";import*as m from"zlib";import h from"ws";import{v4 as g}from"uuid";const _={FULL_CLIENT_REQUEST:1,AUDIO_ONLY_REQUEST:2,FULL_SERVER_RESPONSE:9,ERROR_RESPONSE:15},v={NONE:0,HAS_SEQUENCE:1,LAST_PACKET:2,LAST_PACKET_WITH_SEQUENCE:3},y={NONE:0,JSON:1},b={NONE:0,GZIP:1};function x(e,t,n,r){return[17,e<<4|t,n<<4|r,0]}function S(e,t=!0){let n=x(_.FULL_CLIENT_REQUEST,v.NONE,y.JSON,t?b.GZIP:b.NONE),r=JSON.stringify(e),i=Buffer.from(r,`utf-8`),a=t?m.gzipSync(i):i,o=Buffer.alloc(4);return o.writeUInt32BE(a.length,0),Buffer.concat([Buffer.from(n),o,a])}function C(e,t=!1,n=!0){let r=x(_.AUDIO_ONLY_REQUEST,t?v.LAST_PACKET:v.NONE,y.NONE,n?b.GZIP:b.NONE),i=n?m.gzipSync(e):e,a=Buffer.alloc(4);return a.writeUInt32BE(i.length,0),Buffer.concat([Buffer.from(r),a,i])}function w(e){if(e.length<4)throw Error(`Invalid response: too short`);let t=e[0],n=e[1],r=e[2],i=t>>4&15,a=(t&15)*4,o=n>>4&15,s=n&15,c=r>>4&15,l=r&15;if(i!==1)throw Error(`Unsupported protocol version: ${i}`);let u=a,d;(s&1)==1&&e.length>=u+4&&(d=e.readUInt32BE(u),u+=4);let f=(s&2)==2;if(o===_.ERROR_RESPONSE){let t=e.readUInt32BE(u);u+=4;let n=e.readUInt32BE(u);u+=4;let r=e.slice(u,u+n).toString(`utf-8`);return{type:`error`,sequence:d,isLast:!0,data:{code:t,message:r}}}if(o===_.FULL_SERVER_RESPONSE){let t=e.readUInt32BE(u);if(u+=4,t===0)return{type:`result`,sequence:d,isLast:f,data:{}};let n=e.slice(u,u+t);if(l===b.GZIP)try{n=m.gunzipSync(n)}catch(e){throw e}let r;if(c===y.JSON){let e=n.toString(`utf-8`);try{r=JSON.parse(e)}catch(e){throw e}}else r={};return{type:`result`,sequence:d,isLast:f,data:r}}throw Error(`Unknown message type: ${o}`)}var T=class{config;ws=null;callbacks={};connectId=``;isConnected=!1;sessionConfig={};constructor(e){this.config=e}getWsUrl(){return this.config.useAsyncMode===!1?`wss://openspeech.bytedance.com/api/v3/sauc/bigmodel`:`wss://openspeech.bytedance.com/api/v3/sauc/bigmodel_async`}getResourceId(){return this.config.resourceId||`volc.bigasr.sauc.duration`}connect(e,t={}){return new Promise((n,r)=>{this.callbacks=e,this.sessionConfig=t,this.connectId=g(),this.ws=new h(this.getWsUrl(),{headers:{"X-Api-App-Key":this.config.appId,"X-Api-Access-Key":this.config.accessKey,"X-Api-Resource-Id":this.getResourceId(),"X-Api-Connect-Id":this.connectId}}),this.ws.on(`open`,()=>{this.isConnected=!0,this.sendFullClientRequest(),this.callbacks.onConnected?.(),n()}),this.ws.on(`message`,e=>{try{let t=w(e);if(t.type===`error`){let e=t.data;this.callbacks.onError?.(Error(`ASR Error ${e.code}: ${e.message}`))}else{let e=t.data;this.callbacks.onResult?.(e,t.isLast),t.isLast}}catch(e){this.callbacks.onError?.(e instanceof Error?e:Error(String(e)))}}),this.ws.on(`error`,e=>{this.callbacks.onError?.(e),r(e)}),this.ws.on(`close`,()=>{this.isConnected=!1,this.ws=null,this.callbacks.onClose?.()})})}sendFullClientRequest(){if(!this.ws||this.ws.readyState!==h.OPEN)return;let e=S({user:{uid:`ai-chat-user`},audio:{format:this.sessionConfig.format||`pcm`,rate:this.sessionConfig.sampleRate||16e3,bits:16,channel:1},request:{model_name:`bigmodel`,enable_itn:this.sessionConfig.enableItn??!0,enable_punc:this.sessionConfig.enablePunc??!0,enable_ddc:this.sessionConfig.enableDdc??!1,show_utterances:this.sessionConfig.showUtterances??!0,result_type:`full`}});this.ws.send(e)}sendAudio(e){if(!this.ws||this.ws.readyState!==h.OPEN)return;let t=C(e,!1);this.ws.send(t)}finish(){if(!this.ws||this.ws.readyState!==h.OPEN)return;let e=C(Buffer.alloc(0),!0);this.ws.send(e)}disconnect(){this.ws&&=(this.ws.close(),null),this.isConnected=!1}get connected(){return this.isConnected&&this.ws?.readyState===h.OPEN}};function E(t){let{channelPrefix:n=`ai-chat`,appId:r,accessKey:i,resourceId:a}=t,o=new Map;function s(e,t){let n=o.get(e);return n||(n={client:new T({appId:r,accessKey:i,resourceId:a,useAsyncMode:!0}),webContents:t},o.set(e,n),t.on(`destroyed`,()=>{let t=o.get(e);t&&(t.client.disconnect(),o.delete(e))})),n}return e.handle(`${n}:asr:start`,async(e,t)=>{let r=e.sender,i=r.id,a=s(i,r);a.client.connected&&a.client.disconnect();try{return await a.client.connect({onConnected:()=>{r.isDestroyed()||r.send(`${n}:asr:connected`)},onResult:(e,t)=>{r.isDestroyed()||r.send(`${n}:asr:result`,{result:e,isLast:t})},onError:e=>{r.isDestroyed()||r.send(`${n}:asr:error`,{message:e.message})},onClose:()=>{r.isDestroyed()||r.send(`${n}:asr:closed`)}},t),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}),e.handle(`${n}:asr:sendAudio`,async(e,t)=>{let n=e.sender.id,r=o.get(n);if(!r||!r.client.connected)return{success:!1,error:`ASR 会话未启动`};try{let e=t instanceof ArrayBuffer?Buffer.from(t):Buffer.from(new Uint8Array(t));return r.client.sendAudio(e),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}),e.handle(`${n}:asr:finish`,async e=>{let t=e.sender.id,n=o.get(t);if(!n||!n.client.connected)return{success:!1,error:`ASR 会话未启动`};try{return n.client.finish(),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}),e.handle(`${n}:asr:stop`,async e=>{let t=e.sender.id,n=o.get(t);return n&&n.client.disconnect(),{success:!0}}),e.handle(`${n}:asr:status`,async e=>{let t=e.sender.id;return{connected:o.get(t)?.client.connected??!1}}),e.handle(`${n}:asr:warmup`,async(e,t)=>{let n=e.sender,r=n.id,i=s(r,n);if(i.client.connected)return{success:!0};try{return await i.client.connect({onConnected:()=>{},onResult:()=>{},onError:()=>{},onClose:()=>{}},t),i.client.disconnect(),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}),{cleanup:()=>{for(let e of o.values())e.client.disconnect();o.clear()}}}async function D(e){let{dataDir:t,assetsDir:n,metadataStore:r,llm:i,systemPrompt:a,maxIterations:o,maxDurationMs:s,maxToolCalls:c,maxTotalTokens:l,tools:u,preloadTools:f,asr:p,channelPrefix:m,cwd:h,defaultContext:g,sqliteFactory:_,beforeChat:v,dataEngine:y}=e,b=`${t}/data`,x=await d({tools:u,assetsDir:n??`${t}/assets`,metadataStore:r,preloadTools:f}),{agent:S,storage:C}=await N({llmConfig:i,systemPrompt:a,maxIterations:o,maxDurationMs:s,maxToolCalls:c,maxTotalTokens:l,dataDir:b,tools:x.tools,cwd:h,channelPrefix:m,defaultContext:g,sqliteFactory:_,beforeChat:async e=>{let t=x.getEnabledSkillContents(),n=t.length>0?{...e,skillContents:[...t,...e.skillContents??[]]}:e;return v?v(n):n},dataEngine:y});return await x.bind(S),p&&E({...p,channelPrefix:m}),{agent:S,storage:C,assetManager:x}}function O(e,t){return e===`INVALID_PARAMS`?`validation`:e===`PERMISSION_DENIED`||e===`TOOL_NOT_ENABLED`?`permission`:e===`NETWORK_ERROR`?`network`:e===`NOT_FOUND`?`not_found`:e===`TIMEOUT`?`runtime`:t}function k(e){let{message:t,failureReason:n,code:r,category:i,retryable:a,suggestion:o,details:s,cause:c,stack:l}=e,u={...s??{},...c?{cause:c}:{},...l?{stack:l}:{}};return{status:`error`,failureReason:n,error:{message:t,...r?{code:r}:{},...i?{category:i}:{},...a===void 0?{}:{retryable:a},...o?{suggestion:o}:{},...Object.keys(u).length>0?{details:u}:{}}}}function A(e){if(typeof e!=`number`||!Number.isFinite(e))return;let t=Math.trunc(e);return t>0?t:void 0}function j(e){if(e)try{let t=JSON.parse(e);if(!t||typeof t!=`object`)return;let n={maxIterations:A(t.maxIterations),maxDurationMs:A(t.maxDurationMs),maxToolCalls:A(t.maxToolCalls),maxTotalTokens:A(t.maxTotalTokens)};return Object.values(n).some(e=>e!==void 0)?n:void 0}catch{return}}function M(e){return e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:e}async function N(d){let{llmConfig:m,systemPrompt:h,cwd:g=process.cwd(),maxIterations:_,maxDurationMs:v,maxToolCalls:y,maxTotalTokens:b,channelPrefix:x=`ai-chat`,dataDir:S,defaultContext:C={},sqliteFactory:w,tools:T,mcpServers:E,dataEngine:D,resolveAtFileContext:A}=d,N=r.join(S,`db.sqlite`),P=new Map,F=async e=>{let t=global.currentApprovalContext,n=t?.auditContext;d.audit?.recordToolApprovalRequest?.({id:e.id,name:e.name,toolName:e.toolName??null,extensionId:e.extensionId??null,displayName:e.displayName??null,args:e.args,context:n});let r=t?.webContents;return r?new Promise((t,i)=>{P.set(e.id,{resolve:t,reject:i,webContents:r,name:e.name,toolName:e.toolName,extensionId:e.extensionId,displayName:e.displayName,auditContext:n}),r.send(`${x}:toolApprovalRequest`,{id:e.id,name:e.name,args:e.args})}):(d.audit?.recordToolApprovalResponse?.({id:e.id,name:e.name,toolName:e.toolName??null,extensionId:e.extensionId??null,displayName:e.displayName??null,approved:!1,context:n}),!1)},I=await p({type:`sqlite`,sqlitePath:N,sqliteFactory:w}),L=()=>C,R=async()=>{try{let e=await I.getUserSetting(`autoRunConfig`,L());if(e)return JSON.parse(e)}catch{}},z=async()=>{try{return j(await I.getUserSetting(`agentExecutionBudget`,L()))}catch{return}},B=new i({llmConfig:m,systemPrompt:h,cwd:g,maxIterations:_,maxDurationMs:v,maxToolCalls:y,maxTotalTokens:b,tools:T,mcpServers:E,onToolApprovalRequest:F,getAutoRunConfig:R,dataEngine:D,audit:d.audit},f(g));e.on(`${x}:debugLog`,(e,t)=>{Array.isArray(t?.args)&&t.args.map(M),`${t?.module||`unknown`}`,t?.level}),e.handle(`${x}:models`,()=>a(m)),e.handle(`${x}:getAllTools`,async()=>(await B.ensureInitialized(),B.getAllTools())),e.handle(`${x}:getToolDefinitions`,async()=>(await B.ensureInitialized(),Array.from(B.getToolRuntimeManager().getSessionTools().values()).map(e=>({name:e.name,description:e.description,parameters:{type:`object`,properties:e.parameters?.properties||{},required:e.parameters?.required||[]}})))),e.handle(`${x}:executeTool`,async(e,t)=>{let{name:n,args:r,toolCallId:i,sessionId:a}=t;await B.ensureInitialized();try{let e=()=>B.executeTool(n,r,void 0,{toolCallId:i??`client-tool-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`,toolName:n}),t=d.audit?await d.audit.runWithContext({conversationId:a??null},e):await e(),o=B.getToolRuntimeManager().getSessionTools().get(n),f=await c(o,r),p,m;typeof t==`string`?m=t:t&&typeof t==`object`&&`result`in t?m=String(t.result??``):(p=s(t),m=JSON.stringify(u(p)));let h=p?l(p,o?.ui):o?.ui;return{success:!0,result:m,...h?{ui:h}:{},...f.approvalPolicy?{approvalPolicy:f.approvalPolicy}:{},...f.sideEffectLevel?{sideEffectLevel:f.sideEffectLevel}:{},...f.hostDependency?{hostDependency:f.hostDependency}:{},...f.riskSummary?{riskSummary:f.riskSummary}:{},...f.riskTags?.length?{riskTags:f.riskTags}:{},...f.riskSignals?.length?{riskSignals:f.riskSignals}:{}}}catch(e){let t=e instanceof Error?e.message:String(e),n=o(e)?e.toolError:void 0,r=n?.code===`TIMEOUT`?`timeout`:`execution_error`,i={success:!1,result:JSON.stringify(k({message:n?.message??t,failureReason:r,code:n?.code,category:n?.category??O(n?.code,`runtime`),retryable:n?.retryable,suggestion:n?.suggestion,details:n?.details,cause:e instanceof Error&&e.cause?String(e.cause):void 0,stack:e instanceof Error?e.stack:void 0})),error:n?.message??t};return n?{...i,toolError:n}:i}}),e.handle(`${x}:send`,async(e,t)=>{let n=e.sender,{message:r,images:i,sessionId:a}=t,o=t.options||{};d.beforeChat&&(o=await d.beforeChat(o)||o);let s=await z();s&&(o={...s,...o});let c=d.audit?.startTurn({conversationId:a??null,model:o.model??null,mode:o.mode??null,userMessage:r,metadata:{imageCount:i?.length??0}});global.currentApprovalContext={webContents:n,auditContext:{conversationId:a??null,turnId:c??null}};let l=0,u=async()=>{for await(let e of B.chat(r,o,i))l+=1,n.isDestroyed()||n.send(`${x}:progress`,{...e,sessionId:a})};try{d.audit?await d.audit.runWithContext({conversationId:a??null,turnId:c??null},u):await u(),c&&d.audit?.finishTurn({turnId:c,eventCount:l})}catch(e){if(c&&d.audit?.failTurn({turnId:c,eventCount:l,error:e}),e instanceof Error,!n.isDestroyed()){let t=e instanceof Error?{category:`api`,message:e.message||String(e),cause:e.stack}:{category:`api`,message:String(e)};n.send(`${x}:progress`,{type:`error`,data:t,sessionId:a})}}finally{delete global.currentApprovalContext}}),e.handle(`${x}:toolApprovalResponse`,(e,t)=>{let{id:n,approved:r}=t,i=P.get(n);i&&(P.delete(n),d.audit?.recordToolApprovalResponse?.({id:n,name:i.name,toolName:i.toolName??null,extensionId:i.extensionId??null,displayName:i.displayName??null,approved:r,context:i.auditContext}),i.resolve(r))}),e.handle(`${x}:cancel`,()=>{B.abort()}),e.handle(`${x}:settings:get`,async(e,t)=>I.getUserSetting(t,L())),e.handle(`${x}:settings:set`,async(e,t,n)=>(await I.setUserSetting(t,n,L()),{success:!0})),e.handle(`${x}:settings:getAll`,async()=>I.getUserSettings(L())),e.handle(`${x}:settings:delete`,async(e,t)=>(await I.deleteUserSetting(t,L()),{success:!0})),e.handle(`${x}:setCwd`,(e,t)=>{B.setCwd(t)}),e.handle(`${x}:config`,async()=>{let[e,t]=await Promise.all([R(),z()]);return{...B.getConfig(),currentAutoRunConfig:e,currentExecutionBudget:t}}),e.handle(`${x}:sessions:list`,async()=>I.getSessions(L())),e.handle(`${x}:sessions:get`,async(e,t)=>I.getSession(t,L())),e.handle(`${x}:sessions:create`,async(e,t)=>{let n={id:t.id||crypto.randomUUID(),title:t.title||`新对话`,model:t.model||m.defaultModel,mode:t.mode||`agent`,webSearchEnabled:t.webSearchEnabled??!0,thinkingEnabled:t.thinkingEnabled??!0,hidden:t.hidden??!1};return I.createSession(n,L())}),e.handle(`${x}:sessions:update`,async(e,t,n)=>(await I.updateSession(t,n,L()),I.getSession(t,L()))),e.handle(`${x}:sessions:delete`,async(e,t)=>(await I.deleteSession(t,L()),{success:!0})),e.handle(`${x}:messages:list`,async(e,t)=>I.getMessages(t,L())),e.handle(`${x}:messages:listPage`,async(e,t,n)=>I.getMessagesPage(t,n,L())),e.handle(`${x}:at:chats:search`,async(e,t,n)=>I.searchAtChats(t,n,L())),e.handle(`${x}:messages:save`,async(e,t)=>{let n=t.id||crypto.randomUUID(),r={id:n,clientId:n,sessionId:t.sessionId,role:t.role,content:t.content,atContextItems:t.atContextItems||null,images:t.images||[],model:t.model||null,modelDisplayName:t.modelDisplayName||null,mode:t.mode||null,webSearchEnabled:t.webSearchEnabled??null,thinkingEnabled:t.thinkingEnabled??null,parts:t.parts||[],operationIds:t.operationIds||null,finishReason:t.finishReason??null};return I.saveMessage(r,L())}),e.handle(`${x}:messages:update`,async(e,t)=>(await I.updateMessage(t.id,{content:t.content,atContextItems:t.atContextItems,parts:t.parts,usage:t.usage,duration:t.duration,finishReason:t.finishReason},L()),{success:!0})),e.handle(`${x}:messages:deleteAfter`,async(e,t,n)=>(await I.deleteMessagesAfter(t,new Date(n),L()),{success:!0})),e.handle(`${x}:messages:deleteAfterMessageId`,async(e,t,n)=>(await I.deleteMessagesAfterMessageId(t,n,L()),{success:!0})),e.handle(`${x}:operations:list`,async(e,t)=>I.getOperations(t,L())),e.handle(`${x}:trash:list`,async()=>I.getTrashItems?.(L())||[]),e.handle(`${x}:trash:restore`,async(e,t)=>I.restoreFromTrash?.(t,L())),e.handle(`${x}:openExternal`,async(e,n)=>t.openExternal(n)),e.handle(`${x}:fs:listDir`,async(e,t)=>{try{let e=n.readdirSync(t,{withFileTypes:!0}),i=[];for(let a of e){let e=r.join(t,a.name);try{let t=n.statSync(e);i.push({name:a.name,path:e,isDirectory:a.isDirectory(),size:t.size,modifiedAt:t.mtime,extension:a.isDirectory()?``:r.extname(a.name).toLowerCase()})}catch{}}return i.sort((e,t)=>e.isDirectory&&!t.isDirectory?-1:!e.isDirectory&&t.isDirectory?1:e.name.localeCompare(t.name))}catch{return[]}}),e.handle(`${x}:fs:exists`,async(e,t)=>n.existsSync(t)),e.handle(`${x}:fs:stat`,async(e,t)=>{try{let e=n.statSync(t);return{name:r.basename(t),path:t,isDirectory:e.isDirectory(),size:e.size,modifiedAt:e.mtime,extension:e.isDirectory()?``:r.extname(t).toLowerCase()}}catch{return null}}),e.handle(`${x}:fs:readFile`,async(e,t)=>{try{return n.readFileSync(t,`utf-8`)}catch{return null}}),e.handle(`${x}:fs:resolveAtFileContext`,async(e,t)=>{if(!A)return null;try{return await A(t)}catch{return null}}),e.handle(`${x}:fs:readFileBase64`,async(e,t)=>{try{return n.readFileSync(t).toString(`base64`)}catch{return null}}),e.handle(`${x}:fs:homeDir`,async()=>process.env.HOME||process.env.USERPROFILE||`/`),e.handle(`${x}:fs:resolvePath`,async(e,t)=>{if(t.startsWith(`~`)){let e=process.env.HOME||process.env.USERPROFILE||`/`;return r.join(e,t.slice(1))}return r.resolve(t)}),e.handle(`${x}:fs:parentDir`,async(e,t)=>r.dirname(t));let V=new Map;return e.handle(`${x}:fs:watchDir`,async(e,t)=>{let r=e.sender;V.has(t)&&(V.get(t)?.close(),V.delete(t));try{let e=n.watch(t,{persistent:!1},(e,n)=>{r.isDestroyed()||r.send(`${x}:fs:dirChange`,{dirPath:t,eventType:e,filename:n})});return e.on(`error`,e=>{V.delete(t)}),V.set(t,e),!0}catch{return!1}}),e.handle(`${x}:fs:unwatchDir`,async(e,t)=>{let n=V.get(t);n&&(n.close(),V.delete(t))}),{agent:B,storage:I}}export{a as buildModelOptions,E as createAsrBridge,N as createElectronBridge,D as createElectronChat};
|
|
1
|
+
import{ipcMain as e,shell as t}from"electron";import*as n from"fs";import*as r from"path";import{ChatRuntime as i,buildModelOptions as a,isToolError as o,normalizeToolResult as s,resolveToolGovernanceSnapshot as c,resolveToolResultUi as l,serializeToolResult as u}from"@huyooo/ai-chat-core";import{assetManager as d,createDefaultToolExecutor as f}from"@huyooo/ai-chat-host-node";import{createStorage as p}from"@huyooo/ai-chat-storage";import*as m from"zlib";import h from"ws";import{v4 as g}from"uuid";const _={FULL_CLIENT_REQUEST:1,AUDIO_ONLY_REQUEST:2,FULL_SERVER_RESPONSE:9,ERROR_RESPONSE:15},v={NONE:0,HAS_SEQUENCE:1,LAST_PACKET:2,LAST_PACKET_WITH_SEQUENCE:3},y={NONE:0,JSON:1},b={NONE:0,GZIP:1};function x(e,t,n,r){return[17,e<<4|t,n<<4|r,0]}function S(e,t=!0){let n=x(_.FULL_CLIENT_REQUEST,v.NONE,y.JSON,t?b.GZIP:b.NONE),r=JSON.stringify(e),i=Buffer.from(r,`utf-8`),a=t?m.gzipSync(i):i,o=Buffer.alloc(4);return o.writeUInt32BE(a.length,0),Buffer.concat([Buffer.from(n),o,a])}function C(e,t=!1,n=!0){let r=x(_.AUDIO_ONLY_REQUEST,t?v.LAST_PACKET:v.NONE,y.NONE,n?b.GZIP:b.NONE),i=n?m.gzipSync(e):e,a=Buffer.alloc(4);return a.writeUInt32BE(i.length,0),Buffer.concat([Buffer.from(r),a,i])}function w(e){if(e.length<4)throw Error(`Invalid response: too short`);let t=e[0],n=e[1],r=e[2],i=t>>4&15,a=(t&15)*4,o=n>>4&15,s=n&15,c=r>>4&15,l=r&15;if(i!==1)throw Error(`Unsupported protocol version: ${i}`);let u=a,d;(s&1)==1&&e.length>=u+4&&(d=e.readUInt32BE(u),u+=4);let f=(s&2)==2;if(o===_.ERROR_RESPONSE){let t=e.readUInt32BE(u);u+=4;let n=e.readUInt32BE(u);u+=4;let r=e.slice(u,u+n).toString(`utf-8`);return{type:`error`,sequence:d,isLast:!0,data:{code:t,message:r}}}if(o===_.FULL_SERVER_RESPONSE){let t=e.readUInt32BE(u);if(u+=4,t===0)return{type:`result`,sequence:d,isLast:f,data:{}};let n=e.slice(u,u+t);if(l===b.GZIP)try{n=m.gunzipSync(n)}catch(e){throw e}let r;if(c===y.JSON){let e=n.toString(`utf-8`);try{r=JSON.parse(e)}catch(e){throw e}}else r={};return{type:`result`,sequence:d,isLast:f,data:r}}throw Error(`Unknown message type: ${o}`)}var T=class{config;ws=null;callbacks={};connectId=``;isConnected=!1;sessionConfig={};constructor(e){this.config=e}connect(e,t={}){return new Promise((n,r)=>{this.callbacks=e,this.sessionConfig=t,this.connectId=g();let i={Authorization:this.config.auth.authorization.startsWith(`Bearer `)?this.config.auth.authorization:`Bearer ${this.config.auth.authorization}`};this.config.auth.appId&&(i[`app-id`]=this.config.auth.appId),this.ws=new h(this.config.auth.wsUrl,{headers:i}),this.ws.on(`open`,()=>{this.isConnected=!0,this.sendFullClientRequest(),this.callbacks.onConnected?.(),n()}),this.ws.on(`message`,e=>{try{let t=w(e);if(t.type===`error`){let e=t.data;this.callbacks.onError?.(Error(`ASR Error ${e.code}: ${e.message}`))}else{let e=t.data;this.callbacks.onResult?.(e,t.isLast),t.isLast}}catch(e){this.callbacks.onError?.(e instanceof Error?e:Error(String(e)))}}),this.ws.on(`error`,e=>{this.callbacks.onError?.(e),r(e)}),this.ws.on(`close`,()=>{this.isConnected=!1,this.ws=null,this.callbacks.onClose?.()})})}sendFullClientRequest(){if(!this.ws||this.ws.readyState!==h.OPEN)return;let e={user:{uid:`ai-chat-user`},audio:{format:this.sessionConfig.format||`pcm`,rate:this.sessionConfig.sampleRate||16e3,bits:16,channel:1},request:{model_name:`bigmodel`,enable_itn:this.sessionConfig.enableItn??!0,enable_punc:this.sessionConfig.enablePunc??!0,enable_ddc:this.sessionConfig.enableDdc??!1,show_utterances:this.sessionConfig.showUtterances??!0,result_type:`full`}};this.ws.send(S(e))}sendAudio(e){!this.ws||this.ws.readyState!==h.OPEN||this.ws.send(C(e,!1))}finish(){!this.ws||this.ws.readyState!==h.OPEN||this.ws.send(C(Buffer.alloc(0),!0))}disconnect(){this.ws&&=(this.ws.close(),null),this.isConnected=!1}get connected(){return this.isConnected&&this.ws?.readyState===h.OPEN}};function E(t){let{channelPrefix:n=`ai-chat`,resolvePlatformAuth:r,onAfterFinish:i}=t,a=new Map;function o(e){if(e.lastResultDurationMs>0)return e.lastResultDurationMs;let t=Math.max(e.sampleRate,1)*2;return Math.floor(e.audioBytesSent/t*1e3)}async function s(e){if(!(!e||!i))try{await i({billingSessionId:e.billingSessionId,durationMs:o(e)})}catch{}finally{e.billingSessionId=void 0,e.audioBytesSent=0,e.lastResultDurationMs=0}}function c(e,t){let n=a.get(e);return n||(n={client:new T({auth:{wsUrl:`wss://invalid.local`,authorization:`Bearer pending`}}),webContents:t,audioBytesSent:0,lastResultDurationMs:0,sampleRate:16e3},a.set(e,n),t.on(`destroyed`,()=>{let t=a.get(e);t&&(s(t),t.client.disconnect(),a.delete(e))})),n}return e.handle(`${n}:asr:start`,async(e,t)=>{let i=e.sender,a=i.id,o=c(a,i);o.client.connected&&(await s(o),o.client.disconnect()),o.audioBytesSent=0,o.lastResultDurationMs=0,o.sampleRate=t?.sampleRate??16e3,o.billingSessionId=void 0;let l;try{l=await r(),o.billingSessionId=l.billingSessionId}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}o.client=new T({auth:{wsUrl:l.wsUrl,authorization:l.authorization,appId:l.appId}});try{return await o.client.connect({onConnected:()=>{i.isDestroyed()||i.send(`${n}:asr:connected`)},onResult:(e,t)=>{let r=e.audio_info?.duration;typeof r==`number`&&r>0&&(o.lastResultDurationMs=Math.max(o.lastResultDurationMs,r)),i.isDestroyed()||i.send(`${n}:asr:result`,{result:e,isLast:t})},onError:e=>{i.isDestroyed()||i.send(`${n}:asr:error`,{message:e.message})},onClose:()=>{i.isDestroyed()||i.send(`${n}:asr:closed`)}},t),{success:!0}}catch(e){return await s(o),{success:!1,error:e instanceof Error?e.message:String(e)}}}),e.handle(`${n}:asr:sendAudio`,async(e,t)=>{let n=e.sender.id,r=a.get(n);if(!r||!r.client.connected)return{success:!1,error:`ASR 会话未启动`};try{let e=t instanceof ArrayBuffer?Buffer.from(t):Buffer.from(new Uint8Array(t));return r.client.sendAudio(e),r.audioBytesSent+=e.byteLength,{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}),e.handle(`${n}:asr:finish`,async e=>{let t=e.sender.id,n=a.get(t);if(!n||!n.client.connected)return{success:!1,error:`ASR 会话未启动`};try{return n.client.finish(),await s(n),{success:!0}}catch(e){return await s(n),{success:!1,error:e instanceof Error?e.message:String(e)}}}),e.handle(`${n}:asr:stop`,async e=>{let t=e.sender.id,n=a.get(t);return n&&(await s(n),n.client.disconnect()),{success:!0}}),e.handle(`${n}:asr:status`,async e=>{let t=e.sender.id;return{connected:a.get(t)?.client.connected??!1}}),e.handle(`${n}:asr:warmup`,async()=>({success:!0})),{cleanup:()=>{for(let e of a.values())s(e),e.client.disconnect();a.clear()}}}async function D(e){let{dataDir:t,assetsDir:n,metadataStore:r,llm:i,systemPrompt:a,maxIterations:o,maxDurationMs:s,maxToolCalls:c,maxTotalTokens:l,tools:u,preloadTools:f,asr:p,channelPrefix:m,cwd:h,defaultContext:g,sqliteFactory:_,beforeChat:v,dataEngine:y}=e,b=`${t}/data`,x=await d({tools:u,assetsDir:n??`${t}/assets`,metadataStore:r,preloadTools:f}),{agent:S,storage:C}=await N({llmConfig:i,systemPrompt:a,maxIterations:o,maxDurationMs:s,maxToolCalls:c,maxTotalTokens:l,dataDir:b,tools:x.tools,cwd:h,channelPrefix:m,defaultContext:g,sqliteFactory:_,beforeChat:async e=>{let t=x.getEnabledSkillContents(),n=t.length>0?{...e,skillContents:[...t,...e.skillContents??[]]}:e;return v?v(n):n},dataEngine:y});return await x.bind(S),p&&E({...p,channelPrefix:m}),{agent:S,storage:C,assetManager:x}}function O(e,t){return e===`INVALID_PARAMS`?`validation`:e===`PERMISSION_DENIED`||e===`TOOL_NOT_ENABLED`?`permission`:e===`NETWORK_ERROR`?`network`:e===`NOT_FOUND`?`not_found`:e===`TIMEOUT`?`runtime`:t}function k(e){let{message:t,failureReason:n,code:r,category:i,retryable:a,suggestion:o,details:s,cause:c,stack:l}=e,u={...s??{},...c?{cause:c}:{},...l?{stack:l}:{}};return{status:`error`,failureReason:n,error:{message:t,...r?{code:r}:{},...i?{category:i}:{},...a===void 0?{}:{retryable:a},...o?{suggestion:o}:{},...Object.keys(u).length>0?{details:u}:{}}}}function A(e){if(typeof e!=`number`||!Number.isFinite(e))return;let t=Math.trunc(e);return t>0?t:void 0}function j(e){if(e)try{let t=JSON.parse(e);if(!t||typeof t!=`object`)return;let n={maxIterations:A(t.maxIterations),maxDurationMs:A(t.maxDurationMs),maxToolCalls:A(t.maxToolCalls),maxTotalTokens:A(t.maxTotalTokens)};return Object.values(n).some(e=>e!==void 0)?n:void 0}catch{return}}function M(e){return e instanceof Error?{name:e.name,message:e.message,stack:e.stack}:e}async function N(d){let{llmConfig:m,systemPrompt:h,cwd:g=process.cwd(),maxIterations:_,maxDurationMs:v,maxToolCalls:y,maxTotalTokens:b,channelPrefix:x=`ai-chat`,dataDir:S,defaultContext:C={},sqliteFactory:w,tools:T,mcpServers:E,dataEngine:D,resolveAtFileContext:A}=d,N=r.join(S,`db.sqlite`),P=new Map,F=async e=>{let t=global.currentApprovalContext,n=t?.auditContext;d.audit?.recordToolApprovalRequest?.({id:e.id,name:e.name,toolName:e.toolName??null,extensionId:e.extensionId??null,displayName:e.displayName??null,args:e.args,context:n});let r=t?.webContents;return r?new Promise((t,i)=>{P.set(e.id,{resolve:t,reject:i,webContents:r,name:e.name,toolName:e.toolName,extensionId:e.extensionId,displayName:e.displayName,auditContext:n}),r.send(`${x}:toolApprovalRequest`,{id:e.id,name:e.name,args:e.args})}):(d.audit?.recordToolApprovalResponse?.({id:e.id,name:e.name,toolName:e.toolName??null,extensionId:e.extensionId??null,displayName:e.displayName??null,approved:!1,context:n}),!1)},I=await p({type:`sqlite`,sqlitePath:N,sqliteFactory:w}),L=()=>C,R=async()=>{try{let e=await I.getUserSetting(`autoRunConfig`,L());if(e)return JSON.parse(e)}catch{}},z=async()=>{try{return j(await I.getUserSetting(`agentExecutionBudget`,L()))}catch{return}},B=new i({llmConfig:m,systemPrompt:h,cwd:g,maxIterations:_,maxDurationMs:v,maxToolCalls:y,maxTotalTokens:b,tools:T,mcpServers:E,onToolApprovalRequest:F,getAutoRunConfig:R,dataEngine:D,audit:d.audit},f(g));e.on(`${x}:debugLog`,(e,t)=>{Array.isArray(t?.args)&&t.args.map(M),`${t?.module||`unknown`}`,t?.level}),e.handle(`${x}:models`,()=>a(m)),e.handle(`${x}:getAllTools`,async()=>(await B.ensureInitialized(),B.getAllTools())),e.handle(`${x}:getToolDefinitions`,async()=>(await B.ensureInitialized(),Array.from(B.getToolRuntimeManager().getSessionTools().values()).map(e=>({name:e.name,description:e.description,parameters:{type:`object`,properties:e.parameters?.properties||{},required:e.parameters?.required||[]}})))),e.handle(`${x}:executeTool`,async(e,t)=>{let{name:n,args:r,toolCallId:i,sessionId:a}=t;await B.ensureInitialized();try{let e=()=>B.executeTool(n,r,void 0,{toolCallId:i??`client-tool-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`,toolName:n}),t=d.audit?await d.audit.runWithContext({conversationId:a??null},e):await e(),o=B.getToolRuntimeManager().getSessionTools().get(n),f=await c(o,r),p,m;typeof t==`string`?m=t:t&&typeof t==`object`&&`result`in t?m=String(t.result??``):(p=s(t),m=JSON.stringify(u(p)));let h=p?l(p,o?.ui):o?.ui;return{success:!0,result:m,...h?{ui:h}:{},...f.approvalPolicy?{approvalPolicy:f.approvalPolicy}:{},...f.sideEffectLevel?{sideEffectLevel:f.sideEffectLevel}:{},...f.hostDependency?{hostDependency:f.hostDependency}:{},...f.riskSummary?{riskSummary:f.riskSummary}:{},...f.riskTags?.length?{riskTags:f.riskTags}:{},...f.riskSignals?.length?{riskSignals:f.riskSignals}:{}}}catch(e){let t=e instanceof Error?e.message:String(e),n=o(e)?e.toolError:void 0,r=n?.code===`TIMEOUT`?`timeout`:`execution_error`,i={success:!1,result:JSON.stringify(k({message:n?.message??t,failureReason:r,code:n?.code,category:n?.category??O(n?.code,`runtime`),retryable:n?.retryable,suggestion:n?.suggestion,details:n?.details,cause:e instanceof Error&&e.cause?String(e.cause):void 0,stack:e instanceof Error?e.stack:void 0})),error:n?.message??t};return n?{...i,toolError:n}:i}}),e.handle(`${x}:send`,async(e,t)=>{let n=e.sender,{message:r,images:i,sessionId:a}=t,o=t.options||{};d.beforeChat&&(o=await d.beforeChat(o)||o);let s=await z();s&&(o={...s,...o});let c=d.audit?.startTurn({conversationId:a??null,model:o.model??null,mode:o.mode??null,userMessage:r,metadata:{imageCount:i?.length??0}});global.currentApprovalContext={webContents:n,auditContext:{conversationId:a??null,turnId:c??null}};let l=0,u=async()=>{for await(let e of B.chat(r,o,i))l+=1,n.isDestroyed()||n.send(`${x}:progress`,{...e,sessionId:a})};try{d.audit?await d.audit.runWithContext({conversationId:a??null,turnId:c??null},u):await u(),c&&d.audit?.finishTurn({turnId:c,eventCount:l})}catch(e){if(c&&d.audit?.failTurn({turnId:c,eventCount:l,error:e}),e instanceof Error,!n.isDestroyed()){let t=e instanceof Error?{category:`api`,message:e.message||String(e),cause:e.stack}:{category:`api`,message:String(e)};n.send(`${x}:progress`,{type:`error`,data:t,sessionId:a})}}finally{delete global.currentApprovalContext}}),e.handle(`${x}:toolApprovalResponse`,(e,t)=>{let{id:n,approved:r}=t,i=P.get(n);i&&(P.delete(n),d.audit?.recordToolApprovalResponse?.({id:n,name:i.name,toolName:i.toolName??null,extensionId:i.extensionId??null,displayName:i.displayName??null,approved:r,context:i.auditContext}),i.resolve(r))}),e.handle(`${x}:cancel`,()=>{B.abort()}),e.handle(`${x}:settings:get`,async(e,t)=>I.getUserSetting(t,L())),e.handle(`${x}:settings:set`,async(e,t,n)=>(await I.setUserSetting(t,n,L()),{success:!0})),e.handle(`${x}:settings:getAll`,async()=>I.getUserSettings(L())),e.handle(`${x}:settings:delete`,async(e,t)=>(await I.deleteUserSetting(t,L()),{success:!0})),e.handle(`${x}:setCwd`,(e,t)=>{B.setCwd(t)}),e.handle(`${x}:config`,async()=>{let[e,t]=await Promise.all([R(),z()]);return{...B.getConfig(),currentAutoRunConfig:e,currentExecutionBudget:t}}),e.handle(`${x}:sessions:list`,async()=>I.getSessions(L())),e.handle(`${x}:sessions:get`,async(e,t)=>I.getSession(t,L())),e.handle(`${x}:sessions:create`,async(e,t)=>{let n={id:t.id||crypto.randomUUID(),title:t.title||`新对话`,model:t.model||m.defaultModel,mode:t.mode||`agent`,webSearchEnabled:t.webSearchEnabled??!0,thinkingEnabled:t.thinkingEnabled??!0,hidden:t.hidden??!1};return I.createSession(n,L())}),e.handle(`${x}:sessions:update`,async(e,t,n)=>(await I.updateSession(t,n,L()),I.getSession(t,L()))),e.handle(`${x}:sessions:delete`,async(e,t)=>(await I.deleteSession(t,L()),{success:!0})),e.handle(`${x}:messages:list`,async(e,t)=>I.getMessages(t,L())),e.handle(`${x}:messages:listPage`,async(e,t,n)=>I.getMessagesPage(t,n,L())),e.handle(`${x}:at:chats:search`,async(e,t,n)=>I.searchAtChats(t,n,L())),e.handle(`${x}:messages:save`,async(e,t)=>{let n=t.id||crypto.randomUUID(),r={id:n,clientId:n,sessionId:t.sessionId,role:t.role,content:t.content,atContextItems:t.atContextItems||null,images:t.images||[],model:t.model||null,modelDisplayName:t.modelDisplayName||null,mode:t.mode||null,webSearchEnabled:t.webSearchEnabled??null,thinkingEnabled:t.thinkingEnabled??null,parts:t.parts||[],operationIds:t.operationIds||null,finishReason:t.finishReason??null};return I.saveMessage(r,L())}),e.handle(`${x}:messages:update`,async(e,t)=>(await I.updateMessage(t.id,{content:t.content,atContextItems:t.atContextItems,parts:t.parts,usage:t.usage,duration:t.duration,finishReason:t.finishReason},L()),{success:!0})),e.handle(`${x}:messages:deleteAfter`,async(e,t,n)=>(await I.deleteMessagesAfter(t,new Date(n),L()),{success:!0})),e.handle(`${x}:messages:deleteAfterMessageId`,async(e,t,n)=>(await I.deleteMessagesAfterMessageId(t,n,L()),{success:!0})),e.handle(`${x}:operations:list`,async(e,t)=>I.getOperations(t,L())),e.handle(`${x}:trash:list`,async()=>I.getTrashItems?.(L())||[]),e.handle(`${x}:trash:restore`,async(e,t)=>I.restoreFromTrash?.(t,L())),e.handle(`${x}:openExternal`,async(e,n)=>t.openExternal(n)),e.handle(`${x}:fs:listDir`,async(e,t)=>{try{let e=n.readdirSync(t,{withFileTypes:!0}),i=[];for(let a of e){let e=r.join(t,a.name);try{let t=n.statSync(e);i.push({name:a.name,path:e,isDirectory:a.isDirectory(),size:t.size,modifiedAt:t.mtime,extension:a.isDirectory()?``:r.extname(a.name).toLowerCase()})}catch{}}return i.sort((e,t)=>e.isDirectory&&!t.isDirectory?-1:!e.isDirectory&&t.isDirectory?1:e.name.localeCompare(t.name))}catch{return[]}}),e.handle(`${x}:fs:exists`,async(e,t)=>n.existsSync(t)),e.handle(`${x}:fs:stat`,async(e,t)=>{try{let e=n.statSync(t);return{name:r.basename(t),path:t,isDirectory:e.isDirectory(),size:e.size,modifiedAt:e.mtime,extension:e.isDirectory()?``:r.extname(t).toLowerCase()}}catch{return null}}),e.handle(`${x}:fs:readFile`,async(e,t)=>{try{return n.readFileSync(t,`utf-8`)}catch{return null}}),e.handle(`${x}:fs:resolveAtFileContext`,async(e,t)=>{if(!A)return null;try{return await A(t)}catch{return null}}),e.handle(`${x}:fs:readFileBase64`,async(e,t)=>{try{return n.readFileSync(t).toString(`base64`)}catch{return null}}),e.handle(`${x}:fs:homeDir`,async()=>process.env.HOME||process.env.USERPROFILE||`/`),e.handle(`${x}:fs:resolvePath`,async(e,t)=>{if(t.startsWith(`~`)){let e=process.env.HOME||process.env.USERPROFILE||`/`;return r.join(e,t.slice(1))}return r.resolve(t)}),e.handle(`${x}:fs:parentDir`,async(e,t)=>r.dirname(t));let V=new Map;return e.handle(`${x}:fs:watchDir`,async(e,t)=>{let r=e.sender;V.has(t)&&(V.get(t)?.close(),V.delete(t));try{let e=n.watch(t,{persistent:!1},(e,n)=>{r.isDestroyed()||r.send(`${x}:fs:dirChange`,{dirPath:t,eventType:e,filename:n})});return e.on(`error`,e=>{V.delete(t)}),V.set(t,e),!0}catch{return!1}}),e.handle(`${x}:fs:unwatchDir`,async(e,t)=>{let n=V.get(t);n&&(n.close(),V.delete(t))}),{agent:B,storage:I}}export{a as buildModelOptions,E as createAsrBridge,N as createElectronBridge,D as createElectronChat};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@huyooo/ai-chat-bridge-electron",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.31",
|
|
4
4
|
"description": "AI Chat Electron Bridge - IPC integration for Electron apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/main/index.js",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@huyooo/ai-chat-core": "^0.3.
|
|
36
|
-
"@huyooo/ai-chat-host-node": "^0.3.
|
|
37
|
-
"@huyooo/ai-chat-storage": "^0.3.
|
|
38
|
-
"@huyooo/ai-chat-tools-node": "^0.3.
|
|
39
|
-
"@huyooo/ai-chat-types": "^0.3.
|
|
35
|
+
"@huyooo/ai-chat-core": "^0.3.31",
|
|
36
|
+
"@huyooo/ai-chat-host-node": "^0.3.31",
|
|
37
|
+
"@huyooo/ai-chat-storage": "^0.3.31",
|
|
38
|
+
"@huyooo/ai-chat-tools-node": "^0.3.31",
|
|
39
|
+
"@huyooo/ai-chat-types": "^0.3.31",
|
|
40
40
|
"uuid": "^11.1.0",
|
|
41
41
|
"ws": "^8.18.3"
|
|
42
42
|
},
|