grix-connector 3.8.3 → 3.9.1

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 +1 @@
1
- const c=25e3;class a{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;pauseReasons=new Set;get ready(){return this.pauseReasons.size===0}constructor(e,t){this.config=e,this.callbacks=t}pause(e){this.pauseReasons.add(e)}resume(e){this.pauseReasons.delete(e)&&this.ready&&this.drainNext()}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":this.ready&&this.running.size<this.config.maxConcurrent?(this.startRunning(e),"accepted"):this.config.maxQueued<=0||this.queued.length>=this.config.maxQueued?(this.callbacks.onRejected(e,"queue full"),"rejected"):(this.enqueue(e),"accepted")}cancel(e){const t=this.queued.findIndex(i=>i.event_id===e);if(t>=0){if(!this.config.cancelableQueued)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.callbacks.onStateChange(e,i.session_id,"canceled",{reason:"canceled by user"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id),!0}return this.running.has(e)&&this.config.cancelableRunning?(this.callbacks.onCancelRunning(e),!0):!1}removeQueued(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.broadcastQueuePositions(),this.checkStopComposing(i.session_id),!0}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e,t="queue cleared"){const i=[],n=[];for(const u of this.queued)u.session_id===e?(this.clearTimer(u.event_id),this.callbacks.onStateChange(u.event_id,e,"canceled",{reason:t}),i.push(u.event_id)):n.push(u);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),i}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),u=this.queued.flatMap((s,o)=>s.session_id===e?[{event_id:s.event_id,position:o+1,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)}]:[]);return{running:i,running_items:n,queued:u}}hasCapacity(){return this.running.size<this.config.maxConcurrent}get runningCount(){return this.running.size}get queuedCount(){return this.queued.length}destroy(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear();for(const e of this.composingTimers.values())clearInterval(e);this.composingTimers.clear(),this.queued=[],this.running.clear(),this.pauseReasons.clear()}enqueue(e){this.queued.push(e);const t=this.queued.length;if(this.callbacks.onStateChange(e.event_id,e.session_id,"queued",{queue_position:t,queue_total:t,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.config.queueTimeoutMs>0){const i=setTimeout(()=>{this.timeoutEvent(e.event_id)},this.config.queueTimeoutMs);i.unref(),this.timers.set(e.event_id,i)}this.ensureComposing(e.session_id)}timeoutEvent(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0;){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t];this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content)})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}stopComposing(e){const t=this.composingTimers.get(e);t&&(clearInterval(t),this.composingTimers.delete(e)),this.callbacks.onComposing?.(e,!1)}sessionHasEvents(e){for(const t of this.running.values())if(t.session_id===e)return!0;return this.queued.some(t=>t.session_id===e)}getFirstRunningEventId(e){for(const t of this.running.values())if(t.session_id===e)return t.event_id}}export{a as EventQueue};
1
+ const a=25e3;class c{config;callbacks;running=new Map;queued=[];timers=new Map;composingTimers=new Map;adapterDoneEventIds=new Set;pauseReasons=new Set;get ready(){return this.pauseReasons.size===0}constructor(e,t){this.config=e,this.callbacks=t}pause(e){this.pauseReasons.add(e)}resume(e){this.pauseReasons.delete(e)&&this.ready&&this.drainNext()}submit(e){return this.running.has(e.event_id)||this.queued.some(t=>t.event_id===e.event_id)?"accepted":this.ready&&this.running.size<this.config.maxConcurrent?(this.startRunning(e),"accepted"):this.config.maxQueued<=0||this.queued.length>=this.config.maxQueued?(this.callbacks.onRejected(e,"queue full"),"rejected"):(this.enqueue(e),"accepted")}cancel(e){const t=this.queued.findIndex(i=>i.event_id===e);if(t>=0){if(!this.config.cancelableQueued)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.callbacks.onStateChange(e,i.session_id,"canceled",{reason:"canceled by user"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id),!0}return this.running.has(e)&&this.config.cancelableRunning?(this.callbacks.onCancelRunning(e),!0):!1}removeQueued(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return!1;const[i]=this.queued.splice(t,1);return this.clearTimer(e),this.broadcastQueuePositions(),this.checkStopComposing(i.session_id),!0}complete(e){const i=this.running.get(e)?.session_id;this.running.delete(e),this.adapterDoneEventIds.delete(e),this.clearTimer(e),queueMicrotask(()=>this.drainNext()),i&&this.checkStopComposing(i)}clear(e,t="queue cleared"){const i=[],n=[];for(const u of this.queued)u.session_id===e?(this.clearTimer(u.event_id),this.callbacks.onStateChange(u.event_id,e,"canceled",{reason:t}),i.push(u.event_id)):n.push(u);return this.queued=n,i.length>0&&this.broadcastQueuePositions(),this.checkStopComposing(e),i}drainQueuedForSession(e){const t=[],i=[];for(const n of this.queued)n.session_id===e?(this.clearTimer(n.event_id),t.push(n)):i.push(n);return this.queued=i,t}snapshot(e){const t=[...this.running.values()].filter(s=>s.session_id===e),i=t.map(s=>s.event_id),n=t.map(s=>({event_id:s.event_id,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)})),u=this.queued.flatMap((s,o)=>s.session_id===e?[{event_id:s.event_id,position:o+1,content_preview:this.buildQueueItemTitle(s.content),title:this.buildQueueItemTitle(s.content),summary:this.buildQueueItemTitle(s.content)}]:[]);return{running:i,running_items:n,queued:u}}hasCapacity(){return this.running.size<this.config.maxConcurrent}get runningCount(){return this.running.size}get queuedCount(){return this.queued.length}destroy(){for(const e of this.timers.values())clearTimeout(e);this.timers.clear();for(const e of this.composingTimers.values())clearInterval(e);this.composingTimers.clear(),this.queued=[],this.running.clear(),this.pauseReasons.clear()}enqueue(e){this.queued.push(e);const t=this.queued.length;if(this.callbacks.onStateChange(e.event_id,e.session_id,"queued",{queue_position:t,queue_total:t,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.config.queueTimeoutMs>0){const i=setTimeout(()=>{this.timeoutEvent(e.event_id)},this.config.queueTimeoutMs);i.unref(),this.timers.set(e.event_id,i)}this.ensureComposing(e.session_id)}timeoutEvent(e){const t=this.queued.findIndex(n=>n.event_id===e);if(t<0)return;const[i]=this.queued.splice(t,1);this.timers.delete(e),this.callbacks.onStateChange(e,i.session_id,"failed",{reason:"queue timeout"}),this.broadcastQueuePositions(),this.drainNext(),this.checkStopComposing(i.session_id)}startRunning(e){this.running.set(e.event_id,e),this.callbacks.onStateChange(e.event_id,e.session_id,"running",{actions:this.config.cancelableRunning?[{type:"stop"}]:[],content_preview:this.buildQueueItemTitle(e.content)}),this.ensureComposing(e.session_id),this.callbacks.onDeliver(e)}drainNext(){for(;this.ready&&this.running.size<this.config.maxConcurrent&&this.queued.length>0;){const e=this.queued.shift();this.clearTimer(e.event_id),this.startRunning(e)}this.queued.length>0&&this.broadcastQueuePositions()}broadcastQueuePositions(){const e=this.queued.length;for(let t=0;t<e;t++){const i=this.queued[t];this.callbacks.onStateChange(i.event_id,i.session_id,"queued",{queue_position:t+1,queue_total:e,actions:this.config.cancelableQueued?[{type:"cancel"}]:[],content_preview:this.buildQueueItemTitle(i.content)})}}clearTimer(e){const t=this.timers.get(e);t&&(clearTimeout(t),this.timers.delete(e))}buildQueueItemTitle(e){const t=String(e??"").replace(/\s+/g," ").trim();return t?t.length>64?`${t.slice(0,64)}...`:t:"Message"}ensureComposing(e){if(!this.callbacks.onComposing||this.composingTimers.has(e))return;this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e));const t=setInterval(()=>{this.sessionHasEvents(e)?this.callbacks.onComposing(e,!0,this.getFirstRunningEventId(e)):this.stopComposing(e)},25e3);t.unref(),this.composingTimers.set(e,t)}checkStopComposing(e){this.sessionHasEvents(e)||this.stopComposing(e)}markAdapterDone(e){const t=this.running.get(e);t&&(this.adapterDoneEventIds.add(e),this.sessionHasEvents(t.session_id)||this.stopComposing(t.session_id))}stopComposing(e){const t=this.composingTimers.get(e);t&&(clearInterval(t),this.composingTimers.delete(e)),this.callbacks.onComposing?.(e,!1)}sessionHasEvents(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return!0;return this.queued.some(t=>t.session_id===e)}getFirstRunningEventId(e){for(const t of this.running.values())if(t.session_id===e&&!this.adapterDoneEventIds.has(t.event_id))return t.event_id}}export{c as EventQueue};
@@ -1,3 +1,3 @@
1
- import{log as c}from"../core/log/index.js";import{splitTextForAibotProtocol as g,AIBOT_PROTOCOL_MAX_RUNES as C}from"../core/protocol/protocol-text.js";import{isThinkingEventDropped as E,isToolEventDropped as k,normalizeRuntimeConfigSnapshot as S}from"./runtime-config.js";class d{handle;connectorRuntimeConfig;eventRuntimeConfigs=new Map;thinkingSeqMap=new Map;toolCardSeq=0;bufferedEventTexts=new Map;chunkSeqMap=new Map;rejectedEvents=new Set;stoppedEvents=new Set;streamCoalesce=new Map;streamCoalesceMs=T();static STREAM_COALESCE_MAX_RUNES=2048;constructor(e){this.connectorRuntimeConfig={...e}}bind(e){this.handle=e}markEventRejected(e){if(!e)return;this.rejectedEvents.add(e);const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])if(s.startsWith(t)){const n=this.streamCoalesce.get(s);n.timer&&clearTimeout(n.timer),this.streamCoalesce.delete(s)}c.info("send-ctrl",`event marked as stream-rejected, future chunks will be dropped event=${e}`)}markEventStopped(e){e&&(this.stoppedEvents.add(e),c.info("send-ctrl",`[stop-trace] event marked stopped; subsequent outbound will be flagged event=${e}`))}flagOutboundAfterStop(e,t,s,n,i,r){!e||!this.stoppedEvents.has(e)||c.warn("send-ctrl",`[stop-trace] OUTBOUND AFTER STOP ${s} event=${e} session=${t} seq=${n} is_finish=${i} len=${r}`)}getGlobalRuntimeConfig(){return this.connectorRuntimeConfig}setStreamCoalesceMs(e){this.streamCoalesceMs=Number.isFinite(e)&&e>0?Math.floor(e):0}captureEventRuntimeConfig(e){const t=S(e.connector_runtime_config)??this.connectorRuntimeConfig;this.eventRuntimeConfigs.set(e.event_id,{responseDelivery:t.responseDelivery,toolEvents:t.toolEvents,thinkingEvents:t.thinkingEvents})}resolveEventRuntimeConfig(e){if(e){const t=this.eventRuntimeConfigs.get(e);if(t)return t}return this.connectorRuntimeConfig}shouldDropToolDisplayEvent(e){return k(this.resolveEventRuntimeConfig(e))}shouldDropThinkingDisplayEvent(e){return E(this.resolveEventRuntimeConfig(e))}shouldDropCodexDisplayEvent(e,t){return this.shouldDropToolDisplayEvent(e)?t==="item/completed":!1}shouldDropAcpRawDisplayEvent(e,t){return!!(this.shouldDropToolDisplayEvent(e)&&(t==="tool_use"||t==="tool_result")||this.shouldDropThinkingDisplayEvent(e)&&t==="thinking")}sendReply(e,t,s,n,i,r){if(!r?.skipToolFilter&&this.shouldDropToolDisplayEvent(e)&&y(s,i)||e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"reply",0,!1,(s||"").length),this.resolveEventRuntimeConfig(e).responseDelivery==="stream"&&!i){const o=g(s),u=e?`reply_stream_${e}`:`reply_stream_${Date.now()}`;if(o.length===0){this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s||" ",quoted_message_id:n||void 0});return}for(let a=0;a<o.length;a++){const h=a===o.length-1;this.sendStreamChunk(e,t,o[a],a+1,h,u,n)}return}this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s,quoted_message_id:n||void 0,...i?{extra:i}:{}})}sendStreamChunk(e,t,s,n,i,r,l){if(e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"stream_chunk",n,i,(s||"").length),this.finalizeThinking(e,t),this.resolveEventRuntimeConfig(e).responseDelivery==="single_message"&&e){this.bufferStreamChunk(e,t,s,i,l);return}if(this.streamCoalesceMs<=0){this.emitStreamChunkParts(e,t,s,n,i,r,l);return}this.coalesceStreamChunk(e,t,s,i,r,l)}coalesceStreamChunk(e,t,s,n,i,r){if(!s&&!n)return;const l=`${e}:${i??"default"}`;let o=this.streamCoalesce.get(l);if(o||(o={eventId:e,sessionId:t,clientMsgId:i,quotedMessageId:r,text:"",flushedOnce:!1},this.streamCoalesce.set(l,o)),s&&(o.text+=s),o.sessionId=t,i!==void 0&&(o.clientMsgId=i),!o.quotedMessageId&&r&&(o.quotedMessageId=r),n){this.flushCoalesce(l,!0);return}if(!o.flushedOnce||o.text.length>=d.STREAM_COALESCE_MAX_RUNES){this.flushCoalesce(l,!1);return}o.timer||(o.timer=setTimeout(()=>this.flushCoalesce(l,!1),this.streamCoalesceMs))}flushCoalesce(e,t){const s=this.streamCoalesce.get(e);if(!s)return;s.timer&&(clearTimeout(s.timer),s.timer=void 0);const n=s.text;s.text="",s.flushedOnce=!0,t&&this.streamCoalesce.delete(e),!(!n&&!t)&&this.emitStreamChunkParts(s.eventId,s.sessionId,n,0,t,s.clientMsgId,s.quotedMessageId)}flushCoalesceForEvent(e){const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])s.startsWith(t)&&(this.flushCoalesce(s,!1),this.streamCoalesce.delete(s))}emitStreamChunkParts(e,t,s,n,i,r,l){const o=s||(i?`
2
- `:"");if(!o&&!i)return;const u=g(o,C);u.length===0&&u.push(o||`
3
- `);const a=`${e}:${r??"default"}`;for(let h=0;h<u.length;h++){const _=h===u.length-1,m=this.chunkSeqMap.get(a)??0,p=h===0&&n>m?n:m+1;this.chunkSeqMap.set(a,p),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:u[h],chunk_seq:p,is_finish:_&&i,...r?{client_msg_id:r}:{},...l?{quoted_message_id:l}:{}})}}sendEventResult(e,t,s,n){this.finalizeThinking(e,""),this.flushCoalesceForEvent(e),this.flushBufferedStreamText(e),this.handle.sendEventResult({event_id:e,status:t,...s?{msg:s}:{},...n?{code:n}:{},updated_at:Date.now()}),this.eventRuntimeConfigs.delete(e),this.rejectedEvents.delete(e),this.stoppedEvents.delete(e)&&c.info("send-ctrl",`[stop-trace] event_result(${t}) cleared stop flag event=${e}`);for(const i of this.chunkSeqMap.keys())i.startsWith(`${e}:`)&&this.chunkSeqMap.delete(i)}sendThinking(e,t,s){if(this.shouldDropThinkingDisplayEvent(e)||e&&this.rejectedEvents.has(e))return;this.flagOutboundAfterStop(e,t,"thinking",this.thinkingSeqMap.get(e)??0,!1,(s||"").length);const n=(this.thinkingSeqMap.get(e)??0)+1;this.thinkingSeqMap.set(e,n),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:s,chunk_seq:n,is_finish:!1,client_msg_id:`${e}_thinking`,is_thinking:!0})}sendToolExecutionCard(e,t,s,n){if(this.shouldDropToolDisplayEvent(e)){c.info("send-ctrl",`[tool-card] DROP event=${e} session=${t} summary=${s.summaryText}`);return}const r={summary_text:s.summaryText};s.detailText&&(r.detail_text=s.detailText),c.info("send-ctrl",`[tool-card] SEND event=${e} session=${t} summary=${s.summaryText}`),this.handle.sendMsg({event_id:e,session_id:t,client_msg_id:n??`tool_${++this.toolCardSeq}_${Date.now()}`,msg_type:1,content:"",extra:{channel_data:{grix:{toolExecution:r}},agent_api_origin:!0}})}finalizeThinking(e,t){const s=this.thinkingSeqMap.get(e);s!==void 0&&(this.thinkingSeqMap.delete(e),t&&(e&&this.rejectedEvents.has(e)||this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:"",chunk_seq:s+1,is_finish:!0,client_msg_id:`${e}_thinking`,is_thinking:!0})))}bufferOnly(e,t,s,n,i){this.bufferStreamChunk(e,t,s,n,i)}bufferStreamChunk(e,t,s,n,i){const r=this.bufferedEventTexts.get(e);r?(s&&(r.text+=s),!r.quotedMessageId&&i&&(r.quotedMessageId=i)):this.bufferedEventTexts.set(e,{sessionId:t,text:s??"",quotedMessageId:i||void 0}),n&&this.flushBufferedStreamText(e)}flushBufferedStreamText(e){const t=this.bufferedEventTexts.get(e);t&&(this.bufferedEventTexts.delete(e),t.text&&this.handle.sendMsg({event_id:e,session_id:t.sessionId,msg_type:1,content:t.text,...t.quotedMessageId?{quoted_message_id:t.quotedMessageId}:{}}))}}function T(){const f=process.env.GRIX_STREAM_COALESCE_MS;if(f===void 0||f==="")return 500;const e=Number(f);return Number.isFinite(e)&&e>=0?Math.floor(e):500}function y(f,e){return e&&typeof e=="object"&&e.channel_data?.grix?.toolExecution?!0:/^\[tool\]/.test(f)||/^\[tool result\]/.test(f)}export{d as SendController};
1
+ import{log as f}from"../core/log/index.js";import{splitTextForAibotProtocol as g,AIBOT_PROTOCOL_MAX_RUNES as C}from"../core/protocol/protocol-text.js";import{isThinkingEventDropped as E,isToolEventDropped as k,normalizeRuntimeConfigSnapshot as S}from"./runtime-config.js";class p{handle;connectorRuntimeConfig;eventRuntimeConfigs=new Map;thinkingSeqMap=new Map;toolCardSeq=0;bufferedEventTexts=new Map;chunkSeqMap=new Map;rejectedEvents=new Set;stoppedEvents=new Set;streamCoalesce=new Map;streamCoalesceMs=T();static STREAM_COALESCE_MAX_RUNES=2048;recentToolCards=new Map;static TOOL_CARD_DEDUP_MS=100;constructor(e){this.connectorRuntimeConfig={...e}}bind(e){this.handle=e}markEventRejected(e){if(!e)return;this.rejectedEvents.add(e);const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])if(s.startsWith(t)){const i=this.streamCoalesce.get(s);i.timer&&clearTimeout(i.timer),this.streamCoalesce.delete(s)}f.info("send-ctrl",`event marked as stream-rejected, future chunks will be dropped event=${e}`)}markEventStopped(e){e&&(this.stoppedEvents.add(e),f.info("send-ctrl",`[stop-trace] event marked stopped; subsequent outbound will be flagged event=${e}`))}flagOutboundAfterStop(e,t,s,i,n,o){!e||!this.stoppedEvents.has(e)||f.warn("send-ctrl",`[stop-trace] OUTBOUND AFTER STOP ${s} event=${e} session=${t} seq=${i} is_finish=${n} len=${o}`)}getGlobalRuntimeConfig(){return this.connectorRuntimeConfig}setStreamCoalesceMs(e){this.streamCoalesceMs=Number.isFinite(e)&&e>0?Math.floor(e):0}captureEventRuntimeConfig(e){const t=S(e.connector_runtime_config)??this.connectorRuntimeConfig;this.eventRuntimeConfigs.set(e.event_id,{responseDelivery:t.responseDelivery,toolEvents:t.toolEvents,thinkingEvents:t.thinkingEvents})}resolveEventRuntimeConfig(e){if(e){const t=this.eventRuntimeConfigs.get(e);if(t)return t}return this.connectorRuntimeConfig}shouldDropToolDisplayEvent(e){return k(this.resolveEventRuntimeConfig(e))}shouldDropThinkingDisplayEvent(e){return E(this.resolveEventRuntimeConfig(e))}shouldDropCodexDisplayEvent(e,t){return this.shouldDropToolDisplayEvent(e)?t==="item/completed":!1}shouldDropAcpRawDisplayEvent(e,t){return!!(this.shouldDropToolDisplayEvent(e)&&(t==="tool_use"||t==="tool_result")||this.shouldDropThinkingDisplayEvent(e)&&t==="thinking")}sendReply(e,t,s,i,n,o){if(!o?.skipToolFilter&&this.shouldDropToolDisplayEvent(e)&&y(s,n)||e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"reply",0,!1,(s||"").length),this.resolveEventRuntimeConfig(e).responseDelivery==="stream"&&!n){const r=g(s),a=e?`reply_stream_${e}`:`reply_stream_${Date.now()}`;if(r.length===0){this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s||" ",quoted_message_id:i||void 0});return}for(let u=0;u<r.length;u++){const h=u===r.length-1;this.sendStreamChunk(e,t,r[u],u+1,h,a,i)}return}this.handle.sendMsg({event_id:e,session_id:t,msg_type:1,content:s,quoted_message_id:i||void 0,...n?{extra:n}:{}})}sendStreamChunk(e,t,s,i,n,o,l){if(e&&this.rejectedEvents.has(e))return;if(this.flagOutboundAfterStop(e,t,"stream_chunk",i,n,(s||"").length),this.finalizeThinking(e,t),this.resolveEventRuntimeConfig(e).responseDelivery==="single_message"&&e){this.bufferStreamChunk(e,t,s,n,l);return}if(this.streamCoalesceMs<=0){this.emitStreamChunkParts(e,t,s,i,n,o,l);return}this.coalesceStreamChunk(e,t,s,n,o,l)}coalesceStreamChunk(e,t,s,i,n,o){if(!s&&!i)return;const l=`${e}:${n??"default"}`;let r=this.streamCoalesce.get(l);if(r||(r={eventId:e,sessionId:t,clientMsgId:n,quotedMessageId:o,text:"",flushedOnce:!1},this.streamCoalesce.set(l,r)),s&&(r.text+=s),r.sessionId=t,n!==void 0&&(r.clientMsgId=n),!r.quotedMessageId&&o&&(r.quotedMessageId=o),i){this.flushCoalesce(l,!0);return}if(!r.flushedOnce||r.text.length>=p.STREAM_COALESCE_MAX_RUNES){this.flushCoalesce(l,!1);return}r.timer||(r.timer=setTimeout(()=>this.flushCoalesce(l,!1),this.streamCoalesceMs))}flushCoalesce(e,t){const s=this.streamCoalesce.get(e);if(!s)return;s.timer&&(clearTimeout(s.timer),s.timer=void 0);const i=s.text;s.text="",s.flushedOnce=!0,t&&this.streamCoalesce.delete(e),!(!i&&!t)&&this.emitStreamChunkParts(s.eventId,s.sessionId,i,0,t,s.clientMsgId,s.quotedMessageId)}flushCoalesceForEvent(e){const t=`${e}:`;for(const s of[...this.streamCoalesce.keys()])s.startsWith(t)&&(this.flushCoalesce(s,!1),this.streamCoalesce.delete(s))}emitStreamChunkParts(e,t,s,i,n,o,l){const r=s||(n?`
2
+ `:"");if(!r&&!n)return;const a=g(r,C);a.length===0&&a.push(r||`
3
+ `);const u=`${e}:${o??"default"}`;for(let h=0;h<a.length;h++){const _=h===a.length-1,m=this.chunkSeqMap.get(u)??0,d=h===0&&i>m?i:m+1;this.chunkSeqMap.set(u,d),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:a[h],chunk_seq:d,is_finish:_&&n,...o?{client_msg_id:o}:{},...l?{quoted_message_id:l}:{}})}}sendEventResult(e,t,s,i){this.finalizeThinking(e,""),this.flushCoalesceForEvent(e),this.flushBufferedStreamText(e),this.handle.sendEventResult({event_id:e,status:t,...s?{msg:s}:{},...i?{code:i}:{},updated_at:Date.now()}),this.eventRuntimeConfigs.delete(e),this.rejectedEvents.delete(e),this.stoppedEvents.delete(e)&&f.info("send-ctrl",`[stop-trace] event_result(${t}) cleared stop flag event=${e}`);for(const n of this.chunkSeqMap.keys())n.startsWith(`${e}:`)&&this.chunkSeqMap.delete(n);for(const n of this.recentToolCards.keys())n.startsWith(`${e}:`)&&this.recentToolCards.delete(n)}sendThinking(e,t,s){if(this.shouldDropThinkingDisplayEvent(e)||e&&this.rejectedEvents.has(e))return;this.flagOutboundAfterStop(e,t,"thinking",this.thinkingSeqMap.get(e)??0,!1,(s||"").length);const i=(this.thinkingSeqMap.get(e)??0)+1;this.thinkingSeqMap.set(e,i),this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:s,chunk_seq:i,is_finish:!1,client_msg_id:`${e}_thinking`,is_thinking:!0})}sendToolExecutionCard(e,t,s,i){if(this.shouldDropToolDisplayEvent(e)){f.info("send-ctrl",`[tool-card] DROP event=${e} session=${t} summary=${s.summaryText}`);return}const o=Date.now(),l=`${e}:${s.summaryText}:${s.detailText??""}`,r=this.recentToolCards.get(l);if(r!==void 0&&o-r<p.TOOL_CARD_DEDUP_MS){f.info("send-ctrl",`[tool-card] DEDUP event=${e} session=${t} summary=${s.summaryText}`);return}this.recentToolCards.set(l,o);const a={summary_text:s.summaryText};s.detailText&&(a.detail_text=s.detailText),f.info("send-ctrl",`[tool-card] SEND event=${e} session=${t} summary=${s.summaryText}`),this.handle.sendMsg({event_id:e,session_id:t,client_msg_id:i??`tool_${++this.toolCardSeq}_${Date.now()}`,msg_type:1,content:"",extra:{channel_data:{grix:{toolExecution:a}},agent_api_origin:!0}})}finalizeThinking(e,t){const s=this.thinkingSeqMap.get(e);s!==void 0&&(this.thinkingSeqMap.delete(e),t&&(e&&this.rejectedEvents.has(e)||this.handle.sendStreamChunk({event_id:e,session_id:t,delta_content:"",chunk_seq:s+1,is_finish:!0,client_msg_id:`${e}_thinking`,is_thinking:!0})))}bufferOnly(e,t,s,i,n){this.bufferStreamChunk(e,t,s,i,n)}bufferStreamChunk(e,t,s,i,n){const o=this.bufferedEventTexts.get(e);o?(s&&(o.text+=s),!o.quotedMessageId&&n&&(o.quotedMessageId=n)):this.bufferedEventTexts.set(e,{sessionId:t,text:s??"",quotedMessageId:n||void 0}),i&&this.flushBufferedStreamText(e)}flushBufferedStreamText(e){const t=this.bufferedEventTexts.get(e);t&&(this.bufferedEventTexts.delete(e),t.text&&this.handle.sendMsg({event_id:e,session_id:t.sessionId,msg_type:1,content:t.text,...t.quotedMessageId?{quoted_message_id:t.quotedMessageId}:{}}))}}function T(){const c=process.env.GRIX_STREAM_COALESCE_MS;if(c===void 0||c==="")return 500;const e=Number(c);return Number.isFinite(e)&&e>=0?Math.floor(e):500}function y(c,e){return e&&typeof e=="object"&&e.channel_data?.grix?.toolExecution?!0:/^\[tool\]/.test(c)||/^\[tool result\]/.test(c)}export{p as SendController};
@@ -1,4 +1,4 @@
1
- import{SESSION_CONTROL_VERBS as l,SESSION_CONTROL_ERROR_CODES as g}from"../adapter/claude/protocol-contract.js";import{parseInteractionMessage as _,readCardSubmissionParam as b}from"../core/protocol/interaction-parser.js";import{log as S}from"../core/log/index.js";function w(t){const o=String(t.content??"").trim();if(!o.startsWith("/grix ")){const s=_(o);for(const i of s.cardSubmissions){if(i.cardId!=="agent_open_session")continue;const e=b(i,"cwd")??"";return e?{verb:l.open,args:e}:null}return null}const d=o.slice(6).trim().split(/\s+/),c=d[0]?.toLowerCase(),m=d.slice(1).join(" ");return Object.values(l).includes(c)?{verb:c,args:m}:null}function k(t,o,d,c){const{verb:m,args:r}=t,s=o.session_id,i=()=>c.sendEventAck(o.event_id,s),e=(n,a)=>c.sendEventResult(o.event_id,n,a);switch(m){case l.open:{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}d.getSessionBindings().set(s,n),d.onSessionBound?.(s,n),i(),e("responded",{msg:`Session bound to ${n}`});break}case l.where:{const n=d.getSessionBindings().get(s)||d.getCwd()||"not set";i(),e("responded",{msg:`Working directory: ${n}`});break}case l.status:{const n=d.getStatus(),a=d.getAcpSessionOptions(),f=a?`
1
+ import{SESSION_CONTROL_VERBS as l,SESSION_CONTROL_ERROR_CODES as g}from"../adapter/claude/protocol-contract.js";import{parseInteractionMessage as _,readCardSubmissionParam as b}from"../core/protocol/interaction-parser.js";import{log as S}from"../core/log/index.js";function w(t){const o=String(t.content??"").trim();if(!o.startsWith("/grix ")){const s=_(o);for(const i of s.cardSubmissions){if(i.cardId!=="agent_open_session")continue;const e=b(i,"cwd")??"";return{verb:l.open,args:e}}return null}const d=o.slice(6).trim().split(/\s+/),c=d[0]?.toLowerCase(),m=d.slice(1).join(" ");return Object.values(l).includes(c)?{verb:c,args:m}:null}function k(t,o,d,c){const{verb:m,args:r}=t,s=o.session_id,i=()=>c.sendEventAck(o.event_id,s),e=(n,a)=>c.sendEventResult(o.event_id,n,a);switch(m){case l.open:{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix open <working-directory>",code:g.cwdRequired});return}d.getSessionBindings().set(s,n),d.onSessionBound?.(s,n),i(),e("responded",{msg:`Session bound to ${n}`});break}case l.where:{const n=d.getSessionBindings().get(s)||d.getCwd()||"not set";i(),e("responded",{msg:`Working directory: ${n}`});break}case l.status:{const n=d.getStatus(),a=d.getAcpSessionOptions(),f=a?`
2
2
  Mode: ${a.currentModeId} (${a.modes.map(p=>p.id).join(", ")})`:"",u=a?`
3
3
  Model: ${a.currentModelId} (${a.models.map(p=>p.modelId).join(", ")})`:"";i(),e("responded",{msg:`Status: alive=${n.alive} busy=${n.busy} adapter=${n.adapterType}${f}${u}`});break}case l.stop:{if(!d.getSessionBindings().get(s)){i(),e("failed",{msg:"No session binding found for this session",code:g.bindingMissing});return}d.cancelActiveRun?.().catch(()=>{}),i(),e("responded",{msg:`Session ${s} stopped`});break}case l.restart:{const n=d.getSessionBindings().get(s);if(!n){i(),e("failed",{msg:"No session binding found for this session",code:g.bindingMissing});return}d.cancelActiveRun?.().catch(()=>{}),i(),e("responded",{msg:`Session ${s} restarted for ${n}`});break}case"set_mode":{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix set_mode <mode-id>"});return}d.setMode(n).then(a=>{a&&d.onModeSet?.(n),i(),e(a?"responded":"failed",{msg:a?`Mode set to ${n}`:`Failed to set mode ${n}`})});break}case"set_model":{const n=r.trim();if(!n){i(),e("failed",{msg:"Usage: /grix set_model <model-id>"});return}d.setModel(n).then(a=>{a&&d.onModelSet?.(n),i(),e(a?"responded":"failed",{msg:a?`Model set to ${n}`:`Failed to set model ${n}`})});break}case"list_options":{const n=d.getAcpSessionOptions();if(!n){i(),e("failed",{msg:"No ACP session active"});return}const a=n.modes.map(u=>`${u.id}: ${u.name}`).join(`
4
4
  `),f=n.models.map(u=>`${u.modelId}: ${u.name}`).join(`
@@ -1 +1 @@
1
- import{readdirSync as h,statSync as l}from"node:fs";import i from"node:path";import{homedir as f}from"node:os";class g{cachedEntries=null;builtAt=0;dirSignatures=new Map;inflight=null;minCheckIntervalMs;maxTTLMs;scanner;resolveLeafDirs;constructor(t,s,e){this.scanner=t,this.resolveLeafDirs=s,this.minCheckIntervalMs=e?.minCheckIntervalMs??2e3,this.maxTTLMs=e?.maxTTLMs??3e4}invalidate(){this.cachedEntries=null,this.builtAt=0,this.dirSignatures.clear()}get(){const t=Date.now();return this.cachedEntries&&t-this.builtAt<this.minCheckIntervalMs?this.cachedEntries:!this.cachedEntries||t-this.builtAt>=this.maxTTLMs?this.rebuild():this.hasSignatureChanged()?this.rebuild():this.cachedEntries}rebuild(){const t=this.scanner();return this.cachedEntries=t,this.builtAt=Date.now(),this.refreshSignatures(),t}hasSignatureChanged(){const t=this.resolveLeafDirs();if(t.length!==this.dirSignatures.size)return!0;for(const s of t){const e=this.dirSignatures.get(s);if(!e)return!0;try{if(l(s).mtimeMs!==e.mtimeMs||h(s).length!==e.childCount)return!0}catch{return!0}}return!1}refreshSignatures(){this.dirSignatures.clear();const t=this.resolveLeafDirs();for(const s of t)try{const e=l(s),r=h(s);this.dirSignatures.set(s,{mtimeMs:e.mtimeMs,childCount:r.length})}catch{}}}function y(n){const t=n||process.env.CODEX_HOME||i.join(f(),".codex"),s=[i.join(t,"sessions"),i.join(t,"archived_sessions")],e=[];for(const r of s)e.push(r),d(r,3,7,e);return e}function j(n){const t=n||i.join(f(),".claude","projects"),s=[t];try{const e=h(t,{withFileTypes:!0});for(const r of e)r.isDirectory()&&s.push(i.join(t,r.name))}catch{}return s}function v(n){const t=n||f(),s=[],e=i.join(t,".gemini","tmp");s.push(e);try{const o=h(e,{withFileTypes:!0});for(const c of o)if(c.isDirectory()){const u=i.join(e,c.name,"chats");s.push(u)}}catch{}const r=i.join(t,".qwen","projects");s.push(r);try{const o=h(r,{withFileTypes:!0});for(const c of o)if(c.isDirectory()){const u=i.join(r,c.name,"chats");s.push(u)}}catch{}const a=i.join(t,".kiro","sessions","cli");return s.push(a),s}function d(n,t,s,e){if(t<=0){e.push(n);return}let r;try{r=h(n)}catch{return}r.sort((o,c)=>c.localeCompare(o));const a=r.slice(0,s);for(const o of a){const c=i.join(n,o);try{l(c).isDirectory()&&d(c,t-1,s,e)}catch{}}}export{g as SessionScanCache,v as resolveAcpLeafDirs,j as resolveClaudeLeafDirs,y as resolveCodexLeafDirs};
1
+ import{readdirSync as h,statSync as l}from"node:fs";import o from"node:path";import{homedir as u}from"node:os";import{resolveKimiCodeHome as p,resolveCopilotHome as d,resolveReasonixSessionsDir as D}from"../adapter/acp/session-scanner.js";import{resolvePiAgentDir as v}from"../adapter/pi/session-scanner.js";import{resolveCodeWhaleHome as y}from"../adapter/codewhale/session-scanner.js";import{resolveCursorChatsDir as j}from"../adapter/cursor/session-scanner.js";import{resolveOpenCodeDataDir as g}from"../adapter/opencode/session-scanner.js";class E{cachedEntries=null;builtAt=0;dirSignatures=new Map;inflight=null;minCheckIntervalMs;maxTTLMs;scanner;resolveLeafDirs;constructor(s,t,e){this.scanner=s,this.resolveLeafDirs=t,this.minCheckIntervalMs=e?.minCheckIntervalMs??2e3,this.maxTTLMs=e?.maxTTLMs??3e4}invalidate(){this.cachedEntries=null,this.builtAt=0,this.dirSignatures.clear()}get(){const s=Date.now();return this.cachedEntries&&s-this.builtAt<this.minCheckIntervalMs?this.cachedEntries:!this.cachedEntries||s-this.builtAt>=this.maxTTLMs?this.rebuild():this.hasSignatureChanged()?this.rebuild():this.cachedEntries}rebuild(){const s=this.scanner();return this.cachedEntries=s,this.builtAt=Date.now(),this.refreshSignatures(),s}hasSignatureChanged(){const s=this.resolveLeafDirs();if(s.length!==this.dirSignatures.size)return!0;for(const t of s){const e=this.dirSignatures.get(t);if(!e)return!0;try{if(l(t).mtimeMs!==e.mtimeMs||h(t).length!==e.childCount)return!0}catch{return!0}}return!1}refreshSignatures(){this.dirSignatures.clear();const s=this.resolveLeafDirs();for(const t of s)try{const e=l(t),r=h(t);this.dirSignatures.set(t,{mtimeMs:e.mtimeMs,childCount:r.length})}catch{}}}function b(i){const s=i||process.env.CODEX_HOME||o.join(u(),".codex"),t=[o.join(s,"sessions"),o.join(s,"archived_sessions")],e=[];for(const r of t)e.push(r),m(r,3,7,e);return e}function A(i){const s=i||o.join(u(),".claude","projects"),t=[s];try{const e=h(s,{withFileTypes:!0});for(const r of e)r.isDirectory()&&t.push(o.join(s,r.name))}catch{}return t}function F(i,s){const t=i||u(),e=[],r=n=>!s||s.includes(n);if(r("gemini")){const n=o.join(t,".gemini","tmp");e.push(n);try{const a=h(n,{withFileTypes:!0});for(const c of a)if(c.isDirectory()){const f=o.join(n,c.name,"chats");e.push(f)}}catch{}}if(r("qwen")){const n=o.join(t,".qwen","projects");e.push(n);try{const a=h(n,{withFileTypes:!0});for(const c of a)if(c.isDirectory()){const f=o.join(n,c.name,"chats");e.push(f)}}catch{}}if(r("kiro")&&e.push(o.join(t,".kiro","sessions","cli")),r("kimi")){const n=o.join(p(t),"sessions");e.push(n);try{const a=h(n,{withFileTypes:!0});for(const c of a)c.isDirectory()&&e.push(o.join(n,c.name))}catch{}}return r("copilot")&&e.push(o.join(d(t),"session-state")),e}function H(i){return[D(i||u())]}function I(i,s){const t=o.join(v(i||u(),s),"sessions"),e=[t];try{for(const r of h(t,{withFileTypes:!0}))r.isDirectory()&&e.push(o.join(t,r.name))}catch{}return e}function O(i){return[o.join(y(i||u()),"sessions")]}function R(i){const s=j(i||u()),t=[s];try{for(const e of h(s,{withFileTypes:!0}))e.isDirectory()&&t.push(o.join(s,e.name))}catch{}return t}function q(i){return[g(i||u())]}function m(i,s,t,e){if(s<=0){e.push(i);return}let r;try{r=h(i)}catch{return}r.sort((a,c)=>c.localeCompare(a));const n=r.slice(0,t);for(const a of n){const c=o.join(i,a);try{l(c).isDirectory()&&m(c,s-1,t,e)}catch{}}}export{E as SessionScanCache,F as resolveAcpLeafDirs,A as resolveClaudeLeafDirs,O as resolveCodeWhaleLeafDirs,b as resolveCodexLeafDirs,R as resolveCursorLeafDirs,q as resolveOpenCodeLeafDirs,I as resolvePiLeafDirs,H as resolveReasonixLeafDirs};
@@ -1,2 +1,2 @@
1
- import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import p from"node:os";import m from"ws";import{log as c}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as S}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as k,KICKED_REASON_AGENT_DELETED as A,AgentDeletedError as $}from"./errors.js";function y(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const R="aibot-agent-api-v1",T=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,n;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const s=S();s>0&&(n=s)}catch(s){c.warn("aibot",`file server pre-start failed: ${s}`)}}catch(s){c.warn("aibot",`tailnet detect failed: ${s}`)}})();return new Promise((s,h)=>{const o=new m(this.config.url);this.ws=o;const a=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(a);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,c.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),s(l)):l.code===k?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(a),h(u)},timer:r}),o.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:R,contract_version:T,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:p.platform(),arch:p.arch(),os_release:p.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...n!==void 0&&n>0&&{file_server_https_port:n}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),o.on("message",u=>{if(this.ws!==o)return;let l;try{l=JSON.parse(y(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),o.on("close",(u,l)=>{if(this.ws!==o)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;c.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),o.on("error",u=>{this.ws===o&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===A){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}c.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,n=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${n?` ${n}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const n=this.seqEventMap.get(e.seq);n&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(n),c.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${n}`),this.emit("streamRejected",n,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||c.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(c.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
- `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||c.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,n=15e3){return new Promise((i,s)=>{const h=b(),o=Math.max(1e3,Math.min(n,6e4)),a=setTimeout(()=>{this.pendingInvokes.delete(h),s(new Error(`agent_invoke timeout: ${e}`))},o);this.pendingInvokes.set(h,{resolve:i,reject:s,timer:a}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:o})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,n){return new Promise((i,s)=>{const h=++this.seq,o=setTimeout(()=>{this.pendingRequests.delete(h),s(new Error(`request timeout: ${e} (expected ${n.expected.join("/")})`))},n.timeoutMs);this.pendingRequests.set(h,{expected:n.expected,resolve:i,reject:s,timer:o}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(o),s(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,n=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:n})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){c.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,c.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),n=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+n)),!this.reconnecting)return;try{const i=await this.connect(),s=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,c.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${s}`),this.emit("auth",i);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const s=i instanceof Error?i.message:String(i);if(c.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${s}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,c.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(s)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,c.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,n){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const s=n??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const o=t.event_id;if(o&&(this.seqEventMap.set(s,o),this.seqEventMap.size>200)){const a=this.seqEventMap.keys().next().value;a!==void 0&&this.seqEventMap.delete(a)}}const h={cmd:e,seq:s,payload:t};this.packetLog?.logOutboundPacket(e,s,t,"sent");try{const o=this.ws.readyState,a=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?c.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(e==="client_stream_chunk"){const r=t;d?c.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${s} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${s} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${o} bufferedAmount=${a}`)}else if(e==="event_ack"){const r=t;d?c.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(e==="send_msg"){const r=t;d?c.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${o} bufferedAmount=${a} err=${d.message}`):c.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${s} readyState=${o} bufferedAmount=${a}`)}else if(d){const r=t;c.warn("aibot",`${e} ws send failed seq=${s} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${o} bufferedAmount=${a} err=${d.message}`)}}),!0}catch(o){return this.emitClientError(new Error(`sendPacket failed: ${o}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,n??0,t,"dropped"),!1;if(n!==void 0)return this.packetLog?.logOutboundPacket(e,n,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,n??0,t,"buffered"),e==="client_stream_chunk"){const i=t;c.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,n=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let s=1;s<=t;s++){const h=this.ws?.readyState??-1,o=this.ws?.bufferedAmount??0;c.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${s}/${t} readyState=${h} bufferedAmount=${o}`);try{const a=await this.sendEventResultRequest(e,n);if(a.cmd==="send_ack"){const r=a.payload;c.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${s}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=a.payload;if(c.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${s}/${t} cmd=${a.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){c.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(a){const d=a instanceof Error?a.message:String(a);if(c.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${s}/${t} err=${d}`),s===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*s))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(n=>n.cmd!=="client_stream_chunk"?!0:n.payload?.event_id!==e),this.outboundBuffer.length<t&&c.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){c.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:n}of e){const i=++this.seq;if(t==="client_stream_chunk"&&n&&typeof n=="object"){const h=n.event_id;h&&this.seqEventMap.set(i,h)}const s={cmd:t,seq:i,payload:n};try{this.ws.send(JSON.stringify(s))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((n,i)=>n[0]-i[0]);this.seqEventMap.clear();for(const[n,i]of t.slice(-100))this.seqEventMap.set(n,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).catch(()=>{this.connected&&(this.cleanupSocket(),this.attemptReconnect())})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
1
+ import{EventEmitter as v}from"node:events";import{randomUUID as b}from"node:crypto";import p from"node:os";import m from"ws";import{log as a}from"../log/index.js";import{getMachineName as w}from"../util/index.js";import{detectTailnetIPv4 as E,ensureServerAndGetPort as q,getFileServerHttpsPort as A}from"../files/file-serve.js";import{AUTH_CODE_AGENT_DELETED as k,KICKED_REASON_AGENT_DELETED as S,AgentDeletedError as $}from"./errors.js";function y(g){return g.replace(/(?<=[\[:,\[]\s*)(\d{16,})(?=\s*[,}\]\n])/g,'"$1"')}function P(g){const e=[...g??["stream_chunk","local_action_v1","agent_invoke"]];return e.includes("agent_invoke")||e.push("agent_invoke"),e.includes("event_result_ack")||e.push("event_result_ack"),e}const R="aibot-agent-api-v1",T=1;class f extends v{static DROPPABLE_COMMANDS=new Set(["update_binding_card"]);static BUFFER_OVERFLOW_RETAIN_COMMANDS=new Set(["event_result","codex_event","client_stream_chunk"]);static MAX_OUTBOUND_BUFFER_SIZE=1e3;static BACKPRESSURE_THRESHOLD=64*1024;ws=null;seq=0;heartbeatTimer=null;heartbeatSec=30;heartbeatFailures=0;static HEARTBEAT_MAX_FAILURES=2;connected=!1;reconnecting=!1;reconnectAttempts=0;everConnected=!1;agentDeleted=!1;config;packetLog;pendingInvokes=new Map;seqEventMap=new Map;pendingRequests=new Map;outboundBuffer=[];ackPolicy=null;constructor(e,t){super(),this.packetLog=t?.packetLog??null,this.config={url:e.url,agentId:e.agentId,apiKey:e.apiKey,clientType:e.clientType,clientVersion:e.clientVersion??"",adapterHint:e.adapterHint??"",capabilities:P(e.capabilities),localActions:e.localActions??["exec_approve","exec_reject"],skills:e.skills}}get isConnected(){return this.connected}async connect(){let e,t,s;const i=(async()=>{try{if(e=await E(),e!==void 0)try{t=await q(e);const n=A();n>0&&(s=n)}catch(n){a.warn("aibot",`file server pre-start failed: ${n}`)}}catch(n){a.warn("aibot",`tailnet detect failed: ${n}`)}})();return new Promise((n,h)=>{const c=new m(this.config.url);this.ws=c;const o=setTimeout(()=>{h(new Error("Auth timeout: no auth_ack received within 15s")),this.cleanupSocket()},15e3),d=++this.seq,r=setTimeout(()=>{this.pendingRequests.delete(d),h(new Error("Auth request timeout")),this.cleanupSocket()},15e3);this.pendingRequests.set(d,{expected:["auth_ack"],resolve:u=>{clearTimeout(o);const l=u.payload;l.code===0?(this.connected=!0,this.everConnected=!0,this.reconnectAttempts=0,l.heartbeat_sec&&(this.heartbeatSec=l.heartbeat_sec),l.ack_policy&&(this.ackPolicy=l.ack_policy,a.info("aibot",`ack_policy received: push_ack_timeout_ms=${l.ack_policy.push_ack_timeout_ms??"default"} max_retries=${l.ack_policy.max_retries??"default"} timeout_action=${l.ack_policy.timeout_action??"default"}`)),this.startHeartbeat(),this.flushOutboundBuffer(),this.emit("auth",l),n(l)):l.code===k?(this.agentDeleted=!0,h(new $(`Agent deleted: code=${l.code} msg=${l.msg}`))):h(new Error(`Auth failed: code=${l.code} msg=${l.msg}`))},reject:u=>{clearTimeout(o),h(u)},timer:r}),c.on("open",async()=>{await i;const u={agent_id:this.config.agentId,api_key:this.config.apiKey,client_type:this.config.clientType,protocol_version:R,contract_version:T,capabilities:this.config.capabilities??[],local_actions:this.config.localActions,skills:this.config.skills};this.config.sharedOwnerId&&(u.shared_owner_id=this.config.sharedOwnerId),this.config.clientVersion&&(u.client="grix-connector",u.client_version=this.config.clientVersion,u.host_type=this.config.clientType,u.host_version=this.config.clientVersion),this.config.adapterHint&&(u.adapter_hint=this.config.adapterHint),u.host_meta={hostname:w(),platform:p.platform(),arch:p.arch(),os_release:p.release(),...e!==void 0&&{tailnet_ip:e},...t!==void 0&&t>0&&{file_server_port:t},...s!==void 0&&s>0&&{file_server_https_port:s}},this.config.concurrency&&(u.concurrency=this.config.concurrency),this.sendPacket("auth",u,d)}),c.on("message",u=>{if(this.ws!==c)return;let l;try{l=JSON.parse(y(u.toString()))}catch{return}try{this.handlePacket(l)}catch(_){this.emitClientError(new Error(`handlePacket error: ${_}`))}}),c.on("close",(u,l)=>{if(this.ws!==c)return;this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("websocket closed"),this.emit("close",u,l.toString());const _=u!==1e3&&this.everConnected&&!this.agentDeleted;a.info("aibot",`ws closed agent=${this.config.clientType}:${this.config.agentId} code=${u} reason=${l.toString()||"<none>"} everConnected=${this.everConnected} reconnecting=${this.reconnecting} agentDeleted=${this.agentDeleted} willReconnect=${_}`),_&&this.attemptReconnect()}),c.on("error",u=>{this.ws===c&&(this.emitClientError(u instanceof Error?u:new Error(String(u))),this.connected||h(u))})})}handlePacket(e){if(this.packetLog?.logInboundPacket(e.cmd,e.seq,e.payload),e.seq>0&&this.pendingRequests.has(e.seq)){const t=this.pendingRequests.get(e.seq);this.pendingRequests.delete(e.seq),clearTimeout(t.timer),t.expected.includes(e.cmd)?t.resolve(e):t.reject(new Error(`unexpected response: got ${e.cmd}, expected ${t.expected.join("/")}`));return}switch(e.cmd){case"auth_ack":break;case"ping":{this.sendPacket("pong",e.payload??{});break}case"event_msg":{this.emit("event",e.payload);break}case"local_action":{this.emit("localAction",e.payload);break}case"event_stop":{this.emit("stop",e.payload);break}case"event_revoke":{this.emit("revoke",e.payload);break}case"event_edit":{this.emit("edit",e.payload);break}case"event_cancel":{this.emit("eventCancel",e.payload);break}case"queue_clear":{this.emit("queueClear",e.payload);break}case"queue_snapshot_query":{this.emit("queueSnapshotQuery",e.payload);break}case"control_share_set":{this.emit("shareSet",e.payload);break}case"agent_profile_push":{this.emit("profilePush",e.payload);break}case"kicked":{const t=e.payload;if(this.emit("kicked",t),this.connected=!1,this.stopHeartbeat(),this.rejectAllPendingRequests("kicked"),this.outboundBuffer.length=0,t?.reason===S){if(this.agentDeleted=!0,this.reconnecting=!1,this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}a.error("aibot",`kicked: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}, reconnect disabled`),this.emit("agentDeleted",{source:"kicked",reason:t.reason});break}if(this.reconnectAttempts=Math.max(this.reconnectAttempts,3),this.ws){try{this.ws.close(4001,"kicked")}catch{}this.ws=null}break}case"error":{const t=e.payload,s=[t.ref_cmd?`ref_cmd=${t.ref_cmd}`:"",t.ref_id?`ref_id=${t.ref_id}`:""].filter(Boolean).join(" ");this.emitClientError(new Error(`Server error: code=${t.code} msg=${t.msg}${s?` ${s}`:""}`));break}case"agent_invoke_result":{this.handleInvokeResult(e.payload);break}case"mcp_frame":{const t=e.payload;this.emit("mcpFrame",t.session_id??"",t.frame??null);break}case"send_ack":break;case"send_nack":{const t=e.payload;if(t.code===4003&&e.seq>0){const s=this.seqEventMap.get(e.seq);s&&(this.seqEventMap.delete(e.seq),this.purgeBufferedStreamChunks(s),a.warn("aibot",`stream chunk rejected (4003), purging buffered chunks for event=${s}`),this.emit("streamRejected",s,t.code))}break}case"local_action_ack":break;default:break}}sendEventAck(e){this.sendPacket("event_ack",e)||a.warn("aibot",`event_ack NOT sent (ws not open) event=${e.event_id} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}sendStreamChunk(e){!e.delta_content&&!e.is_finish&&(a.warn("aibot",`stream_chunk delta_content empty, patched to newline event=${e.event_id??""} session=${e.session_id} chunk_seq=${e.chunk_seq} is_finish=${e.is_finish}`),e={...e,delta_content:`
2
+ `}),this.sendPacket("client_stream_chunk",e)}sendMsg(e){this.sendPacket("send_msg",e)||a.warn("aibot",`send_msg NOT sent (ws not open) event=${e.event_id??""} session=${e.session_id??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}editMsg(e){this.sendPacket("edit_msg",e)}sendEventResult(e){if(!this.ws||this.ws.readyState!==m.OPEN){this.sendPacket("event_result",e);return}this.sendEventResultReliable(e)}sendLocalActionResult(e){this.sendPacket("local_action_result",e)}sendEventStopAck(e){this.sendPacket("event_stop_ack",e)}sendEventStopResult(e){this.sendPacket("event_stop_result",e)}sendSessionActivitySet(e){this.sendPacket("session_activity_set",e)}sendCodexEvent(e){this.sendPacket("codex_event",e)}sendUpdateBindingCard(e){this.sendPacket("update_binding_card",e)}sendSkillsUpdate(e){this.sendPacket("agent_skills_update",e)}sendPing(){this.sendPacket("ping",{})}sendEventState(e){this.sendPacket("event_state",e)}sendEventCancelResult(e){this.sendPacket("event_cancel_result",e)}sendQueueClearResult(e){this.sendPacket("queue_clear_result",e)}sendQueueSnapshot(e){this.sendPacket("queue_snapshot",e)}agentInvoke(e,t,s=15e3){return new Promise((i,n)=>{const h=b(),c=Math.max(1e3,Math.min(s,6e4)),o=setTimeout(()=>{this.pendingInvokes.delete(h),n(new Error(`agent_invoke timeout: ${e}`))},c);this.pendingInvokes.set(h,{resolve:i,reject:n,timer:o}),this.sendPacket("agent_invoke",{invoke_id:h,action:e,params:t,timeout_ms:c})})}sendMcpFrame(e,t){this.sendPacket("mcp_frame",{session_id:e,frame:t})}request(e,t,s){return new Promise((i,n)=>{const h=++this.seq,c=setTimeout(()=>{this.pendingRequests.delete(h),n(new Error(`request timeout: ${e} (expected ${s.expected.join("/")})`))},s.timeoutMs);this.pendingRequests.set(h,{expected:s.expected,resolve:i,reject:n,timer:c}),this.sendPacket(e,t,h)||(this.pendingRequests.delete(h),clearTimeout(c),n(new Error(`send failed: ${e}`)))})}async sendStreamChunkRequest(e,t=2e4){return this.request("client_stream_chunk",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendText(e,t=2e4){return this.request("send_msg",{msg_type:1,...e},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async sendMedia(e,t=2e4){return this.request("send_msg",{...e,msg_type:2},{expected:["send_ack","send_nack","error"],timeoutMs:t})}async editMessage(e,t=2e4){return this.request("edit_msg",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}async deleteMessage(e,t,s=2e4){return this.request("delete_msg",{session_id:e,msg_id:t},{expected:["send_ack","send_nack","error"],timeoutMs:s})}async sendEventResultRequest(e,t=5e3){return this.request("event_result",e,{expected:["send_ack","send_nack","error"],timeoutMs:t})}disconnect(){a.info("aibot",`disconnect() agent=${this.config.clientType}:${this.config.agentId} wasConnected=${this.connected} reconnecting=${this.reconnecting} reconnectAttempts=${this.reconnectAttempts}`),this.connected=!1,this.everConnected=!1,this.reconnecting=!1,this.reconnectAttempts=0,this.stopHeartbeat(),this.rejectAllPendingInvokes("disconnect"),this.rejectAllPendingRequests("disconnect"),this.outboundBuffer.length=0,this.ws&&(this.ws.close(1e3,"client disconnect"),this.ws=null)}static MAX_CONSECUTIVE_AUTH_FAILURES=5;async attemptReconnect(){if(this.reconnecting||this.agentDeleted)return;this.reconnecting=!0,a.info("aibot",`attemptReconnect start agent=${this.config.clientType}:${this.config.agentId} fromAttempts=${this.reconnectAttempts}`),this.emit("disconnected");let e=0;for(;this.reconnecting;){const t=Math.min(1e3*2**this.reconnectAttempts,3e4),s=Math.floor(t*.2*Math.random());if(this.reconnectAttempts++,await new Promise(i=>setTimeout(i,t+s)),!this.reconnecting)return;try{await this.connect();const i=this.reconnectAttempts;this.reconnectAttempts=0,this.reconnecting=!1,a.info("aibot",`reconnect succeeded agent=${this.config.clientType}:${this.config.agentId} attempt=${i}`);return}catch(i){if(this.ws){try{this.ws.close()}catch{}this.ws=null}const n=i instanceof Error?i.message:String(i);if(a.warn("aibot",`reconnect failed agent=${this.config.clientType}:${this.config.agentId} attempt=${this.reconnectAttempts} err=${n}`),i instanceof $){this.agentDeleted=!0,this.reconnecting=!1,a.error("aibot",`reconnect aborted: agent deleted on platform agent=${this.config.clientType}:${this.config.agentId}`),this.emit("agentDeleted",{source:"auth_ack",code:k});return}if(/Auth failed/i.test(n)){if(e++,e>=f.MAX_CONSECUTIVE_AUTH_FAILURES){this.reconnecting=!1,a.error("aibot",`reconnect giving up after ${e} consecutive auth failures agent=${this.config.clientType}:${this.config.agentId}`);return}}else e=0}}}sendPacket(e,t,s){if(this.ws&&this.ws.readyState===m.OPEN){const i=this.ws.bufferedAmount>f.BACKPRESSURE_THRESHOLD;if(!i||!f.DROPPABLE_COMMANDS.has(e)){if(i&&f.DROPPABLE_COMMANDS.has(e))return!1;const n=s??++this.seq;if(e==="client_stream_chunk"&&t&&typeof t=="object"){const c=t.event_id;if(c&&(this.seqEventMap.set(n,c),this.seqEventMap.size>200)){const o=this.seqEventMap.keys().next().value;o!==void 0&&this.seqEventMap.delete(o)}}const h={cmd:e,seq:n,payload:t};this.packetLog?.logOutboundPacket(e,n,t,"sent");try{const c=this.ws.readyState,o=this.ws.bufferedAmount;return this.ws.send(JSON.stringify(h),d=>{if(e==="event_result"){const r=t;d?a.warn("aibot",`event_result ws send callback failed event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_result ws send callback ok event=${r.event_id??""} status=${r.status??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="client_stream_chunk"){const r=t;d?a.warn("aibot",`stream_chunk ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`stream_chunk ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} chunk_seq=${r.chunk_seq??""} is_finish=${r.is_finish??""} client_msg_id=${r.client_msg_id??""} quoted_message_id=${r.quoted_message_id??""} readyState=${c} bufferedAmount=${o}`)}else if(e==="event_ack"){const r=t;d?a.warn("aibot",`event_ack ws send failed event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`event_ack ws send ok event=${r.event_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(e==="send_msg"){const r=t;d?a.warn("aibot",`send_msg ws send failed event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o} err=${d.message}`):a.info("aibot",`send_msg ws send ok event=${r.event_id??""} session=${r.session_id??""} seq=${n} readyState=${c} bufferedAmount=${o}`)}else if(d){const r=t;a.warn("aibot",`${e} ws send failed seq=${n} session=${r.session_id??""} event=${r.event_id??""} client_msg_id=${r.client_msg_id??""} readyState=${c} bufferedAmount=${o} err=${d.message}`)}}),!0}catch(c){return this.emitClientError(new Error(`sendPacket failed: ${c}`)),!1}}}if(f.DROPPABLE_COMMANDS.has(e))return this.packetLog?.logOutboundPacket(e,s??0,t,"dropped"),!1;if(s!==void 0)return this.packetLog?.logOutboundPacket(e,s,t,"dropped"),!1;if(this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&(this.outboundBuffer=this.outboundBuffer.filter(i=>f.BUFFER_OVERFLOW_RETAIN_COMMANDS.has(i.cmd)),this.outboundBuffer.length>=f.MAX_OUTBOUND_BUFFER_SIZE&&this.outboundBuffer.shift()),this.outboundBuffer.push({cmd:e,payload:t}),this.packetLog?.logOutboundPacket(e,s??0,t,"buffered"),e==="client_stream_chunk"){const i=t;a.info("aibot",`stream_chunk buffered (ws not open) event=${i.event_id??""} session=${i.session_id??""} chunk_seq=${i.chunk_seq??""} is_finish=${i.is_finish??""} ws=${this.ws?`state=${this.ws.readyState}`:"null"}`)}return!1}async sendEventResultReliable(e){const t=this.ackPolicy?.max_retries??3,s=this.ackPolicy?.push_ack_timeout_ms??5e3,i=750;for(let n=1;n<=t;n++){const h=this.ws?.readyState??-1,c=this.ws?.bufferedAmount??0;a.info("aibot",`event_result send attempt event=${e.event_id} status=${e.status} attempt=${n}/${t} readyState=${h} bufferedAmount=${c}`);try{const o=await this.sendEventResultRequest(e,s);if(o.cmd==="send_ack"){const r=o.payload;a.info("aibot",`event_result ack event=${e.event_id} status=${e.status} attempt=${n}/${t} ack_event=${r.event_id??""} ack_status=${r.status??""}`);return}const d=o.payload;if(a.warn("aibot",`event_result rejected event=${e.event_id} status=${e.status} attempt=${n}/${t} cmd=${o.cmd} code=${d.code??""} msg=${d.msg??""}${d.ref_cmd?` ref_cmd=${d.ref_cmd}`:""}${d.ref_id?` ref_id=${d.ref_id}`:""}`),d.code===4003){a.warn("aibot",`event_result stopping retries: 4003 ownership denied event=${e.event_id}`);return}return}catch(o){const d=o instanceof Error?o.message:String(o);if(a.warn("aibot",`event_result attempt failed event=${e.event_id} status=${e.status} attempt=${n}/${t} err=${d}`),n===t){this.emitClientError(new Error(`event_result ack failed after ${t} attempts: event=${e.event_id} status=${e.status}`));return}await new Promise(r=>setTimeout(r,i*n))}}}purgeBufferedStreamChunks(e){const t=this.outboundBuffer.length;this.outboundBuffer=this.outboundBuffer.filter(s=>s.cmd!=="client_stream_chunk"?!0:s.payload?.event_id!==e),this.outboundBuffer.length<t&&a.info("aibot",`purged ${t-this.outboundBuffer.length} buffered stream chunks for event=${e}`)}emitClientError(e){if(this.listenerCount("error")===0){a.warn("aibot",`Client error (no listeners): ${e.message}`);return}this.emit("error",e)}flushOutboundBuffer(){if(this.outboundBuffer.length===0||!this.ws||this.ws.readyState!==m.OPEN)return;const e=this.outboundBuffer;this.outboundBuffer=[];for(const{cmd:t,payload:s}of e){const i=++this.seq;if(t==="client_stream_chunk"&&s&&typeof s=="object"){const h=s.event_id;h&&this.seqEventMap.set(i,h)}const n={cmd:t,seq:i,payload:s};try{this.ws.send(JSON.stringify(n))}catch{break}}if(this.seqEventMap.size>200){const t=[...this.seqEventMap.entries()].sort((s,i)=>s[0]-i[0]);this.seqEventMap.clear();for(const[s,i]of t.slice(-100))this.seqEventMap.set(s,i)}}handleInvokeResult(e){const t=this.pendingInvokes.get(e.invoke_id);t&&(this.pendingInvokes.delete(e.invoke_id),clearTimeout(t.timer),e.code===0?t.resolve(e.data??null):t.reject(new Error(`agent_invoke error code=${e.code}: ${e.msg??""}`)))}rejectAllPendingInvokes(e){for(const[,t]of this.pendingInvokes)clearTimeout(t.timer),t.reject(new Error(`agent_invoke canceled: ${e}`));this.pendingInvokes.clear()}rejectAllPendingRequests(e){for(const[,t]of this.pendingRequests)clearTimeout(t.timer),t.reject(new Error(`request canceled: ${e}`));this.pendingRequests.clear()}cleanupSocket(){if(this.ws)try{this.ws.close()}catch{}}startHeartbeat(){this.stopHeartbeat(),this.heartbeatFailures=0,this.heartbeatTimer=setInterval(()=>{this.connected&&this.request("ping",{ts:Date.now()},{expected:["pong"],timeoutMs:5e3}).then(()=>{this.heartbeatFailures=0}).catch(()=>{this.connected&&(this.heartbeatFailures++,!(this.heartbeatFailures<f.HEARTBEAT_MAX_FAILURES)&&(this.cleanupSocket(),this.attemptReconnect()))})},this.heartbeatSec*1e3)}stopHeartbeat(){this.heartbeatTimer&&(clearInterval(this.heartbeatTimer),this.heartbeatTimer=null)}}export{f as AibotClient};
@@ -1 +1 @@
1
- const c="grix://card/",l="grix://open/",u="_submit",d=/\(\s*(grix:\/\/card\/[\s\S]+?)\s*\)/gi,m=/grix:\/\/card\/[^\s)\]]+/gi,f=/^grix:\/\/open\/\S+$/i;function g(t){const n=[],r=new Set;for(const e of p(t)){const s=h(e);!s||r.has(s.normalizedUri)||(r.add(s.normalizedUri),n.push(s))}return{cardSubmissions:n}}function C(t,n){return U(t.params.get(n))}function p(t){const n=S(t),r=[];for(const o of n.matchAll(d)){const i=a(o[1]);i&&r.push(i)}for(const o of n.matchAll(m)){const i=a(o[0]);i&&r.push(i)}const e=n.trim();f.test(e)&&r.push(e);const s=a(e);return s.startsWith(c)&&r.push(s),R(r)}function h(t){if(t.startsWith(l))return _(t);if(!t.startsWith(c))return null;let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="card")return null;const r=n.pathname.replace(/^\/+/,"").trim();if(!r.endsWith(u))return null;const e=r.slice(0,-u.length).trim();return!e||e==="agent_open_session"?null:{cardId:e,rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function _(t){let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="open")return null;const r=n.pathname.split("/").map(e=>e.trim()).filter(Boolean);return r.length!==1||r[0]!=="session"?null:{cardId:"agent_open_session",rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function S(t){return t.replace(/&amp;/gi,"&")}function a(t){return t.replace(/\s+/g,"").trim()}function R(t){const n=[...new Set(t)];return n.filter(r=>!n.some(e=>e!==r&&e.startsWith(r)))}function U(t){if(!t)return;let n=t.trim();for(let r=0;r<3;r++)try{const e=decodeURIComponent(n);if(e===n)break;n=e}catch{break}return n.trim()||void 0}export{g as parseInteractionMessage,C as readCardSubmissionParam};
1
+ const c="grix://card/",m="grix://open/",u="_submit",f=/\(\s*(grix:\/\/card\/[\s\S]+?)\s*\)/gi,p=/grix:\/\/card\/[^\s)\]]+/gi,l=/^grix:\/\/open\/\S+$/i;function U(t){const n=[],r=new Set;for(const e of h(t)){const s=S(e);!s||r.has(s.normalizedUri)||(r.add(s.normalizedUri),n.push(s))}return{cardSubmissions:n}}function C(t,n){return g(t.params.get(n))}function I(t){return l.test(d(t).trim())}function h(t){const n=d(t),r=[];for(const o of n.matchAll(f)){const i=a(o[1]);i&&r.push(i)}for(const o of n.matchAll(p)){const i=a(o[0]);i&&r.push(i)}const e=n.trim();l.test(e)&&r.push(e);const s=a(e);return s.startsWith(c)&&r.push(s),R(r)}function S(t){if(t.startsWith(m))return _(t);if(!t.startsWith(c))return null;let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="card")return null;const r=n.pathname.replace(/^\/+/,"").trim();if(!r.endsWith(u))return null;const e=r.slice(0,-u.length).trim();return!e||e==="agent_open_session"?null:{cardId:e,rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function _(t){let n;try{n=new URL(t)}catch{return null}if(n.hostname!=="open")return null;const r=n.pathname.split("/").map(e=>e.trim()).filter(Boolean);return r.length!==1||r[0]!=="session"?null:{cardId:"agent_open_session",rawUri:t,normalizedUri:n.toString(),params:n.searchParams}}function d(t){return t.replace(/&amp;/gi,"&")}function a(t){return t.replace(/\s+/g,"").trim()}function R(t){const n=[...new Set(t)];return n.filter(r=>!n.some(e=>e!==r&&e.startsWith(r)))}function g(t){if(!t)return;let n=t.trim();for(let r=0;r<3;r++)try{const e=decodeURIComponent(n);if(e===n)break;n=e}catch{break}return n.trim()||void 0}export{I as isOpenSessionDirectiveMessage,U as parseInteractionMessage,C as readCardSubmissionParam};
@@ -1 +1 @@
1
- import{detectProvider as o,queryProviderQuota as t}from"./providers.js";export{o as detectProvider,t as queryProviderQuota};
1
+ import{detectProvider as i,queryProviderQuota as o,readKimiProviderSettings as t,resolveKimiProviderSettings as d}from"./providers.js";export{i as detectProvider,o as queryProviderQuota,t as readKimiProviderSettings,d as resolveKimiProviderSettings};
@@ -1 +1,2 @@
1
- function R(s){const e=s.toLowerCase();return e.includes("open.bigmodel.cn")||e.includes("bigmodel.cn")||e.includes("api.z.ai")?{id:"zhipu",label:"Zhipu GLM"}:e.includes("api.kimi.com")?{id:"kimi",label:"Kimi"}:e.includes("api.minimaxi.com")?{id:"minimax_cn",label:"MiniMax"}:e.includes("api.minimax.io")?{id:"minimax_en",label:"MiniMax"}:e.includes("api.deepseek.com")?{id:"deepseek",label:"DeepSeek"}:e.includes("api.stepfun.ai")||e.includes("api.stepfun.com")?{id:"stepfun",label:"StepFun"}:e.includes("api.siliconflow.cn")?{id:"siliconflow_cn",label:"SiliconFlow"}:e.includes("api.siliconflow.com")?{id:"siliconflow_en",label:"SiliconFlow"}:e.includes("openrouter.ai")?{id:"openrouter",label:"OpenRouter"}:e.includes("api.novita.ai")?{id:"novita",label:"Novita AI"}:null}const f=1e4;function u(s,e,i){return{provider:s,providerLabel:e,planName:null,tiers:[],balance:null,success:!1,error:i}}function m(s){if(typeof s=="number")return s;if(typeof s=="string"){const e=Number(s);return Number.isFinite(e)?e:null}return null}function w(s){if(!Number.isFinite(s)||s<=0)return null;try{return new Date(s).toISOString()}catch{return null}}function T(s){if(typeof s=="string")return s;if(typeof s=="number"){const e=s<1e12?s*1e3:s;return w(e)}return null}async function S(s){const e="zhipu",i="Zhipu GLM";try{const t=await fetch("https://api.z.ai/api/monitor/usage/quota/limit",{method:"GET",headers:{Authorization:s,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const o=await t.json();if(o.success===!1)return u(e,i,`API error: ${o.msg??"Unknown error"}`);const n=o.data;if(!n)return u(e,i,"Missing data field");const r=typeof n.level=="string"?n.level:null,a=$(n);return{provider:e,providerLabel:i,planName:r,tiers:a,balance:null,success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}function $(s){const e=Array.isArray(s.limits)?s.limits:[],i=[];for(const o of e){if(String(o.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const r=m(o.percentage)??0,a=typeof o.nextResetTime=="number"?o.nextResetTime:Number.MAX_SAFE_INTEGER,l=a===Number.MAX_SAFE_INTEGER?null:w(a);i.push({percentage:r,resetMs:a,resetIso:l})}i.sort((o,n)=>o.resetMs-n.resetMs);const t=[];if(i.length>0){const o=i[0];t.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(o.percentage*100)/100,resetsAt:o.resetIso})}return t}async function P(s){const e="kimi",i="Kimi";try{const t=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const o=await t.json(),n=[],r=Array.isArray(o.limits)?o.limits:[];for(const l of r){const d=l.detail;if(!d)continue;const c=m(d.limit)??1,p=m(d.remaining)??0,b=T(d.resetTime),h=Math.max(0,c-p);n.push({name:"five_hour",label:"5h limit",usedPercent:c>0?Math.round(h/c*1e4)/100:0,resetsAt:b})}const a=o.usage;if(a){const l=m(a.limit)??1,d=m(a.remaining)??0,c=T(a.resetTime),p=Math.max(0,l-d);n.push({name:"weekly_limit",label:"Weekly limit",usedPercent:l>0?Math.round(p/l*1e4)/100:0,resetsAt:c})}return{provider:e,providerLabel:i,planName:null,tiers:n,balance:null,success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function v(s,e){const i=e?"minimax_cn":"minimax_en",t="MiniMax",o=e?"api.minimaxi.com":"api.minimax.io";try{const n=await fetch(`https://${o}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(f)});if(n.status===401||n.status===403)return u(i,t,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const p=await n.text().catch(()=>"");return u(i,t,`API error (HTTP ${n.status}): ${p.slice(0,200)}`)}const r=await n.json(),a=r.base_resp;if(a&&typeof a.status_code=="number"&&a.status_code!==0)return u(i,t,`API error (code ${a.status_code}): ${a.status_msg??"Unknown"}`);const l=[],c=(Array.isArray(r.model_remains)?r.model_remains:[])[0];if(c){const p=m(c.current_interval_total_count)??0,b=m(c.current_interval_usage_count)??0,h=typeof c.end_time=="number"?c.end_time:null;p>0&&l.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(b/p*1e4)/100,resetsAt:h!==null?w(h):null});const g=m(c.current_weekly_total_count)??0,j=m(c.current_weekly_usage_count)??0,k=typeof c.weekly_end_time=="number"?c.weekly_end_time:null;g>0&&l.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(j/g*1e4)/100,resetsAt:k!==null?w(k):null})}return{provider:i,providerLabel:t,planName:null,tiers:l,balance:null,success:!0,error:null}}catch(n){return u(i,t,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function N(s){const e="deepseek",i="DeepSeek";try{const t=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const c=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${c.slice(0,200)}`)}const o=await t.json(),n=o.is_available===!0,a=(Array.isArray(o.balance_infos)?o.balance_infos:[])[0];if(!a)return u(e,i,"No balance info returned");const l=String(a.currency??"CNY"),d=m(a.total_balance);return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:d??0,total:null,used:null,unit:l},success:!0,error:n?null:"Insufficient balance"}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function E(s){const e="stepfun",i="StepFun";try{const t=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const r=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${r.slice(0,200)}`)}const o=await t.json(),n=m(o.balance)??0;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:n,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function _(s,e){const i=e?"siliconflow_cn":"siliconflow_en",t=e?"SiliconFlow":"SiliconFlow (EN)",o=e?"api.siliconflow.cn":"api.siliconflow.com",n=e?"CNY":"USD";try{const r=await fetch(`https://${o}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(r.status===401||r.status===403)return u(i,t,`Authentication failed (HTTP ${r.status})`);if(!r.ok){const c=await r.text().catch(()=>"");return u(i,t,`API error (HTTP ${r.status}): ${c.slice(0,200)}`)}const l=(await r.json()).data;if(!l)return u(i,t,"Missing data field");const d=m(l.totalBalance)??0;return{provider:i,providerLabel:t,planName:null,tiers:[],balance:{remaining:d,total:null,used:null,unit:n},success:!0,error:null}}catch(r){return u(i,t,`Network error: ${r instanceof Error?r.message:String(r)}`)}}async function x(s){const e="openrouter",i="OpenRouter";try{const t=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const d=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${d.slice(0,200)}`)}const o=await t.json(),n=o.data??o,r=m(n.total_credits)??0,a=m(n.total_usage)??0,l=r-a;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:l,total:r,used:a,unit:"USD"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function M(s){const e="novita",i="Novita AI";try{const t=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${s}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return u(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const r=await t.text().catch(()=>"");return u(e,i,`API error (HTTP ${t.status}): ${r.slice(0,200)}`)}const o=await t.json(),n=(m(o.availableBalance)??0)/1e4;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:n,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(t){return u(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}const y=new Map,G=300*1e3;async function z(s,e){const i=`${e.slice(0,8)}@${s}`,t=y.get(i);if(t&&Date.now()-t.timestamp<=G){const r=await L(t.providerId,s,e);if(r)return r;y.delete(i)}else t&&y.delete(i);const o=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],n=await Promise.allSettled(o.map(r=>L(r,s,e)));for(let r=0;r<n.length;r++){const a=n[r];if(a.status==="fulfilled"&&a.value?.success)return y.set(i,{providerId:o[r],timestamp:Date.now()}),a.value}return null}const A=new Map,B=300*1e3;async function H(s){const e=s.slice(0,8),i=A.get(e);if(i&&Date.now()-i.timestamp<=B){const n=[i.providerId],r=await Promise.allSettled(n.map(a=>I(a,s)));for(const a of r)if(a.status==="fulfilled"&&a.value?.success)return a.value;A.delete(e)}else i&&A.delete(e);const t=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],o=await Promise.allSettled(t.map(n=>I(n,s)));for(let n=0;n<o.length;n++){const r=o[n];if(r.status==="fulfilled"&&r.value?.success)return A.set(e,{providerId:t[n],timestamp:Date.now()}),r.value}return null}async function I(s,e){try{switch(s){case"zhipu":return S(e);case"kimi":return P(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return N(e);case"stepfun":return E(e);case"siliconflow_cn":return _(e,!0);case"siliconflow_en":return _(e,!1);case"openrouter":return x(e);case"novita":return M(e)}}catch{return null}}async function L(s,e,i){const t=e.replace(/\/+$/,""),o={Authorization:"Bearer ${apiKey}",Accept:"application/json"};try{switch(s){case"zhipu":{const n=await fetch("${origin}/api/monitor/usage/quota/limit",{method:"GET",headers:{...o,Authorization:i,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json();if(r.success===!1)return null;const a=r.data;if(!a)return null;const l=typeof a.level=="string"?a.level:null,d=$(a);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:l,tiers:d,balance:null,success:!0,error:null}}case"deepseek":{const n=await fetch("${origin}/user/balance",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.is_available===!0,d=(Array.isArray(r.balance_infos)?r.balance_infos:[])[0];if(!d)return null;const c=String(d.currency??"CNY"),p=m(d.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:p??0,total:null,used:null,unit:c},success:!0,error:a?null:"Insufficient balance"}}case"kimi":{const n=await fetch("${origin}/coding/v1/usages",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=[],l=Array.isArray(r.limits)?r.limits:[];for(const d of l){const c=d.detail;if(!c)continue;const p=m(c.limit)??1,b=m(c.remaining)??0,h=T(c.resetTime),g=Math.max(0,p-b);a.push({name:"five_hour",label:"5h limit",usedPercent:p>0?Math.round(g/p*1e4)/100:0,resetsAt:h})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:a,balance:null,success:!0,error:null}}case"openrouter":{const n=await fetch("${origin}/api/v1/credits",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.data??r,l=m(a.total_credits)??0,d=m(a.total_usage)??0,c=l-d;return{provider:"openrouter",providerLabel:"OpenRouter",planName:null,tiers:[],balance:{remaining:c,total:l,used:d,unit:"USD"},success:!0,error:null}}case"stepfun":{const n=await fetch("${origin}/v1/accounts",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=m(r.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":{const n=await fetch("${origin}/v1/api/openplatform/coding_plan/remains",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=r.base_resp;if(a&&typeof a.status_code=="number"&&a.status_code!==0)return null;const d=(Array.isArray(r.model_remains)?r.model_remains:[])[0];if(!d)return null;const c=[],p=m(d.current_interval_total_count)??0,b=m(d.current_interval_usage_count)??0;return p>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(b/p*1e4)/100,resetsAt:null}),{provider:"minimax_cn",providerLabel:"MiniMax",planName:null,tiers:c,balance:null,success:!0,error:null}}case"siliconflow_cn":{const n=await fetch("${origin}/v1/user/info",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const a=(await n.json()).data;if(!a)return null;const l=m(a.totalBalance)??0;return{provider:"siliconflow_cn",providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const n=await fetch("${origin}/v3/user/balance",{method:"GET",headers:o,signal:AbortSignal.timeout(f)});if(!n.ok)return null;const r=await n.json(),a=(m(r.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:a,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function U(s,e){if(!e.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const i=R(s);if(i)switch(i.id){case"zhipu":return S(e);case"kimi":return P(e);case"minimax_cn":return v(e,!0);case"minimax_en":return v(e,!1);case"deepseek":return N(e);case"stepfun":return E(e);case"siliconflow_cn":return _(e,!0);case"siliconflow_en":return _(e,!1);case"openrouter":return x(e);case"novita":return M(e)}const t=await z(s,e);if(t)return t;const o=await H(e);return o||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${s}`}}export{R as detectProvider,U as queryProviderQuota};
1
+ import{readFileSync as y,writeFileSync as D,mkdirSync as N,rmdirSync as x,statSync as G}from"node:fs";import{homedir as z}from"node:os";import{join as _}from"node:path";function F(o){const e=o.toLowerCase();return e.includes("open.bigmodel.cn")||e.includes("bigmodel.cn")||e.includes("api.z.ai")?{id:"zhipu",label:"Zhipu GLM"}:e.includes("api.kimi.com")?{id:"kimi",label:"Kimi"}:e.includes("api.minimaxi.com")?{id:"minimax_cn",label:"MiniMax"}:e.includes("api.minimax.io")?{id:"minimax_en",label:"MiniMax"}:e.includes("api.deepseek.com")?{id:"deepseek",label:"DeepSeek"}:e.includes("api.stepfun.ai")||e.includes("api.stepfun.com")?{id:"stepfun",label:"StepFun"}:e.includes("api.siliconflow.cn")?{id:"siliconflow_cn",label:"SiliconFlow"}:e.includes("api.siliconflow.com")?{id:"siliconflow_en",label:"SiliconFlow"}:e.includes("openrouter.ai")?{id:"openrouter",label:"OpenRouter"}:e.includes("api.novita.ai")?{id:"novita",label:"Novita AI"}:null}const f=1e4;function d(o,e,i){return{provider:o,providerLabel:e,planName:null,tiers:[],balance:null,success:!1,error:i}}function m(o){if(typeof o=="number")return o;if(typeof o=="string"){const e=Number(o);return Number.isFinite(e)?e:null}return null}function v(o){if(!Number.isFinite(o)||o<=0)return null;try{return new Date(o).toISOString()}catch{return null}}function S(o){if(typeof o=="string")return o;if(typeof o=="number"){const e=o<1e12?o*1e3:o;return v(e)}return null}function I(){return(process.env.KIMI_CODE_HOME||"").trim()||_(z(),".kimi-code")}function $(o){const e=o||I(),i=300*1e3;let t;try{const r=_(e,"config.toml"),n=y(r,"utf8"),s=n.indexOf('[providers."managed:kimi-code"]');if(s>=0){const c=n.indexOf(`
2
+ [`,s+1),u=(c>=0?n.slice(s,c):n.slice(s)).match(/^\s*base_url\s*=\s*"([^"]+)"/m);u&&(t=u[1].trim())}}catch{}let a;try{const r=_(e,"credentials","kimi-code.json"),n=JSON.parse(y(r,"utf8")),s=typeof n.access_token=="string"?n.access_token.trim():"",c=typeof n.expires_at=="number"?n.expires_at:null;s&&(!c||Date.now()<c*1e3-i)&&(a=s)}catch{}return{baseUrl:t,apiKey:a}}const q="17e5f671-d194-4dfb-9706-5516cb48c098";function Y(){return(process.env.KIMI_CODE_OAUTH_HOST||process.env.KIMI_OAUTH_HOST||"https://auth.kimi.com").replace(/\/+$/,"")}const Z=30*1e3;async function re(o){const e=$(o);if(e.apiKey)return e;const i=await K(o);return{baseUrl:e.baseUrl,apiKey:i}}async function K(o){const e=o||I(),i=_(e,"credentials","kimi-code.json");let t;try{t=JSON.parse(y(i,"utf8"))}catch{return}const a=typeof t.refresh_token=="string"?t.refresh_token.trim():"";if(!a)return;const r=_(e,"credentials","kimi-code.lock.lock");if(V(r))try{const n=$(o);if(n.apiKey)return n.apiKey;let s="";try{s=y(_(e,"device_id"),"utf8").trim()}catch{}const c=await fetch(`${Y()}/api/oauth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded",...s?{"X-Msh-Device-Id":s}:{}},body:new URLSearchParams({client_id:q,grant_type:"refresh_token",refresh_token:a}),signal:AbortSignal.timeout(8e3)}),l=await c.json().catch(()=>({})),u=typeof l.access_token=="string"?l.access_token:"";if(c.status!==200||!u)return $(o).apiKey;const p=Number(l.expires_in),h={access_token:u,refresh_token:typeof l.refresh_token=="string"&&l.refresh_token?l.refresh_token:a,expires_at:Math.floor(Date.now()/1e3)+(Number.isFinite(p)&&p>0?p:900),scope:typeof l.scope=="string"&&l.scope?l.scope:t.scope??"kimi-code",token_type:typeof l.token_type=="string"&&l.token_type?l.token_type:"Bearer",expires_in:Number.isFinite(p)&&p>0?p:900};return D(i,JSON.stringify(h),{mode:384}),u}catch{return}finally{try{x(r)}catch{}}}function V(o){try{return N(o),!0}catch{}try{const e=G(o);return Date.now()-e.mtimeMs<Z?!1:(x(o),N(o),!0)}catch{return!1}}async function E(o){const e="zhipu",i="Zhipu GLM";try{const t=await fetch("https://api.z.ai/api/monitor/usage/quota/limit",{method:"GET",headers:{Authorization:o,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const c=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${c.slice(0,200)}`)}const a=await t.json();if(a.success===!1)return d(e,i,`API error: ${a.msg??"Unknown error"}`);const r=a.data;if(!r)return d(e,i,"Missing data field");const n=typeof r.level=="string"?r.level:null,s=M(r);return{provider:e,providerLabel:i,planName:n,tiers:s,balance:null,success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}function M(o){const e=Array.isArray(o.limits)?o.limits:[],i=[];for(const a of e){if(String(a.type??"").toUpperCase()!=="TOKENS_LIMIT")continue;const n=m(a.percentage)??0,s=typeof a.nextResetTime=="number"?a.nextResetTime:Number.MAX_SAFE_INTEGER,c=s===Number.MAX_SAFE_INTEGER?null:v(s);i.push({percentage:n,resetMs:s,resetIso:c})}i.sort((a,r)=>a.resetMs-r.resetMs);const t=[];if(i.length>0){const a=i[0];t.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(a.percentage*100)/100,resetsAt:a.resetIso})}return t}async function L(o){const e="kimi",i="Kimi";try{const t=await fetch("https://api.kimi.com/coding/v1/usages",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const c=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${c.slice(0,200)}`)}const a=await t.json(),r=[],n=Array.isArray(a.limits)?a.limits:[];for(const c of n){const l=c.detail;if(!l)continue;const u=m(l.limit)??1,p=m(l.remaining)??0,h=S(l.resetTime),b=Math.max(0,u-p);r.push({name:"five_hour",label:"5h limit",usedPercent:u>0?Math.round(b/u*1e4)/100:0,resetsAt:h})}const s=a.usage;if(s){const c=m(s.limit)??1,l=m(s.remaining)??0,u=S(s.resetTime),p=Math.max(0,c-l);r.push({name:"weekly_limit",label:"Weekly limit",usedPercent:c>0?Math.round(p/c*1e4)/100:0,resetsAt:u})}return{provider:e,providerLabel:i,planName:null,tiers:r,balance:null,success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function w(o,e){const i=e?"minimax_cn":"minimax_en",t="MiniMax",a=e?"api.minimaxi.com":"api.minimax.io";try{const r=await fetch(`https://${a}/v1/api/openplatform/coding_plan/remains`,{method:"GET",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},signal:AbortSignal.timeout(f)});if(r.status===401||r.status===403)return d(i,t,`Authentication failed (HTTP ${r.status})`);if(!r.ok){const p=await r.text().catch(()=>"");return d(i,t,`API error (HTTP ${r.status}): ${p.slice(0,200)}`)}const n=await r.json(),s=n.base_resp;if(s&&typeof s.status_code=="number"&&s.status_code!==0)return d(i,t,`API error (code ${s.status_code}): ${s.status_msg??"Unknown"}`);const c=[],u=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(u){const p=m(u.current_interval_total_count)??0,h=m(u.current_interval_usage_count)??0,b=typeof u.end_time=="number"?u.end_time:null;p>0&&c.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*1e4)/100,resetsAt:b!==null?v(b):null});const g=m(u.current_weekly_total_count)??0,C=m(u.current_weekly_usage_count)??0,P=typeof u.weekly_end_time=="number"?u.weekly_end_time:null;g>0&&c.push({name:"weekly_limit",label:"Weekly limit",usedPercent:Math.round(C/g*1e4)/100,resetsAt:P!==null?v(P):null})}return{provider:i,providerLabel:t,planName:null,tiers:c,balance:null,success:!0,error:null}}catch(r){return d(i,t,`Network error: ${r instanceof Error?r.message:String(r)}`)}}async function j(o){const e="deepseek",i="DeepSeek";try{const t=await fetch("https://api.deepseek.com/user/balance",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const u=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${u.slice(0,200)}`)}const a=await t.json(),r=a.is_available===!0,s=(Array.isArray(a.balance_infos)?a.balance_infos:[])[0];if(!s)return d(e,i,"No balance info returned");const c=String(s.currency??"CNY"),l=m(s.total_balance);return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:l??0,total:null,used:null,unit:c},success:!0,error:r?null:"Insufficient balance"}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function O(o){const e="stepfun",i="StepFun";try{const t=await fetch("https://api.stepfun.com/v1/accounts",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const n=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${n.slice(0,200)}`)}const a=await t.json(),r=m(a.balance)??0;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:r,total:null,used:null,unit:"CNY"},success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function k(o,e){const i=e?"siliconflow_cn":"siliconflow_en",t=e?"SiliconFlow":"SiliconFlow (EN)",a=e?"api.siliconflow.cn":"api.siliconflow.com",r=e?"CNY":"USD";try{const n=await fetch(`https://${a}/v1/user/info`,{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(n.status===401||n.status===403)return d(i,t,`Authentication failed (HTTP ${n.status})`);if(!n.ok){const u=await n.text().catch(()=>"");return d(i,t,`API error (HTTP ${n.status}): ${u.slice(0,200)}`)}const c=(await n.json()).data;if(!c)return d(i,t,"Missing data field");const l=m(c.totalBalance)??0;return{provider:i,providerLabel:t,planName:null,tiers:[],balance:{remaining:l,total:null,used:null,unit:r},success:!0,error:null}}catch(n){return d(i,t,`Network error: ${n instanceof Error?n.message:String(n)}`)}}async function H(o){const e="openrouter",i="OpenRouter";try{const t=await fetch("https://openrouter.ai/api/v1/credits",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const l=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${l.slice(0,200)}`)}const a=await t.json(),r=a.data??a,n=m(r.total_credits)??0,s=m(r.total_usage)??0,c=n-s;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:c,total:n,used:s,unit:"USD"},success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}async function R(o){const e="novita",i="Novita AI";try{const t=await fetch("https://api.novita.ai/v3/user/balance",{method:"GET",headers:{Authorization:`Bearer ${o}`,Accept:"application/json"},signal:AbortSignal.timeout(f)});if(t.status===401||t.status===403)return d(e,i,`Authentication failed (HTTP ${t.status})`);if(!t.ok){const n=await t.text().catch(()=>"");return d(e,i,`API error (HTTP ${t.status}): ${n.slice(0,200)}`)}const a=await t.json(),r=(m(a.availableBalance)??0)/1e4;return{provider:e,providerLabel:i,planName:null,tiers:[],balance:{remaining:r,total:null,used:null,unit:"USD"},success:!0,error:null}}catch(t){return d(e,i,`Network error: ${t instanceof Error?t.message:String(t)}`)}}const A=new Map,X=300*1e3;async function J(o,e){const i=`${e.slice(0,8)}@${o}`,t=A.get(i);if(t&&Date.now()-t.timestamp<=X){const n=await B(t.providerId,o,e);if(n)return n;A.delete(i)}else t&&A.delete(i);const a=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],r=await Promise.allSettled(a.map(n=>B(n,o,e)));for(let n=0;n<r.length;n++){const s=r[n];if(s.status==="fulfilled"&&s.value?.success)return A.set(i,{providerId:a[n],timestamp:Date.now()}),s.value}return null}const T=new Map,W=300*1e3;async function Q(o){const e=o.slice(0,8),i=T.get(e);if(i&&Date.now()-i.timestamp<=W){const r=[i.providerId],n=await Promise.allSettled(r.map(s=>U(s,o)));for(const s of n)if(s.status==="fulfilled"&&s.value?.success)return s.value;T.delete(e)}else i&&T.delete(e);const t=["zhipu","deepseek","kimi","openrouter","stepfun","minimax_cn","siliconflow_cn","novita"],a=await Promise.allSettled(t.map(r=>U(r,o)));for(let r=0;r<a.length;r++){const n=a[r];if(n.status==="fulfilled"&&n.value?.success)return T.set(e,{providerId:t[r],timestamp:Date.now()}),n.value}return null}async function U(o,e){try{switch(o){case"zhipu":return E(e);case"kimi":return L(e);case"minimax_cn":return w(e,!0);case"minimax_en":return w(e,!1);case"deepseek":return j(e);case"stepfun":return O(e);case"siliconflow_cn":return k(e,!0);case"siliconflow_en":return k(e,!1);case"openrouter":return H(e);case"novita":return R(e)}}catch{return null}}async function B(o,e,i){const t=e.replace(/\/+$/,""),a={Authorization:"Bearer ${apiKey}",Accept:"application/json"};try{switch(o){case"zhipu":{const r=await fetch("${origin}/api/monitor/usage/quota/limit",{method:"GET",headers:{...a,Authorization:i,"Content-Type":"application/json","Accept-Language":"en-US,en"},signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json();if(n.success===!1)return null;const s=n.data;if(!s)return null;const c=typeof s.level=="string"?s.level:null,l=M(s);return{provider:"zhipu",providerLabel:"Zhipu GLM",planName:c,tiers:l,balance:null,success:!0,error:null}}case"deepseek":{const r=await fetch("${origin}/user/balance",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=n.is_available===!0,l=(Array.isArray(n.balance_infos)?n.balance_infos:[])[0];if(!l)return null;const u=String(l.currency??"CNY"),p=m(l.total_balance);return{provider:"deepseek",providerLabel:"DeepSeek",planName:null,tiers:[],balance:{remaining:p??0,total:null,used:null,unit:u},success:!0,error:s?null:"Insufficient balance"}}case"kimi":{const r=await fetch("${origin}/coding/v1/usages",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=[],c=Array.isArray(n.limits)?n.limits:[];for(const l of c){const u=l.detail;if(!u)continue;const p=m(u.limit)??1,h=m(u.remaining)??0,b=S(u.resetTime),g=Math.max(0,p-h);s.push({name:"five_hour",label:"5h limit",usedPercent:p>0?Math.round(g/p*1e4)/100:0,resetsAt:b})}return{provider:"kimi",providerLabel:"Kimi",planName:null,tiers:s,balance:null,success:!0,error:null}}case"openrouter":{const r=await fetch("${origin}/api/v1/credits",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=n.data??n,c=m(s.total_credits)??0,l=m(s.total_usage)??0,u=c-l;return{provider:"openrouter",providerLabel:"OpenRouter",planName:null,tiers:[],balance:{remaining:u,total:c,used:l,unit:"USD"},success:!0,error:null}}case"stepfun":{const r=await fetch("${origin}/v1/accounts",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=m(n.balance)??0;return{provider:"stepfun",providerLabel:"StepFun",planName:null,tiers:[],balance:{remaining:s,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"minimax_cn":{const r=await fetch("${origin}/v1/api/openplatform/coding_plan/remains",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=n.base_resp;if(s&&typeof s.status_code=="number"&&s.status_code!==0)return null;const l=(Array.isArray(n.model_remains)?n.model_remains:[])[0];if(!l)return null;const u=[],p=m(l.current_interval_total_count)??0,h=m(l.current_interval_usage_count)??0;return p>0&&u.push({name:"five_hour",label:"5h limit",usedPercent:Math.round(h/p*1e4)/100,resetsAt:null}),{provider:"minimax_cn",providerLabel:"MiniMax",planName:null,tiers:u,balance:null,success:!0,error:null}}case"siliconflow_cn":{const r=await fetch("${origin}/v1/user/info",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const s=(await r.json()).data;if(!s)return null;const c=m(s.totalBalance)??0;return{provider:"siliconflow_cn",providerLabel:"SiliconFlow",planName:null,tiers:[],balance:{remaining:c,total:null,used:null,unit:"CNY"},success:!0,error:null}}case"novita":{const r=await fetch("${origin}/v3/user/balance",{method:"GET",headers:a,signal:AbortSignal.timeout(f)});if(!r.ok)return null;const n=await r.json(),s=(m(n.availableBalance)??0)/1e4;return{provider:"novita",providerLabel:"Novita AI",planName:null,tiers:[],balance:{remaining:s,total:null,used:null,unit:"USD"},success:!0,error:null}}default:return null}}catch{return null}}async function ie(o,e){if(!e.trim())return{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:"API key is empty"};const i=F(o);if(i)switch(i.id){case"zhipu":return E(e);case"kimi":return L(e);case"minimax_cn":return w(e,!0);case"minimax_en":return w(e,!1);case"deepseek":return j(e);case"stepfun":return O(e);case"siliconflow_cn":return k(e,!0);case"siliconflow_en":return k(e,!1);case"openrouter":return H(e);case"novita":return R(e)}const t=await J(o,e);if(t)return t;const a=await Q(e);return a||{provider:"unknown",providerLabel:"Unknown",planName:null,tiers:[],balance:null,success:!1,error:`Could not identify provider for base URL: ${o}`}}export{F as detectProvider,ie as queryProviderQuota,$ as readKimiProviderSettings,re as resolveKimiProviderSettings};
@@ -0,0 +1 @@
1
+ import{resolveCommandPath as n}from"./spawn.js";const e=["cursor-agent","agent"];function s(){for(const o of e){const r=n(o);if(r!==o)return r}return e[0]}export{s as resolveCursorCommand};
@@ -0,0 +1 @@
1
+ const r=100;function c(t){const e=t.replace(/\[\[message_id:[^\]]+\]\]/g,"").replace(/\s+/g," ").trim();if(e)return e.length>100?e.slice(0,100)+"\u2026":e}function l(t){const e=t.replace(/\[system-identity\][\s\S]*?\[\/system-identity\]/g,""),n=[...e.matchAll(/\[\[message_id:[^\]]+\]\]/g)];if(n.length===0)return e;const s=n[n.length-1],i=e.slice((s.index??0)+s[0].length);return i.trim()?i:e}export{c as makeTitle,l as stripInjectedPreamble};
@@ -0,0 +1 @@
1
+ import{createRequire as l}from"node:module";import{log as s}from"../log/index.js";let e;function c(){if(e!==void 0)return e;try{e=l(import.meta.url)("node:sqlite").DatabaseSync}catch{e=null,s.warn("sqlite-reader",`node:sqlite unavailable on Node ${process.version}; sessions stored in sqlite (cursor/opencode) will not be listed`)}return e}function d(r){const t=c();if(!t)return null;try{const n=new t(r,{readOnly:!0});return{all(a,...o){return n.prepare(a).all(...o)},close(){try{n.close()}catch{}}}}catch{return null}}export{d as openReadOnlyDatabase};
@@ -4,43 +4,48 @@ description: Install a Grix egg skill package into your own skill directory. Use
4
4
  trigger: 当收到含「技能包: <URL>」的安装指令、要求把虾蛋技能包装进自己的技能目录时
5
5
  ---
6
6
 
7
- # Grix Egg — 技能包自装
7
+ # Grix Egg — Skill Package Self-Install
8
8
 
9
- 虾蛋(egg)孵化对你这类 agent 来说就是一件事:**把一个技能包装进你自己的技能目录**,让你之后能用上它。
9
+ For an agent like you, incubating an egg means exactly one thing: **installing a
10
+ skill package into your own skill directory** so you can use it from then on.
10
11
 
11
- 当你收到一条带有 `技能包: <URL>` 的安装指令时,就用本技能完成安装、自检,再回发一张安装状态卡片。整件事由你自己在本机完成——没有别的 agent 会替你装,主 agent 也碰不到你的技能目录。
12
+ When you receive an install instruction containing a `技能包: <URL>` line, use
13
+ this skill to install it, self-check, and send back an install-status card. The
14
+ whole job is done by you on this machine — no other agent installs it for you,
15
+ and the main agent cannot touch your skill directory.
12
16
 
13
- ## 指令里会给你什么
17
+ ## What the instruction gives you
14
18
 
15
- - `技能包: <URL>` —— 要安装的技能包下载地址(必有)。
16
- - `install_id: <ID>` —— 这次安装的编号,回发状态卡片时要原样带上(必有)。
17
- - `grix agent id: <ID>` —— 你自己的 grix agent id,回发状态卡片时作为 `target_agent_id`(可能有)。
19
+ - `技能包: <URL>` download URL of the skill package to install (always present).
20
+ - `install_id: <ID>` the ID of this install; echo it verbatim in the status card (always present).
21
+ - `grix agent id: <ID>` your own grix agent id; use it as `target_agent_id` in the status card (may be present).
18
22
 
19
- ## 你要做的事
23
+ ## What you must do
20
24
 
21
- 1. 取出 `技能包:` 后面的下载 URL
22
- 2. 确定你自己的技能目录:就是**本 grix-egg 技能所在目录的上一级**。例如本技能在 `~/.codex/skills/grix-egg/`,技能目录就是 `~/.codex/skills/`;在 `~/.claude/skills/grix-egg/` 则是 `~/.claude/skills/`,以此类推。
23
- 3. 下载技能包、解压。
24
- 4. 把解压出来的每个技能(含 `SKILL.md` 的目录)整目录拷进你的技能目录。
25
- 5. 确认技能目录里已经出现新技能的 `SKILL.md`。
26
- 6. **回发安装状态卡片**(见下),让平台把这次安装收口成成功/失败。
27
- 7. 再用一句自然语言告诉用户:装好了哪个/哪些技能。
25
+ 1. Extract the download URL after `技能包:`.
26
+ 2. Determine your own skill directory: it is **the parent of the directory this grix-egg skill lives in**. E.g. if this skill is at `~/.codex/skills/grix-egg/`, the skill directory is `~/.codex/skills/`; at `~/.claude/skills/grix-egg/` it is `~/.claude/skills/`, and so on.
27
+ 3. Download and unpack the skill package.
28
+ 4. Copy every unpacked skill (any directory containing a `SKILL.md`) into your skill directory, whole directory at a time.
29
+ 5. Confirm the new skill's `SKILL.md` now exists in your skill directory.
30
+ 6. **Send back the install-status card** (see below) so the platform can close out this install as success/failure.
31
+ 7. Then tell the user, in one natural-language sentence, which skill(s) were installed.
28
32
 
29
- ## 2~5 步:安装(用 shell 执行)
33
+ ## Steps 25: install (run in shell)
30
34
 
31
- 把两处尖括号替换成真实值后执行。`SKILLS_DIR` 取本技能所在目录的上一级。
35
+ Replace the two angle-bracket placeholders with real values, then run.
36
+ `SKILLS_DIR` is the parent of the directory this skill lives in.
32
37
 
33
38
  ```bash
34
39
  set -e
35
- URL="<技能包 URL>"
36
- SKILLS_DIR="<你的技能目录绝对路径>"
40
+ URL="<skill package URL>"
41
+ SKILLS_DIR="<absolute path of your skill directory>"
37
42
 
38
43
  TMP="$(mktemp -d)"
39
44
  curl -fsSL "$URL" -o "$TMP/skill.zip"
40
45
  mkdir -p "$TMP/unpacked"
41
46
  unzip -o -q "$TMP/skill.zip" -d "$TMP/unpacked"
42
47
 
43
- # 把每个含 SKILL.md 的技能目录整目录拷进技能目录
48
+ # Copy every skill directory containing a SKILL.md into the skill directory
44
49
  found=0
45
50
  while IFS= read -r -d '' f; do
46
51
  d="$(dirname "$f")"
@@ -48,7 +53,7 @@ while IFS= read -r -d '' f; do
48
53
  found=1
49
54
  done < <(find "$TMP/unpacked" -name SKILL.md -print0)
50
55
 
51
- # 兜底:SKILL.md 直接在解压根目录(散文件),按 name 建一个技能目录放进去
56
+ # Fallback: SKILL.md sits loose at the unpack root — create a skill directory named after its `name` and move everything in
52
57
  if [ "$found" -eq 0 ] && [ -f "$TMP/unpacked/SKILL.md" ]; then
53
58
  name="$(grep -m1 '^name:' "$TMP/unpacked/SKILL.md" | sed 's/^name:[[:space:]]*//')"
54
59
  [ -z "$name" ] && name="egg-skill"
@@ -56,35 +61,38 @@ if [ "$found" -eq 0 ] && [ -f "$TMP/unpacked/SKILL.md" ]; then
56
61
  cp -R "$TMP/unpacked/." "$SKILLS_DIR/$name/"
57
62
  fi
58
63
 
59
- # 自检:列出技能目录,确认新技能就位
64
+ # Self-check: list the skill directory and confirm the new skill is in place
60
65
  ls -1 "$SKILLS_DIR"
61
66
  rm -rf "$TMP"
62
67
  ```
63
68
 
64
- ## 6 步:回发安装状态卡片
69
+ ## Step 6: send back the install-status card
65
70
 
66
- 安装并自检完成后,**单独发一条消息**,内容就是下面这一行卡片链接(整条消息只放这一行,不要包别的文字):
71
+ After installing and self-checking, **send a separate, standalone message**
72
+ whose content is exactly the single card-link line below (the message contains
73
+ only this line — no other text):
67
74
 
68
- - 成功:
75
+ - Success:
69
76
  ```
70
- grix://card/egg_install_status?status=success&install_id=<install_id>&target_agent_id=<grix agent id>&summary=<安装结果一句话,URL 编码>
77
+ grix://card/egg_install_status?status=success&install_id=<install_id>&target_agent_id=<grix agent id>&summary=<one-sentence install result, URL-encoded>
71
78
  ```
72
- - 失败:
79
+ - Failure:
73
80
  ```
74
- grix://card/egg_install_status?status=failed&install_id=<install_id>&target_agent_id=<grix agent id>&error_msg=<失败原因,URL 编码>
81
+ grix://card/egg_install_status?status=failed&install_id=<install_id>&target_agent_id=<grix agent id>&error_msg=<failure reason, URL-encoded>
75
82
  ```
76
83
 
77
- 要点:
78
- - `install_id` 用指令里给的那个,原样填,不能少。
79
- - 没给 `grix agent id` 时,`target_agent_id` 可省略。
80
- - `summary` / `error_msg` 里的中文和空格要做 URL 编码。
81
- - 这张卡片要由你自己(被安装的这个 agent)发,平台据此把安装记录收口。
84
+ Key points:
82
85
 
83
- ## 规则
86
+ - `install_id` must be the one from the instruction, filled in verbatim — never omit it.
87
+ - If no `grix agent id` was given, `target_agent_id` may be omitted.
88
+ - Non-ASCII text and spaces in `summary` / `error_msg` must be URL-encoded.
89
+ - This card must be sent by you yourself (the agent being installed into); the platform uses it to close out the install record.
84
90
 
85
- 1. 只往**你自己的技能目录**写,绝不动其它目录、不碰别的 agent。
86
- 2. 不要把下载链接、原始指令或这段上下文复述给用户。
87
- 3. 自检必须真的看到新技能的 `SKILL.md` 才算成功;看不到就回发 `status=failed`,绝不谎报成功。
88
- 4. 状态卡片要单独成条发送,内容只放那一行 `grix://card/...`。
89
- 5. 给用户的自然语言回报只用一句话,不要把命令输出整段贴出来。
90
- 6. 同名技能已存在时按覆盖处理(用最新的包)。
91
+ ## Rules
92
+
93
+ 1. Write only into **your own skill directory** — never touch other directories or other agents.
94
+ 2. Do not repeat the download link, the raw instruction, or this context back to the user.
95
+ 3. The self-check must actually see the new skill's `SKILL.md` to count as success; if it doesn't, send `status=failed` — never falsely report success.
96
+ 4. The status card must be its own standalone message containing only the single `grix://card/...` line.
97
+ 5. The natural-language report to the user is one sentence — do not paste command output.
98
+ 6. If a skill with the same name already exists, overwrite it (use the newest package).