@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
- * 火山引擎大模型流式语音识别 - WebSocket 客户端
2
+ * 流式语音识别 WebSocket 客户端
3
3
  *
4
- * Electron 主进程中运行,处理与火山引擎 ASR 服务的通信
4
+ * 仅支持双向流式优化版 bigmodel_async。
5
+ * 连接 ai-server 平台 WS 网关(Bearer + ticket),不持有火山密钥。
5
6
  */
6
7
  import { type AsrResult } from './protocol';
7
- /** ASR 客户端配置 */
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
- /** 火山引擎 App ID */
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;
@@ -3,33 +3,26 @@
3
3
  */
4
4
  export * from './protocol';
5
5
  export * from './client';
6
- /** ASR 桥接配置 */
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
- /** 火山引擎 App ID */
11
- appId: string;
12
- /** 火山引擎 Access Key */
13
- accessKey: string;
14
- /** 资源 ID */
15
- resourceId?: string;
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: { appId: '...', accessKey: '...' },
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
- /** ASR 语音识别配置 */
27
- export interface AsrConfig {
28
- appId: string;
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
  *
@@ -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';
@@ -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.29",
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.29",
36
- "@huyooo/ai-chat-host-node": "^0.3.29",
37
- "@huyooo/ai-chat-storage": "^0.3.29",
38
- "@huyooo/ai-chat-tools-node": "^0.3.29",
39
- "@huyooo/ai-chat-types": "^0.3.29",
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
  },