grix-connector 3.21.0 → 3.23.0
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.
- package/dist/adapter/claude/session-history.js +2 -2
- package/dist/adapter/codewhale/session-history.js +1 -0
- package/dist/adapter/codex/session-history.js +1 -0
- package/dist/adapter/opencode/opencode-adapter.js +6 -5
- package/dist/adapter/opencode/opencode-transport.js +3 -3
- package/dist/adapter/opencode/session-history.js +8 -0
- package/dist/adapter/pi/session-history.js +2 -0
- package/dist/adapter/shared/session-history.js +1 -1
- package/dist/bridge/bridge.js +9 -9
- package/dist/mcp/stream-http/security.js +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
`)}return t==null?"":String(t)}function
|
|
1
|
+
import S from"node:os";import{join as _,resolve as g,sep as x}from"node:path";import{SESSION_HISTORY_ERROR_CODES as m,SessionHistoryError as h,readJsonlHistoryPage as A,registerSessionHistoryReader as j}from"../shared/session-history.js";import{encodeProjectPath as T,resolveSessionJsonlPath as E}from"./usage-parser.js";const k=/^[A-Za-z0-9][A-Za-z0-9_-]*$/,P=new Set(["summary","custom-title","ai-title","file-history-snapshot","queue-operation"]),b=["<local-command-caveat","<command-name","<local-command-stdout"];function y(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function w(t){const o=t.trimStart();return b.some(s=>o.startsWith(s))}function O(t){if(typeof t=="string")return t;if(Array.isArray(t)){const o=[];for(const s of t)y(s)&&s.type==="text"&&typeof s.text=="string"&&o.push(s.text);return o.join(`
|
|
2
|
+
`)}return t==null?"":String(t)}function R(t,o){if(t.isSidechain===!0||t.isMeta===!0)return[];const s=typeof t.type=="string"?t.type:"";if(P.has(s))return[];const a=typeof t.uuid=="string"&&t.uuid?t.uuid:o,f=typeof t.parentUuid=="string"?t.parentUuid:null,u=typeof t.timestamp=="string"?t.timestamp:"",p=y(t.message)?t.message:void 0,c=[];if(s==="user"){const i=p?.content;if(typeof i=="string")c.push({role:"user",content:i,msg_type:"text"});else if(Array.isArray(i))for(const r of i){if(!y(r))continue;const n=r;if(n.type==="text"&&typeof n.text=="string"){if(w(n.text))continue;c.push({role:"user",content:n.text,msg_type:"text"})}else if(n.type==="tool_result"){const e={};typeof n.tool_use_id=="string"&&(e.tool_use_id=n.tool_use_id),n.is_error===!0&&(e.is_error=!0),c.push({role:"tool",content:O(n.content),msg_type:"tool_result",...Object.keys(e).length>0?{extra:e}:{}})}}}else if(s==="assistant"){const i=p?.content,r=typeof p?.model=="string"?p.model:void 0;if(typeof i=="string")c.push({role:"assistant",content:i,msg_type:"text",...r?{extra:{model:r}}:{}});else if(Array.isArray(i))for(const n of i){if(!y(n))continue;const e=n;if(e.type==="text"&&typeof e.text=="string")c.push({role:"assistant",content:e.text,msg_type:"text",...r?{extra:{model:r}}:{}});else if(e.type==="thinking"&&typeof e.thinking=="string")c.push({role:"assistant",content:e.thinking,msg_type:"thinking"});else if(e.type==="tool_use"){const l={};typeof e.name=="string"&&(l.tool_name=e.name),typeof e.id=="string"&&(l.tool_use_id=e.id);let d="{}";try{d=JSON.stringify(e.input??{})}catch{}c.push({role:"assistant",content:d,msg_type:"tool_use",...Object.keys(l).length>0?{extra:l}:{}})}}}else if(s==="system")c.push({role:"system",content:String(t.content??""),msg_type:typeof t.subtype=="string"?t.subtype:"system"});else return[];return c.map((i,r)=>({native_message_id:r===0?a:`${a}#${r}`,native_parent_id:f,created_at:u,...i}))}function v(t,o){if(!k.test(t.agent_session_id))throw new h(m.invalidAgentSession,`invalid agent_session_id: ${t.agent_session_id}`);const s=o?.projectsDir??_(S.homedir(),".claude","projects"),a=o?.projectsDir?_(o.projectsDir,T(t.cwd),`${t.agent_session_id}.jsonl`):E(t.agent_session_id,t.cwd),f=g(s),u=g(a);if(u!==f&&!u.startsWith(f+x))throw new h(m.invalidAgentSession,`transcript path escapes projects dir: ${t.agent_session_id}`);return u}async function D(t,o){const s=v(t,o);return{...await A({filePath:s,cursor:t.cursor,limit:t.limit,mapLine:R,sourceLabel:"claude session transcript",logTag:"claude-session-history"}),...t.sync_run_id?{sync_run_id:t.sync_run_id}:{}}}j("claude",D);export{D as readClaudeSessionHistory};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{readFileSync as x,statSync as A}from"node:fs";import{join as p,resolve as S,sep as b}from"node:path";import{SESSION_HISTORY_ERROR_CODES as c,SessionHistoryError as d,decodeOpaqueCursor as C,encodeOpaqueCursor as $,normalizeHistoryLimit as E,registerSessionHistoryReader as I}from"../shared/session-history.js";import{resolveCodeWhaleHome as N}from"./session-scanner.js";const O=/^[A-Za-z0-9][A-Za-z0-9_-]*$/;function f(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function R(e,n){if(!O.test(e.agent_session_id))throw new d(c.invalidAgentSession,`invalid agent_session_id: ${e.agent_session_id}`);const o=p(N(n?.homeDir),"sessions"),s=p(o,`${e.agent_session_id}.json`),i=S(o),t=S(s);if(t!==i&&!t.startsWith(i+b))throw new d(c.invalidAgentSession,`session file path escapes sessions dir: ${e.agent_session_id}`);return t}function D(e){const n=C(e);if(n.v!==2||typeof n.idx!="number"||!Number.isInteger(n.idx)||n.idx<0)throw new d(c.invalidCursor,`invalid sync_history cursor: ${e}`);return n.idx}function H(e){let n;try{if(A(e).isDirectory())throw new d(c.sourceUnreadable,`codewhale session file is a directory: ${e}`);n=x(e,"utf8")}catch(s){throw s instanceof d?s:s.code==="ENOENT"?new d(c.sessionNotFound,`codewhale session file not found: ${e}`):new d(c.sourceUnreadable,`failed to read codewhale session file ${e}: ${s}`)}let o;try{o=JSON.parse(n)}catch(s){throw new d(c.sourceUnreadable,`failed to parse codewhale session file ${e}: ${s}`)}if(!f(o)||!Array.isArray(o.messages))throw new d(c.sourceUnreadable,`codewhale session file has no messages array: ${e}`);return o}function w(e,n,o,s){if(!f(e))return null;const i=e.role;if(i!=="user"&&i!=="assistant"&&i!=="tool"&&i!=="system")return null;const t=typeof e.content=="string"?e.content:e.content==null?"":String(e.content);if(!t)return null;const a=typeof e.timestamp=="string"&&e.timestamp||typeof e.created_at=="string"&&e.created_at||s;return{native_message_id:typeof e.id=="string"&&e.id?e.id:`cw-${n}-${o}`,native_parent_id:null,role:i,content:t,created_at:a,msg_type:i==="system"?"system":"text"}}async function T(e,n){const o=R(e,n),s=e.cursor?D(e.cursor):0,i=E(e.limit),t=H(o),a=f(t.metadata)?t.metadata:{},l=typeof a.created_at=="string"&&a.created_at||typeof a.updated_at=="string"&&a.updated_at||"",m=typeof t.system_prompt=="string"&&t.system_prompt.length>0,_=t.messages.length+(m?1:0);if(s>_)throw new d(c.invalidCursor,`cursor index ${s} is beyond codewhale session message count ${_}: ${o}`);if(s===_)return{messages:[],has_more:!1,next_cursor:$({v:2,idx:s}),...e.sync_run_id?{sync_run_id:e.sync_run_id}:{}};const v=r=>m?r===0?{native_message_id:`cw-${e.agent_session_id}-sys`,native_parent_id:null,role:"system",content:t.system_prompt,created_at:l,msg_type:"system"}:w(t.messages[r-1],e.agent_session_id,r-1,l):w(t.messages[r],e.agent_session_id,r,l),u=[];let g=s,h=!1;for(let r=s;r<_;r++){const y=v(r);if(u.length>=i){if(y){h=!0;break}continue}g=r+1,y&&u.push(y)}return{messages:u,has_more:h,next_cursor:$({v:2,idx:g}),...e.sync_run_id?{sync_run_id:e.sync_run_id}:{}}}I("codewhale",T);export{T as readCodeWhaleSessionHistory};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{resolve as p,sep as g}from"node:path";import{SESSION_HISTORY_ERROR_CODES as _,SessionHistoryError as f,readJsonlHistoryPage as m,registerSessionHistoryReader as x}from"../shared/session-history.js";import{resolveCodexHome as h}from"./codex-trust.js";import{findRolloutFile as S}from"./rollout-locator.js";const v=/^[A-Za-z0-9][A-Za-z0-9_-]*$/;function y(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function A(t){if(typeof t=="string")return t;if(t==null)return"";try{return JSON.stringify(t)}catch{return String(t)}}function w(t,l){if((typeof t.type=="string"?t.type:"")!=="response_item")return[];const e=y(t.payload)?t.payload:void 0;if(!e)return[];const s=typeof e.type=="string"?e.type:"",r=typeof t.timestamp=="string"?t.timestamp:"",u=typeof e.id=="string"&&e.id?e.id:l,c=[];if(s==="message"){const n=e.role==="user"?"user":e.role==="assistant"?"assistant":e.role==="developer"?"system":null;if(!n)return[];const o=e.content;if(!Array.isArray(o))return[];for(const i of o)y(i)&&(i.type==="input_text"||i.type==="output_text")&&typeof i.text=="string"&&c.push({role:n,content:i.text,msg_type:"text"})}else if(s==="function_call"||s==="custom_tool_call"){const n=typeof e.call_id=="string"&&e.call_id?e.call_id:void 0,o=typeof e.name=="string"?e.name:void 0,i=s==="function_call"?e.arguments:e.input,d={};return o&&(d.tool_name=o),n&&(d.call_id=n),c.push({role:"assistant",content:typeof i=="string"?i:"{}",msg_type:"tool_use",...Object.keys(d).length>0?{extra:d}:{}}),[{native_message_id:n??u,native_parent_id:null,created_at:r,...c[0]}]}else if(s==="function_call_output"||s==="custom_tool_call_output"){const n=typeof e.call_id=="string"&&e.call_id?e.call_id:void 0;return[{native_message_id:n?`${n}/output`:u,native_parent_id:null,role:"tool",content:A(e.output),created_at:r,msg_type:"tool_result",...n?{extra:{call_id:n}}:{}}]}else if(s==="reasoning"){const n=e.summary;if(!Array.isArray(n))return[];for(const o of n)y(o)&&typeof o.text=="string"&&o.text&&c.push({role:"assistant",content:o.text,msg_type:"thinking"})}else return[];return c.map((n,o)=>({native_message_id:o===0?u:`${u}#${o}`,native_parent_id:null,created_at:r,...n}))}function R(t,l){if(!v.test(t.agent_session_id))throw new f(_.invalidAgentSession,`invalid agent_session_id: ${t.agent_session_id}`);const a=h(l?.codexHome),e=S(t.agent_session_id,a);if(!e)throw new f(_.sessionNotFound,`codex session rollout not found for thread: ${t.agent_session_id}`);const s=p(a),r=p(e);if(r!==s&&!r.startsWith(s+g))throw new f(_.invalidAgentSession,`rollout path escapes codex home: ${t.agent_session_id}`);return r}async function H(t,l){const a=R(t,l);return{...await m({filePath:a,cursor:t.cursor,limit:t.limit,mapLine:w,sourceLabel:"codex session rollout",logTag:"codex-session-history"}),...t.sync_run_id?{sync_run_id:t.sync_run_id}:{}}}x("codex",H);export{H as readCodexSessionHistory};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import{EventEmitter as
|
|
2
|
-
`,"utf8"),
|
|
3
|
-
`)){const I=u.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(I){clearTimeout(v),l=!0,this.alive=!0,i(I[1]);return}}}),this.process.stderr?.on("data",c=>{const u=c.toString().trim();u&&n.info("opencode-adapter",`[stderr] ${u}`)}),this.process.on("error",c=>{n.error("opencode-adapter",`Spawn error: ${c.message}`),clearTimeout(v),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${c.message}`),this.clearRun()),l?this.stopped||this.emit("exit",1):(l=!0,o(c))}),this.process.on("exit",c=>{n.info("opencode-adapter",`Process exited (code=${c})`),clearTimeout(v),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${c})`),this.clearRun()),l?this.stopped||this.emit("exit",c??1):(l=!0,o(new Error(`opencode serve exited with code ${c}`)))})})}async ensureOcSession(e){const t=this.sessions.get(e),s=this.getSessionCwd(e);if(await this.transport.useDirectory(s),t?.ocSessionId)try{return await this.transport.getSession(t.ocSessionId,s),t.ocSessionId}catch{n.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, creating new`),t.ocSessionId=""}const i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i,s),this.sessions.set(e,{ocSessionId:i,cwd:s}),n.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),i}catch{n.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const o=await this.transport.createSession({title:`grix-${e.slice(-8)}`},s);return this.sessions.set(e,{ocSessionId:o.id,cwd:s}),this.options.bindingStore?.setAcpSessionId(e,o.id),n.info("opencode-adapter",`Created OC session ${o.id} for aibot=${e}`),o.id}async sendOcPrompt(e,t){const s=this.getSessionCwd(e),i=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=i,this.activeRun.cwd=s),await this.transport.sendPromptAsync(i,{parts:[{type:"text",text:t}]},s)}handleSseEvent(e){if(!this.stopped){if(this.lastSseAt=Date.now(),this.updateToolInFlight(e),this.resetIdleTimer(),this.activeRun){const t=this.auditCaptures.get(this.activeRun.eventId);t&&t.push({sequence:t.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}switch(e.type){case"message.part.updated":{if(!this.activeRun)break;const t=e.part,s=e.delta;if(t.type==="text"){const i=s||t.text;i&&this.acceptAssistantPartText(t.messageID,"text",i)}else t.type==="reasoning"?s&&this.acceptAssistantPartText(t.messageID,"reasoning",s):t.type==="tool"&&this.handleToolPartUpdate(t,s);break}case"message.updated":{if(!this.activeRun)break;const t=e.info;if(this.rememberMessageRole(t),t.role==="assistant"){const s=t;this.callbacks.sendUpdateBindingCard(this.activeRun.sessionId,"composing",this.activeRun.cwd,{model_provider:s.providerID,model_id:s.modelID}),s.error&&n.warn("opencode-adapter",`Message error: ${JSON.stringify(s.error)}`);const i=this.activeRun.ocSessionId;i&&s.sessionID===i&&(s.error?(this.flushTextBuffer(),this.finishRun("failed",s.error.message||s.error.type)):s.time?.completed&&s.finish!=="tool-calls"&&(this.flushTextBuffer(),this.finishRun("responded")))}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const t=e.error,s=t?typeof t=="object"&&"message"in t?t.message:JSON.stringify(t):"unknown session error";n.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);break}case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{n.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:break}}}updateToolInFlight(e){if(e.type!=="message.part.updated")return;const t=e.part;if(t?.type!=="tool"||!t.callID)return;const s=t.state?.status;s==="pending"||s==="running"?this.toolCallsInFlight.add(t.callID):(s==="completed"||s==="error")&&this.toolCallsInFlight.delete(t.callID)}rememberMessageRole(e){const t=typeof e.id=="string"?e.id.trim():"";t&&(e.role!=="user"&&e.role!=="assistant"||(this.messageRoles.set(t,e.role),this.flushOrDropPendingParts(t,e.role)))}acceptAssistantPartText(e,t,s){if(!this.activeRun||!s)return;const i=typeof e=="string"?e.trim():"";if(!i)return;const o=this.messageRoles.get(i);if(o==="user")return;if(o==="assistant"){this.emitAssistantPartChunk(t,s);return}const r=this.pendingPartChunks.get(i)??[];r.push({kind:t,value:s}),this.pendingPartChunks.set(i,r)}flushOrDropPendingParts(e,t){const s=this.pendingPartChunks.get(e);if(this.pendingPartChunks.delete(e),!(!s||s.length===0)&&t==="assistant")for(const i of s)this.emitAssistantPartChunk(i.kind,i.value)}emitAssistantPartChunk(e,t){if(!(!this.activeRun||!t)){if(e==="reasoning"){this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t);return}this.appendText(t)}}clearMessageRoleState(){this.messageRoles.clear(),this.pendingPartChunks.clear()}handleToolPartUpdate(e,t){if(!this.activeRun)return;const s=e.state;switch(s.status){case"pending":case"running":{this.sendToolUseOnce(e,s.input);break}case"completed":{this.sendToolUseOnce(e,s.input);break}case"error":{this.sendToolUseOnce(e,s.input),this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,"(failed)");break}}}sendToolUseOnce(e,t){!this.activeRun||this.toolCardsSent.has(e.callID)||(this.flushTextBuffer(),this.toolCardsSent.add(e.callID),this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,H(t??{})))}handlePermission(e){if(!this.activeRun)return;const{eventId:t,sessionId:s,ocSessionId:i}=this.activeRun;if(this.options.permissionPolicy==="fullAuto"){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(r=>{n.warn("opencode-adapter",`Auto-approve failed: ${r}`)});return}this.stopIdleTimer(),this.pendingPermissions.set(t,{permissionId:e.id,ocSessionId:i,cwd:this.activeRun.cwd});const o=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(t,s,e.type,o)}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const t=this.pendingPermissions.get(this.activeRun.eventId);if(!t)return{handled:!1,kind:""};const{permissionId:s,ocSessionId:i,cwd:o}=t,a=e.action_type==="exec_approve"||e.action_type==="permission_approve"?"once":"reject";return this.pendingPermissions.delete(this.activeRun.eventId),this.transport.respondPermission(i,s,a,o).then(()=>{n.info("opencode-adapter",`Permission ${a}: ${s}`),this.resetIdleTimer()}).catch(d=>{n.warn("opencode-adapter",`Permission response failed: ${d}`)}),{handled:!0,kind:"permission"}}startRun(e,t,s=!1){this.activeRun={eventId:e,sessionId:t,ocSessionId:"",cwd:this.getSessionCwd(t),chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null},this.toolCardsSent.clear(),s&&this.auditCaptures.set(e,[]),this.lastSseAt=Date.now(),this.livenessExtendStartAt=void 0}finishRun(e,t){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>Q){const a=[...this.completedEvents].slice(-500);this.completedEvents=new Set(a)}this.snapshotAuditBoundary(s),this.activeRun=null,this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,o=s.clientMsgId;e==="failed"&&t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t);const r=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,o).then(()=>{this.callbacks.sendEventResult(s.eventId,e,t)}).catch(a=>{const d=e==="responded"?"failed":e,h=e==="responded"?`final output delivery failed: ${a instanceof Error?a.message:String(a)}`:t;this.callbacks.sendEventResult(s.eventId,d,h)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,o),this.callbacks.sendEventResult(s.eventId,e,t))};if(s.textBuffer){this.stopTextFlush();for(const a of T(s.textBuffer))s.chunkSeq++,this.callbacks.sendStreamChunk(s.eventId,s.sessionId,a,s.chunkSeq,!1,s.clientMsgId);s.textBuffer=""}r(),this.emit("eventDone",s.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun&&this.snapshotAuditBoundary(this.activeRun),this.activeRun=null,this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopIdleTimer(),e&&this.emit("eventDone",e)}snapshotAuditBoundary(e){const t=this.auditCaptures.get(e.eventId);t&&(this.completedAuditBoundaries.set(e.eventId,{adapterType:"opencode",providerSessionId:e.ocSessionId||void 0,openCodeEvidence:{events:t}}),this.auditCaptures.delete(e.eventId))}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=z){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},q))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of T(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}shouldExtendByLiveness(){if(!this.activeRun)return!1;const e=this.process?.pid;if(!this.alive||!e||!k(e))return!1;const t=Date.now(),s=this.lastSseAt>0?t-this.lastSseAt:Number.POSITIVE_INFINITY;if(s<W)return this.livenessExtendStartAt=t,n.info("opencode-adapter",`Liveness check: fresh SSE for ${this.activeRun.eventId} (sseAge=${s}ms, toolsInFlight=${this.toolCallsInFlight.size}), extending`),!0;const o=this.livenessExtendStartAt??t;return this.livenessExtendStartAt===void 0&&(this.livenessExtendStartAt=o),t-o>x?(n.warn("opencode-adapter",`Liveness extension budget exhausted for ${this.activeRun.eventId} (no progress for ${Math.round((t-o)/6e4)}min), allowing idle fail`),!1):(n.info("opencode-adapter",`Liveness check: turn in progress for ${this.activeRun.eventId} (toolsInFlight=${this.toolCallsInFlight.size}, sseAge=${Number.isFinite(s)?`${s}ms`:"n/a"}), extending`),!0)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{if(this.stopped||!this.activeRun)return;if(this.pendingPermissions.has(this.activeRun.eventId)&&this.shouldExtendByLiveness()){n.info("opencode-adapter",`Idle timeout skipped: pendingPermissions for ${this.activeRun.eventId}, resetting timer`),this.resetIdleTimer();return}if(this.shouldExtendByLiveness()){this.resetIdleTimer();return}const e=this.process?.pid,s=!!(this.alive&&e&&k(e))?`liveness budget exhausted after ${x/6e4}min`:"process not alive";n.error("opencode-adapter",`Idle timeout (${s}, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`idle timeout: ${s}`),this.clearRun()),this.emit("exit",-1)},V))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=this.options.aibotSessionId?this.options.bindingStore?.get(this.options.aibotSessionId)?.cwd:void 0;if(e)return e;const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}getSessionCwd(e){return this.sessions.get(e)?.cwd??this.options.bindingStore?.get(e)?.cwd??this.resolveCwd()}buildPromptText(e){let t=e.text;return e.contextMessages&&e.contextMessages.length>0&&(t=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
1
|
+
import{EventEmitter as _}from"node:events";import{stat as E}from"node:fs/promises";import{existsSync as C,mkdirSync as M,readFileSync as $,writeFileSync as O}from"node:fs";import{join as S,resolve as F,dirname as D}from"node:path";import{homedir as B}from"node:os";import{fileURLToPath as j}from"node:url";import{resolveCommandPath as N,spawnCommand as L,killProcessGroup as k,hasChildProcesses as U}from"../../core/runtime/spawn.js";import{InternalApiServer as Q}from"../../core/mcp/internal-api-server.js";import{IdentityInjector as q}from"../shared/identity-injector.js";import{syncDefaultSkillsToDir as H}from"../../default-skills/index.js";import{buildSimpleProbeReport as J}from"../shared/probe-util.js";import{compactToolInputForWire as z}from"../shared/tool-wire-payload.js";import{buildOpencodeConfigContent as G}from"./opencode-config.js";import{OpenCodeTransport as V}from"./opencode-transport.js";import{log as o}from"../../core/log/index.js";import{splitTextForAibotProtocol as A}from"../../core/protocol/index.js";class X extends _{adapterSessionId;constructor(e){super(),this.adapterSessionId=e}emitError(e){if(this.listenerCount("error")===0){o.warn("opencode-adapter",`Prompt handle error (no listeners): ${e.message}`);return}this.emit("error",e)}async cancel(){}}const W=200,Y=2e3,K=12e4,Z=90*1e3,y=1800*1e3,P=3e4,R=600*1e3,ee="claude_interaction_reply",te="set_mode",se="set_model",ie="127.0.0.1",ne=0,oe=1e3,m={fullAuto:"full_auto",approval:"approval"},b=[{id:m.fullAuto,displayName:"Full Auto"},{id:m.approval,displayName:"Approval"}],re=[{permission:"*",pattern:"*",action:"allow"}],ae=[{permission:"doom_loop",pattern:"*",action:"ask"},{permission:"external_directory",pattern:"*",action:"ask"},{permission:"question",pattern:"*",action:"deny"},{permission:"plan_enter",pattern:"*",action:"deny"},{permission:"plan_exit",pattern:"*",action:"deny"},{permission:"repo_clone",pattern:"*",action:"deny"},{permission:"repo_overview",pattern:"*",action:"deny"},{permission:"read",pattern:"*.env",action:"ask"},{permission:"read",pattern:"*.env.*",action:"ask"},{permission:"read",pattern:"*.env.example",action:"allow"}];function w(g){const e=String(g??"").trim().toLowerCase();if(e==="full_auto"||e==="fullauto")return m.fullAuto;if(e==="approval"||e==="ask"||e==="autoedit"||e==="auto_edit")return m.approval}function x(g){try{return process.kill(g,0),!0}catch(e){return e.code==="EPERM"}}class ke extends _{type="opencode";config;callbacks;options;identity;process=null;transport=new V;alive=!1;stopped=!1;internalApi=null;sessions=new Map;sessionModes=new Map;sessionModels=new Map;availableModels=[];activeRun=null;completedEvents=new Set;clientMsgSeq=0;auditCaptures=new Map;completedAuditBoundaries=new Map;idleTimer=null;lastSseAt=0;livenessExtendStartAt;toolCallsInFlight=new Set;toolCardsSent=new Set;messageRoles=new Map;pendingPartChunks=new Map;pendingPermissions=new Map;pendingQuestions=new Map;permissionHandler=null;constructor(e,t,s){super(),this.config=e,this.callbacks=t,this.options=s??{},this.identity=new q("opencode-adapter",t.getAgentProfile)}onAgentProfileChanged(){this.identity.onProfileChanged()}async start(){await this.startInternalApiAndInjectMcp();const e=this.options.hostname??ie,t=this.options.port??ne,s=await this.spawnAndWait(e,t),i=this.resolveCwd();await this.transport.connect(s,i),this.transport.on("event",n=>this.handleSseEvent(n));try{await this.refreshModels()}catch(n){o.warn("opencode-adapter",`Failed to fetch providers for toolbar models: ${n instanceof Error?n.message:String(n)}`)}o.info("opencode-adapter",`Ready (pid=${this.process?.pid}, url=${s})`)}async refreshModels(){const e=await this.transport.listProviders(),t=[];for(const s of e)for(const i of s.models??[])t.push({id:`${s.id}/${i.id}`,displayName:i.name||i.id});this.availableModels=t,o.info("opencode-adapter",`Fetched ${t.length} models from ${e.length} providers`)}async stop(){if(this.stopped=!0,this.alive=!1,this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.transport.disconnect(),this.internalApi&&(await this.internalApi.stop(),this.internalApi=null),this.process){const e=this.process;try{k(e,"SIGTERM")}catch{}const t=setTimeout(()=>{try{k(e,"SIGKILL")}catch{}},5e3);e.on("exit",()=>clearTimeout(t)),this.process=null}}isAlive(){return this.alive}async createSession(e){const t=e.cwd??this.resolveCwd();await this.transport.useDirectory(t);const s=await this.transport.createSession({title:`grix-${Date.now()}`},t);return this.sessions.set(s.id,{ocSessionId:s.id,cwd:t}),o.info("opencode-adapter",`Created OC session ${s.id} for cwd=${t}`),await this.applySessionPermission(s.id,s.id,t),s.id}async resumeSession(e,t){const s=this.sessions.get(e);if(s?.ocSessionId)try{await this.transport.useDirectory(s.cwd),await this.transport.getSession(s.ocSessionId,s.cwd),await this.applySessionPermission(e,s.ocSessionId,s.cwd)}catch{o.warn("opencode-adapter",`OC session ${s.ocSessionId} gone, will create new on next prompt`),s.ocSessionId=""}}async destroySession(e){const t=this.sessions.get(e),s=t?t.ocSessionId:e;if(s)try{await this.transport.deleteSession(s,t?.cwd)}catch{}this.sessions.delete(e),this.identity.forgetSession(e)}sendPrompt(e){const t=new X(e.adapterSessionId);return this.sendOcPrompt(e.adapterSessionId,this.buildPromptText(e)).catch(s=>{t.emitError(s instanceof Error?s:new Error(String(s)))}),t}async cancel(e){if(this.activeRun)try{await this.transport.abortSession(this.activeRun.ocSessionId,this.activeRun.cwd)}catch{}}deliverInboundEvent(e){const{event_id:t,session_id:s,content:i}=e;if(this.completedEvents.has(t)){o.info("opencode-adapter",`Dropping duplicate event ${t}`),this.callbacks.sendEventAck(t,s),this.callbacks.sendEventResult(t,"responded");return}if(!this.alive){o.warn("opencode-adapter",`Dropping event ${t}: process not alive`),this.callbacks.sendEventAck(t,s),this.callbacks.sendEventResult(t,"failed","Agent process not running");return}this.activeRun&&this.activeRun.eventId!==t&&(o.info("opencode-adapter",`steer: ${this.activeRun.eventId} -> ${t}`),this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"canceled","steered to new event"),this.clearRun()),o.info("opencode-adapter",`prompt: event=${t} session=${s}`),this.callbacks.sendEventAck(t,s),this.startRun(t,s,e.audit?.enabled===!0),this.startComposing(s),this.resetIdleTimer();const n=this.buildPromptTextFromEvent(e);this.sendOcPrompt(s,n).catch(r=>{o.error("opencode-adapter",`prompt_async failed: ${r}`),this.finishRun("failed",String(r))})}deliverStopEvent(e,t){this.activeRun&&this.activeRun.eventId===e&&(o.info("opencode-adapter",`stop: event=${e}`),this.rejectAllPendingInteractions(),this.transport.abortSession(this.activeRun.ocSessionId,this.activeRun.cwd).catch(()=>{}),this.flushTextBuffer(),this.finishRun("canceled","stopped by user"))}setPermissionHandler(e){this.permissionHandler=e}async ping(e){return this.transport.healthCheck()}getStatus(){return{alive:this.alive,busy:this.activeRun!==null,sessions:this.sessions.size}}getActiveEventIds(){return this.activeRun?[this.activeRun.eventId]:[]}takeAuditBoundary(e){const t=this.completedAuditBoundaries.get(e);return t&&this.completedAuditBoundaries.delete(e),t}clearActiveEventForShutdown(){this.stopIdleTimer(),this.stopTextFlush(),this.rejectAllPendingInteractions(),this.activeRun&&this.snapshotAuditBoundary(this.activeRun),this.activeRun=null}getMcpConfig(){if(!this.internalApi)return null;const e=F(j(import.meta.url),"../../../mcp/acp-mcp-server.js");return{name:"grix-connector-tools",command:process.execPath,args:[e,"--api-url",this.internalApi.url]}}async hasBackgroundWork(){const e=this.process?.pid;return e?U(e,[e]):!1}async probe(e){const t=this.getStatus();return J(this.config.command||"opencode",{alive:t.alive,busy:t.busy,started:!!this.process},e)}async startInternalApiAndInjectMcp(){try{this.internalApi=new Q,this.internalApi.setInvokeHandler(async(c,p,d,u)=>this.callbacks.agentInvoke(c,p,u)),await this.internalApi.start(0),o.info("opencode-adapter",`Internal API started at ${this.internalApi.url}`);const e=this.getMcpConfig(),t=process.env.XDG_CONFIG_HOME||S(B(),".config"),s=S(t,"opencode","opencode.json");M(D(s),{recursive:!0});let i={};try{C(s)&&(i=JSON.parse($(s,"utf8")))}catch{}const n=i.mcp&&typeof i.mcp=="object"?i.mcp:{};n[e.name]={type:"local",command:[e.command,...e.args??[]],enabled:!0},i.mcp=n,O(s,`${JSON.stringify(i,null,2)}
|
|
2
|
+
`,"utf8"),o.info("opencode-adapter",`MCP config injected into ${s}`);const r=S(t,"opencode","skills"),a=H(r);a.length>0&&o.info("opencode-adapter",`Synced connector skills to ${r}: [${a.join(", ")}]`)}catch(e){o.warn("opencode-adapter",`Failed to inject MCP tools (non-fatal): ${e instanceof Error?e.message:String(e)}`)}}async handleLocalAction(e){const{action_type:t}=e;if(t==="exec_approve"||t==="exec_reject"||t==="permission_approve"||t==="permission_reject")return this.handlePermissionAction(e);if(t===te)return this.handleSetModeAction(e);if(t===se)return this.handleSetModelAction(e);if(t===ee){const s=e.params??{};if(String(s.kind??"")==="permission"){const i=s.resolution??{},n=String(i.value??"");return this.handlePermissionAction({...e,action_type:n==="allow"?"exec_approve":"exec_reject",params:{...s,approval_command_id:s.request_id}})}return this.handleQuestionReplyAction(e)}return{handled:!1,kind:""}}bindSession(e,t){if(o.info("opencode-adapter",`bindSession: ${e} \u2192 ${t}`),!this.sessions.has(e))this.sessions.set(e,{ocSessionId:"",cwd:t});else{const s=this.sessions.get(e);s.cwd=t}this.callbacks.sendUpdateBindingCard(e,"ready",t,this.buildToolbarMeta(e))}resolveModeId(e){if(e){const s=this.sessionModes.get(e);if(s)return s}const t=w(this.options.permissionPolicy);if(t)return t;if(e){const s=w(this.options.bindingStore?.get(e)?.modeId);if(s)return s}return m.fullAuto}buildModeToolbarMeta(e){const t=this.resolveModeId(e);return{mode_id:t,currentModeId:t,available_modes:b.map(s=>({...s}))}}async applySessionPermission(e,t,s){const i=this.resolveModeId(e),n=i===m.fullAuto?re:ae;try{await this.transport.updateSessionPermission(t,n,s),o.info("opencode-adapter",`Applied session permission ruleset (${i}) to ${t}`)}catch(r){o.warn("opencode-adapter",`Failed to apply session permission ruleset: ${r instanceof Error?r.message:String(r)}`)}}async handleSetModeAction(e){const t=e.params??{},s=String(t.session_id??this.options.aibotSessionId??"").trim(),i=w(t.mode_id);if(!i||!s)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","mode_id must be full_auto or approval and session_id is required"),{handled:!0,kind:"set_mode"};this.sessionModes.set(s,i);const n=this.sessions.get(s);n?.ocSessionId&&await this.applySessionPermission(s,n.ocSessionId,n.cwd);const r=n?.cwd??this.getSessionCwd(s);return this.callbacks.sendUpdateBindingCard(s,"ready",r,this.buildToolbarMeta(s)),this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"mode_set",session_context:{mode_id:i,modeId:i},mode_id:i,available_modes:b.map(a=>({...a}))}),{handled:!0,kind:"set_mode"}}resolveModelId(e){if(e){const s=this.sessionModels.get(e);if(s)return s}const t=String(this.options.model??"").trim();if(t)return t;if(e){const s=String(this.options.bindingStore?.get(e)?.modelId??"").trim();if(s)return s}return""}buildModelToolbarMeta(e){const t=this.resolveModelId(e);return{model_id:t,currentModelId:t,available_models:this.availableModels.map(s=>({...s}))}}buildToolbarMeta(e){return{...this.buildModelToolbarMeta(e),...this.buildModeToolbarMeta(e)}}handleSetModelAction(e){const t=e.params??{},s=String(t.session_id??this.options.aibotSessionId??"").trim(),i=String(t.model_id??"").trim();if(!i||!s)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"invalid_params","model_id and session_id are required"),{handled:!0,kind:"set_model"};this.sessionModels.set(s,i);const n=this.sessions.get(s)?.cwd??this.getSessionCwd(s);return this.callbacks.sendUpdateBindingCard(s,"ready",n,this.buildToolbarMeta(s)),this.callbacks.sendLocalActionResult(e.action_id,"ok",{outcome:"model_set",session_context:{model_id:i,modelId:i},model_id:i,available_models:this.availableModels.map(r=>({...r}))}),{handled:!0,kind:"set_model"}}async spawnAndWait(e,t){const s=this.resolveCwd();try{if(!(await E(s)).isDirectory())throw new Error(`Bound path is not a directory: ${s}`)}catch(i){throw String(i?.code??"")==="ENOENT"?new Error(`Bound directory does not exist: ${s}. Please rebind with /grix open <valid-directory>.`):i}return new Promise((i,n)=>{const r=this.config.command||"opencode",c=[...this.config.args??["serve"],`--hostname=${e}`,`--port=${t}`],p={...process.env,...this.config.env},d=G({model:this.options.model,permissionPolicy:this.options.permissionPolicy,provider:this.options.provider});d&&(p.OPENCODE_CONFIG_CONTENT=JSON.stringify(d));const u=N(r,typeof p.PATH=="string"?p.PATH:void 0);o.info("opencode-adapter",`Spawning: ${u} ${c.join(" ")} (cwd=${s})`),this.process=L(u,c,{env:p,cwd:s}).process;let f="",l=!1;const v=setTimeout(()=>{l||(l=!0,n(new Error(`opencode serve did not start after ${P/1e3}s`)))},P);this.process.stdout?.on("data",h=>{if(f+=h.toString(),!l)for(const I of f.split(`
|
|
3
|
+
`)){const T=I.match(/opencode server listening on (https?:\/\/[^\s]+)/);if(T){clearTimeout(v),l=!0,this.alive=!0,i(T[1]);return}}}),this.process.stderr?.on("data",h=>{const I=h.toString().trim();I&&o.info("opencode-adapter",`[stderr] ${I}`)}),this.process.on("error",h=>{o.error("opencode-adapter",`Spawn error: ${h.message}`),clearTimeout(v),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Spawn error: ${h.message}`),this.clearRun()),l?this.stopped||this.emit("exit",1):(l=!0,n(h))}),this.process.on("exit",h=>{o.info("opencode-adapter",`Process exited (code=${h})`),clearTimeout(v),this.alive=!1,this.transport.disconnect(),this.stopComposing(),this.stopIdleTimer(),this.stopTextFlush(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`Process exited (code=${h})`),this.clearRun()),l?this.stopped||this.emit("exit",h??1):(l=!0,n(new Error(`opencode serve exited with code ${h}`)))})})}async ensureOcSession(e){const t=this.sessions.get(e),s=this.getSessionCwd(e);if(await this.transport.useDirectory(s),t?.ocSessionId)try{return await this.transport.getSession(t.ocSessionId,s),t.ocSessionId}catch{o.warn("opencode-adapter",`OC session ${t.ocSessionId} gone, creating new`),t.ocSessionId=""}const i=this.options.bindingStore?.getAcpSessionId(e);if(i)try{return await this.transport.getSession(i,s),this.sessions.set(e,{ocSessionId:i,cwd:s}),o.info("opencode-adapter",`Resumed OC session ${i} for aibot=${e}`),await this.applySessionPermission(e,i,s),i}catch{o.warn("opencode-adapter",`Persisted OC session ${i} gone, creating new`)}const n=await this.transport.createSession({title:`grix-${e.slice(-8)}`},s);return this.sessions.set(e,{ocSessionId:n.id,cwd:s}),this.options.bindingStore?.setAcpSessionId(e,n.id),o.info("opencode-adapter",`Created OC session ${n.id} for aibot=${e}`),await this.applySessionPermission(e,n.id,s),n.id}async sendOcPrompt(e,t){const s=this.getSessionCwd(e),i=await this.ensureOcSession(e);this.activeRun&&(this.activeRun.ocSessionId=i,this.activeRun.cwd=s);const n=this.resolveModelId(e),r=n.indexOf("/"),a=r>0&&r<n.length-1?{providerID:n.slice(0,r),modelID:n.slice(r+1)}:void 0;await this.transport.sendPromptAsync(i,{parts:[{type:"text",text:t}],...a?{model:a}:{}},s)}handleSseEvent(e){if(!this.stopped){if(this.lastSseAt=Date.now(),this.updateToolInFlight(e),this.resetIdleTimer(),this.activeRun){const t=this.auditCaptures.get(this.activeRun.eventId);t&&t.push({sequence:t.length,observedAt:new Date().toISOString(),event:structuredClone(e)})}switch(e.type){case"message.part.updated":{if(!this.activeRun)break;const t=e.part,s=e.delta;if(t.type==="text"){const i=s||t.text;i&&this.acceptAssistantPartText(t.messageID,"text",i)}else t.type==="reasoning"?s&&this.acceptAssistantPartText(t.messageID,"reasoning",s):t.type==="tool"&&this.handleToolPartUpdate(t,s);break}case"message.updated":{if(!this.activeRun)break;const t=e.info;if(this.rememberMessageRole(t),t.role==="assistant"){const s=t;this.callbacks.sendUpdateBindingCard(this.activeRun.sessionId,"composing",this.activeRun.cwd,{...this.buildToolbarMeta(this.activeRun.sessionId),model_provider:s.providerID,model_id:s.modelID}),s.error&&o.warn("opencode-adapter",`Message error: ${JSON.stringify(s.error)}`);const i=this.activeRun.ocSessionId;i&&s.sessionID===i&&(s.error?(this.flushTextBuffer(),this.finishRun("failed",s.error.message||s.error.type)):s.time?.completed&&s.finish!=="tool-calls"&&(this.flushTextBuffer(),this.finishRun("responded")))}break}case"session.idle":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.status":{if(!this.activeRun)break;e.sessionID===this.activeRun.ocSessionId&&e.status.type==="idle"&&(this.flushTextBuffer(),this.finishRun("responded"));break}case"session.error":{if(!this.activeRun)break;const t=e.error,s=t?typeof t=="object"&&"message"in t?t.message:JSON.stringify(t):"unknown session error";o.error("opencode-adapter",`Session error: ${s}`),this.flushTextBuffer(),this.finishRun("failed",s);break}case"permission.updated":{this.handlePermission(e.permission);break}case"permission.asked":{this.handlePermissionAsked(e);break}case"question.asked":{this.handleQuestionAsked(e);break}case"permission.replied":case"question.replied":case"question.rejected":o.debug("opencode-adapter",`Interaction settled: ${e.type}`);break;case"session.created":case"session.updated":case"session.deleted":case"session.compacted":case"session.diff":case"file.edited":case"server.connected":break;case"server.instance.disposed":{o.warn("opencode-adapter",`Server instance disposed: ${e.directory}`),this.alive=!1,this.transport.disconnect(),this.activeRun&&(this.flushTextBuffer(),this.callbacks.sendEventResult(this.activeRun.eventId,"failed","server instance disposed"),this.clearRun()),this.stopped||this.emit("exit",-1);break}default:o.debug("opencode-adapter",`Unhandled SSE event type: ${e.type}`);break}}}updateToolInFlight(e){if(e.type!=="message.part.updated")return;const t=e.part;if(t?.type!=="tool"||!t.callID)return;const s=t.state?.status;s==="pending"||s==="running"?this.toolCallsInFlight.add(t.callID):(s==="completed"||s==="error")&&this.toolCallsInFlight.delete(t.callID)}rememberMessageRole(e){const t=typeof e.id=="string"?e.id.trim():"";t&&(e.role!=="user"&&e.role!=="assistant"||(this.messageRoles.set(t,e.role),this.flushOrDropPendingParts(t,e.role)))}acceptAssistantPartText(e,t,s){if(!this.activeRun||!s)return;const i=typeof e=="string"?e.trim():"";if(!i)return;const n=this.messageRoles.get(i);if(n==="user")return;if(n==="assistant"){this.emitAssistantPartChunk(t,s);return}const r=this.pendingPartChunks.get(i)??[];r.push({kind:t,value:s}),this.pendingPartChunks.set(i,r)}flushOrDropPendingParts(e,t){const s=this.pendingPartChunks.get(e);if(this.pendingPartChunks.delete(e),!(!s||s.length===0)&&t==="assistant")for(const i of s)this.emitAssistantPartChunk(i.kind,i.value)}emitAssistantPartChunk(e,t){if(!(!this.activeRun||!t)){if(e==="reasoning"){this.callbacks.sendThinking(this.activeRun.eventId,this.activeRun.sessionId,t);return}this.appendText(t)}}clearMessageRoleState(){this.messageRoles.clear(),this.pendingPartChunks.clear()}handleToolPartUpdate(e,t){if(!this.activeRun)return;const s=e.state;switch(s.status){case"pending":case"running":{this.sendToolUseOnce(e,s.input);break}case"completed":{this.sendToolUseOnce(e,s.input);break}case"error":{this.sendToolUseOnce(e,s.input),this.callbacks.sendToolResult(this.activeRun.eventId,this.activeRun.sessionId,e.tool,"(failed)");break}}}sendToolUseOnce(e,t){!this.activeRun||this.toolCardsSent.has(e.callID)||(this.flushTextBuffer(),this.toolCardsSent.add(e.callID),this.callbacks.sendToolUse(this.activeRun.eventId,this.activeRun.sessionId,e.tool,z(t??{})))}onPermissionTimeout(e){const t=this.pendingPermissions.get(e);t&&(this.pendingPermissions.delete(e),o.error("opencode-adapter",`Permission approval timeout: ${e}`),this.transport.respondPermission(t.ocSessionId,e,"reject",t.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===t.eventId&&(this.flushTextBuffer(),this.finishRun("failed","permission approval timeout")))}trackPendingPermission(e){if(!this.activeRun)return;this.stopIdleTimer();const t=setTimeout(()=>this.onPermissionTimeout(e),R);this.pendingPermissions.set(e,{permissionId:e,eventId:this.activeRun.eventId,ocSessionId:this.activeRun.ocSessionId,cwd:this.activeRun.cwd,timer:t})}handlePermissionAsked(e){if(!this.activeRun){this.transport.respondPermission(e.sessionID,e.id,"reject").catch(r=>{o.warn("opencode-adapter",`Orphan permission reject failed: ${r}`)});return}const{eventId:t,sessionId:s,ocSessionId:i}=this.activeRun;if(this.resolveModeId(s)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(r=>{o.warn("opencode-adapter",`Auto-approve failed: ${r}`)});return}this.trackPendingPermission(e.id);const n=e.patterns.length>0?`${e.permission}: ${e.patterns.join(", ")}`:e.permission;this.callbacks.sendPermissionCard?this.callbacks.sendPermissionCard({eventId:t,sessionId:s,approvalId:e.id,toolName:e.permission,toolTitle:n,toolInput:JSON.stringify(e.metadata??{})}):this.callbacks.sendToolUse(t,s,e.permission,JSON.stringify(e.metadata??{}))}handlePermission(e){if(!this.activeRun)return;const{eventId:t,sessionId:s,ocSessionId:i}=this.activeRun;if(this.resolveModeId(s)===m.fullAuto){this.transport.respondPermission(i,e.id,"always",this.activeRun.cwd).catch(n=>{o.warn("opencode-adapter",`Auto-approve failed: ${n}`)});return}if(this.trackPendingPermission(e.id),this.callbacks.sendPermissionCard)this.callbacks.sendPermissionCard({eventId:t,sessionId:s,approvalId:e.id,toolName:e.type,toolTitle:e.title||e.type,toolInput:JSON.stringify(e.metadata??{})});else{const n=JSON.stringify(e.metadata??{});this.callbacks.sendToolUse(t,s,e.type,n)}}handlePermissionAction(e){if(!this.activeRun)return{handled:!1,kind:""};const t=e.params??{},s=String(t.approval_id??t.approval_command_id??t.approvalId??t.tool_call_id??"");let i=s?this.pendingPermissions.get(s):void 0;if(!i){for(const a of this.pendingPermissions.values())if(a.eventId===this.activeRun.eventId){i=a;break}}if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"unknown_or_expired_approval_id","That approval request is no longer pending."),{handled:!0,kind:"permission"};const n=e.action_type==="exec_approve"||e.action_type==="permission_approve",r=n?"once":"reject";return clearTimeout(i.timer),this.pendingPermissions.delete(i.permissionId),this.transport.respondPermission(i.ocSessionId,i.permissionId,r,i.cwd).then(()=>{o.info("opencode-adapter",`Permission ${r}: ${i.permissionId}`),this.resetIdleTimer()}).catch(a=>{o.warn("opencode-adapter",`Permission response failed: ${a}`),this.resetIdleTimer()}),this.callbacks.sendLocalActionResult(e.action_id,"ok",{approval_id:i.permissionId,decision:n?"approve":"reject"}),{handled:!0,kind:"permission"}}onQuestionTimeout(e){const t=this.pendingQuestions.get(e);t&&(this.pendingQuestions.delete(e),o.error("opencode-adapter",`Question answer timeout: ${e}`),this.transport.rejectQuestion(e,t.cwd).catch(()=>{}),this.activeRun&&this.activeRun.eventId===t.eventId&&(this.flushTextBuffer(),this.finishRun("failed","question answer timeout")))}handleQuestionAsked(e){if(!this.activeRun){this.transport.rejectQuestion(e.id).catch(n=>{o.warn("opencode-adapter",`Orphan question reject failed: ${n}`)});return}const{eventId:t,sessionId:s}=this.activeRun;this.stopIdleTimer();const i=setTimeout(()=>this.onQuestionTimeout(e.id),R);if(this.pendingQuestions.set(e.id,{requestId:e.id,eventId:t,cwd:this.activeRun.cwd,questions:e.questions,timer:i}),this.callbacks.sendAgentQuestionCard)this.callbacks.sendAgentQuestionCard(t,s,{request_id:e.id,mode:"form",questions:e.questions.map(n=>({header:n.header,prompt:n.question,...n.options.length>0?{options:n.options.map(r=>r.label)}:{},...n.multiple!==void 0?{multi_select:n.multiple}:{}})),expires_at:Date.now()+R});else{const n=e.questions.map(r=>r.question).join(`
|
|
4
|
+
`);this.callbacks.sendRunError(t,s,`Agent asks: ${n}`)}}handleQuestionReplyAction(e){const t=e.params??{},s=String(t.request_id??""),i=s?this.pendingQuestions.get(s):void 0;if(!i)return this.callbacks.sendLocalActionResult(e.action_id,"failed",void 0,"interaction_request_not_pending","The question is no longer pending; the reply was not delivered."),{handled:!0,kind:"question_reply"};const n=t.resolution??{},r=String(n.type??""),a=(d,u)=>{clearTimeout(i.timer),this.pendingQuestions.delete(i.requestId),d().then(()=>this.resetIdleTimer()).catch(f=>{o.warn("opencode-adapter",`Question response failed: ${f}`),this.resetIdleTimer()}),this.callbacks.sendLocalActionResult(e.action_id,"ok",u)};if(r==="action"){const d=String(n.value??"");if(d==="cancel"||d==="decline")return a(()=>this.transport.rejectQuestion(i.requestId,i.cwd),{request_id:s,resolution:"cancel"}),{handled:!0,kind:"question_reply"}}let c=null;if(Array.isArray(t.answers))c=t.answers.map(d=>Array.isArray(d)?d.map(String):[String(d)]);else if(r==="text"){const d=String(n.value??"");d&&(c=i.questions.map((u,f)=>f===0?[d]:[]))}else if(r==="map"){const d=Array.isArray(n.entries)?n.entries:[];c=i.questions.map((u,f)=>{const l=d.find(v=>v.key===u.header||v.key===String(f));return l&&l.value?[l.value]:[]})}if(!c||c.every(d=>d.length===0))return a(()=>this.transport.rejectQuestion(i.requestId,i.cwd),{request_id:s,resolution:"cancel"}),{handled:!0,kind:"question_reply"};const p=c;return a(()=>this.transport.replyQuestion(i.requestId,p,i.cwd),{request_id:s,resolution:"answer"}),{handled:!0,kind:"question_reply"}}rejectAllPendingInteractions(){for(const e of this.pendingPermissions.values())clearTimeout(e.timer),this.transport.respondPermission(e.ocSessionId,e.permissionId,"reject",e.cwd).catch(()=>{});this.pendingPermissions.clear();for(const e of this.pendingQuestions.values())clearTimeout(e.timer),this.transport.rejectQuestion(e.requestId,e.cwd).catch(()=>{});this.pendingQuestions.clear()}hasPendingInteractionForRun(e){for(const t of this.pendingPermissions.values())if(t.eventId===e)return!0;for(const t of this.pendingQuestions.values())if(t.eventId===e)return!0;return!1}startRun(e,t,s=!1){this.activeRun={eventId:e,sessionId:t,ocSessionId:"",cwd:this.getSessionCwd(t),chunkSeq:0,clientMsgId:`oc_${++this.clientMsgSeq}_${Date.now()}`,textBuffer:"",flushTimer:null},this.toolCardsSent.clear(),s&&this.auditCaptures.set(e,[]),this.lastSseAt=Date.now(),this.livenessExtendStartAt=void 0}finishRun(e,t){const s=this.activeRun;if(!s)return;if(this.completedEvents.add(s.eventId),this.completedEvents.size>oe){const a=[...this.completedEvents].slice(-500);this.completedEvents=new Set(a)}this.snapshotAuditBoundary(s),this.activeRun=null,this.rejectAllPendingInteractions(),this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopComposing(),this.stopIdleTimer();const i=++s.chunkSeq,n=s.clientMsgId;e==="failed"&&t&&this.callbacks.sendRunError(s.eventId,s.sessionId,t);const r=()=>{this.callbacks.sendFinalStreamChunkReliable?this.callbacks.sendFinalStreamChunkReliable(s.eventId,s.sessionId,n).then(()=>{this.callbacks.sendEventResult(s.eventId,e,t)}).catch(a=>{const c=e==="responded"?"failed":e,p=e==="responded"?`final output delivery failed: ${a instanceof Error?a.message:String(a)}`:t;this.callbacks.sendEventResult(s.eventId,c,p)}):(this.callbacks.sendStreamChunk(s.eventId,s.sessionId,"",i,!0,n),this.callbacks.sendEventResult(s.eventId,e,t))};if(s.textBuffer){this.stopTextFlush();for(const a of A(s.textBuffer))s.chunkSeq++,this.callbacks.sendStreamChunk(s.eventId,s.sessionId,a,s.chunkSeq,!1,s.clientMsgId);s.textBuffer=""}r(),this.emit("eventDone",s.eventId)}clearRun(){this.activeRun?.flushTimer&&clearTimeout(this.activeRun.flushTimer);const e=this.activeRun?.eventId;this.activeRun&&this.snapshotAuditBoundary(this.activeRun),this.activeRun=null,this.rejectAllPendingInteractions(),this.toolCallsInFlight.clear(),this.toolCardsSent.clear(),this.livenessExtendStartAt=void 0,this.clearMessageRoleState(),this.stopIdleTimer(),e&&this.emit("eventDone",e)}snapshotAuditBoundary(e){const t=this.auditCaptures.get(e.eventId);t&&(this.completedAuditBoundaries.set(e.eventId,{adapterType:"opencode",providerSessionId:e.ocSessionId||void 0,openCodeEvidence:{events:t}}),this.auditCaptures.delete(e.eventId))}appendText(e){if(this.activeRun){if(this.activeRun.textBuffer+=e,this.activeRun.textBuffer.length>=Y){this.flushTextBuffer();return}this.scheduleTextFlush()}}scheduleTextFlush(){!this.activeRun||this.activeRun.flushTimer||(this.activeRun.flushTimer=setTimeout(()=>{this.activeRun&&(this.activeRun.flushTimer=null),this.flushTextBuffer()},W))}flushTextBuffer(){if(this.stopTextFlush(),!(!this.activeRun||!this.activeRun.textBuffer)){for(const e of A(this.activeRun.textBuffer))this.activeRun.chunkSeq++,this.callbacks.sendStreamChunk(this.activeRun.eventId,this.activeRun.sessionId,e,this.activeRun.chunkSeq,!1,this.activeRun.clientMsgId);this.activeRun.textBuffer=""}}stopTextFlush(){this.activeRun?.flushTimer&&(clearTimeout(this.activeRun.flushTimer),this.activeRun.flushTimer=null)}startComposing(e){}stopComposing(){}shouldExtendByLiveness(){if(!this.activeRun)return!1;const e=this.process?.pid;if(!this.alive||!e||!x(e))return!1;const t=Date.now(),s=this.lastSseAt>0?t-this.lastSseAt:Number.POSITIVE_INFINITY;if(s<Z)return this.livenessExtendStartAt=t,o.info("opencode-adapter",`Liveness check: fresh SSE for ${this.activeRun.eventId} (sseAge=${s}ms, toolsInFlight=${this.toolCallsInFlight.size}), extending`),!0;const n=this.livenessExtendStartAt??t;return this.livenessExtendStartAt===void 0&&(this.livenessExtendStartAt=n),t-n>y?(o.warn("opencode-adapter",`Liveness extension budget exhausted for ${this.activeRun.eventId} (no progress for ${Math.round((t-n)/6e4)}min), allowing idle fail`),!1):(o.info("opencode-adapter",`Liveness check: turn in progress for ${this.activeRun.eventId} (toolsInFlight=${this.toolCallsInFlight.size}, sseAge=${Number.isFinite(s)?`${s}ms`:"n/a"}), extending`),!0)}resetIdleTimer(){this.stopIdleTimer(),!(this.stopped||!this.activeRun)&&(this.idleTimer=setTimeout(()=>{if(this.stopped||!this.activeRun)return;if(this.hasPendingInteractionForRun(this.activeRun.eventId)&&this.shouldExtendByLiveness()){o.info("opencode-adapter",`Idle timeout skipped: pending interaction for ${this.activeRun.eventId}, resetting timer`),this.resetIdleTimer();return}if(this.shouldExtendByLiveness()){this.resetIdleTimer();return}const e=this.process?.pid,s=!!(this.alive&&e&&x(e))?`liveness budget exhausted after ${y/6e4}min`:"process not alive";o.error("opencode-adapter",`Idle timeout (${s}, toolsInFlight=${this.toolCallsInFlight.size}) \u2014 emitting exit for respawn`),this.flushTextBuffer(),this.activeRun&&(this.callbacks.sendEventResult(this.activeRun.eventId,"failed",`idle timeout: ${s}`),this.clearRun()),this.emit("exit",-1)},K))}stopIdleTimer(){this.idleTimer&&(clearTimeout(this.idleTimer),this.idleTimer=null)}resolveCwd(){const e=this.options.aibotSessionId?this.options.bindingStore?.get(this.options.aibotSessionId)?.cwd:void 0;if(e)return e;const t=(this.config.options??{}).cwd;return typeof t=="string"&&t?t:process.cwd()}getSessionCwd(e){return this.sessions.get(e)?.cwd??this.options.bindingStore?.get(e)?.cwd??this.resolveCwd()}buildPromptText(e){let t=e.text;return e.contextMessages&&e.contextMessages.length>0&&(t=e.contextMessages.map(i=>`[context] ${i.senderId}: ${i.content}`).join(`
|
|
4
5
|
`)+`
|
|
5
6
|
|
|
6
|
-
`+t),this.identity.injectOnce(e.adapterSessionId,t)}buildPromptTextFromEvent(e){let t=e.content||"";if(e.context_messages_json)try{const s=JSON.parse(e.context_messages_json);Array.isArray(s)&&s.length>0&&(t=s.map(
|
|
7
|
+
`+t),this.identity.injectOnce(e.adapterSessionId,t)}buildPromptTextFromEvent(e){let t=e.content||"";if(e.context_messages_json)try{const s=JSON.parse(e.context_messages_json);Array.isArray(s)&&s.length>0&&(t=s.map(n=>`[context] ${n.sender_id??"unknown"}: ${n.content}`).join(`
|
|
7
8
|
`)+`
|
|
8
9
|
|
|
9
|
-
`+t)}catch{}return this.identity.injectOnce(e.session_id,t)}}export{
|
|
10
|
+
`+t)}catch{}return this.identity.injectOnce(e.session_id,t)}}export{ke as OpenCodeAdapter};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import{EventEmitter as
|
|
1
|
+
import{EventEmitter as f}from"node:events";import{log as l}from"../../core/log/index.js";const p=3e4,u=3e3,y=3e4;class b extends f{baseUrl="";directory="";abortController=null;closed=!1;sseConnected=!1;sseGeneration=0;async connect(t,e){this.baseUrl=t,this.directory=e??"",this.closed=!1,await this.subscribeEvents(),l.info("opencode-transport",`connected to ${t}`)}async useDirectory(t){const e=t??"";this.directory===e&&this.sseConnected&&!this.closed||(this.directory=e,!(!this.baseUrl||this.closed)&&(this.abortController&&(this.abortController.abort(),this.abortController=null),this.sseConnected=!1,await this.subscribeEvents(),l.info("opencode-transport",`switched directory to ${e||"<default>"}`)))}async subscribeEvents(){if(this.closed)return;const t=++this.sseGeneration,e=new AbortController;this.abortController=e;const s=new URL("/event",this.baseUrl);this.directory&&s.searchParams.set("directory",this.directory);const r=await fetch(s.toString(),{method:"GET",headers:{Accept:"text/event-stream"},signal:e.signal});if(!r.ok||!r.body)throw new Error(`SSE connect failed: ${r.status}`);this.sseConnected=!0,this.readSseStream(r.body.getReader(),t)}async readSseStream(t,e){const s=new TextDecoder;let r="";try{for(;!this.closed;){const{done:n,value:i}=await t.read();if(n)break;r+=s.decode(i,{stream:!0});const a=r.split(`
|
|
2
2
|
|
|
3
|
-
`);r=a.pop()??"";for(const h of a){const c=this.parseSseFrame(h);if(c)try{this.emit("event",c)}catch(
|
|
3
|
+
`);r=a.pop()??"";for(const h of a){const c=this.parseSseFrame(h);if(c)try{this.emit("event",c)}catch(o){const d=o instanceof Error?o.message:String(o);l.warn("opencode-transport",`event handler failed (${c.type}): ${d}`)}}}}catch(n){if(this.closed)return;const i=n instanceof Error?n.message:String(n);if(i.includes("abort"))return;l.warn("opencode-transport",`SSE error: ${i}, reconnecting...`),await this.reconnectSse(e)}finally{e===this.sseGeneration&&(this.sseConnected=!1)}}parseSseFrame(t){let e="",s=[];for(const r of t.split(`
|
|
4
4
|
`))r.startsWith("event:")?e=r.slice(6).trim():r.startsWith("data:")&&s.push(r.slice(5).trimStart());if(s.length===0)return null;try{const r=s.join(`
|
|
5
|
-
`),n=JSON.parse(r);if(e&&n.type!==e&&(n.type=e),n&&typeof n.properties=="object"&&n.properties!==null){const{properties:
|
|
5
|
+
`),n=JSON.parse(r);if(e&&n.type!==e&&(n.type=e),n&&typeof n.properties=="object"&&n.properties!==null){const{properties:i,...a}=n;return{...a,...i}}return n}catch{return null}}async reconnectSse(t){if(this.closed||t!==this.sseGeneration)return;const e=u+Math.random()*u;if(await new Promise(s=>setTimeout(s,Math.min(e,y))),!this.closed&&t===this.sseGeneration)try{await this.subscribeEvents()}catch(s){if(this.closed)return;l.warn("opencode-transport",`SSE reconnect failed: ${s instanceof Error?s.message:String(s)}`),this.reconnectSse(this.sseGeneration)}}async request(t,e,s,r){if(this.closed)throw new Error("transport closed");const n=new URL(e,this.baseUrl),i=r?.directory??this.directory;i&&n.searchParams.set("directory",i);const a=new AbortController,h=setTimeout(()=>a.abort(),p);try{const c={method:t,headers:{"Content-Type":"application/json"},signal:a.signal};s!==void 0&&(c.body=JSON.stringify(s));const o=await fetch(n.toString(),c);if(o.status===204)return;const d=await o.json();if(!o.ok)throw new Error(`REST ${t} ${e}: ${o.status} ${JSON.stringify(d)}`);return d}finally{clearTimeout(h)}}async createSession(t,e){return this.request("POST","/session",t??{},{directory:e})}async getSession(t,e){return this.request("GET",`/session/${t}`,void 0,{directory:e})}async deleteSession(t,e){await this.request("DELETE",`/session/${t}`,void 0,{directory:e})}async updateSessionPermission(t,e,s){await this.request("PATCH",`/session/${t}`,{permission:e},{directory:s})}async sendPromptAsync(t,e,s){await this.request("POST",`/session/${t}/prompt_async`,e,{directory:s})}async abortSession(t,e){await this.request("POST",`/session/${t}/abort`,void 0,{directory:e})}async respondPermission(t,e,s,r){await this.request("POST",`/session/${t}/permissions/${e}`,{response:s},{directory:r})}async replyQuestion(t,e,s){await this.request("POST",`/question/${t}/reply`,{answers:e},{directory:s})}async rejectQuestion(t,e){await this.request("POST",`/question/${t}/reject`,void 0,{directory:e})}async listProviders(){return(await this.request("GET","/config/providers"))?.providers??[]}async healthCheck(){try{return(await fetch(new URL("/session",this.baseUrl).toString(),{method:"GET",headers:{"Content-Type":"application/json"},signal:AbortSignal.timeout(3e3)})).ok}catch{return!1}}disconnect(){this.closed=!0,this.sseGeneration++,this.abortController&&(this.abortController.abort(),this.abortController=null),this.sseConnected=!1,this.removeAllListeners()}get isConnected(){return this.sseConnected&&!this.closed}}export{b as OpenCodeTransport};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import{log as O}from"../../core/log/index.js";import{openReadOnlyDatabase as R}from"../../core/util/sqlite-reader.js";import{SESSION_HISTORY_ERROR_CODES as p,SessionHistoryError as f,decodeOpaqueCursor as E,encodeOpaqueCursor as x,normalizeHistoryLimit as I,registerSessionHistoryReader as b}from"../shared/session-history.js";import{resolveOpenCodeDbPath as T}from"./session-scanner.js";const w="opencode-session-history",A=/^[A-Za-z0-9][A-Za-z0-9_-]*$/;function y(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function N(t){const s=E(t);if(s.v!==2||typeof s.t!="number"||!Number.isFinite(s.t)||s.t<0||typeof s.id!="string")throw new f(p.invalidCursor,`invalid opencode sync_history cursor: ${t}`);return{t:s.t,id:s.id}}function g(t){return x({v:2,t:t.t,id:t.id})}function $(t){return t==="user"||t==="assistant"||t==="system"||t==="tool"?t:"system"}const m=new Set(["pending","running"]);function S(t){try{const s=JSON.parse(t.data);return y(s)?s:null}catch{return null}}function D(t){const s=S(t);if(!s||s.type!=="tool")return!1;const e=y(s.state)?s.state:void 0,r=typeof e?.status=="string"?e.status:"";return m.has(r)}function P(t,s){const e=S(t);if(!e)return[];const r=typeof e.type=="string"?e.type:"",d=t.id||s,c=t.message_id||null,h=new Date(t.time_created).toISOString(),o=[];if(r==="text"){if(typeof e.text!="string"||!e.text)return[];o.push({role:$(t.role),content:e.text,msg_type:"text"})}else if(r==="reasoning"){if(typeof e.text!="string"||!e.text)return[];o.push({role:"assistant",content:e.text,msg_type:"thinking"})}else if(r==="tool"){const a=typeof e.tool=="string"?e.tool:"",i=typeof e.callID=="string"?e.callID:"",n=y(e.state)?e.state:void 0;if(!a||!i)return O.warn(w,`skipped unidentifiable tool part: ${d}`),[];const _=typeof n?.status=="string"?n.status:"";if(m.has(_))return[];let u="{}";try{u=JSON.stringify(n?.input??{})}catch{}o.push({role:"assistant",content:u,msg_type:"tool_use",extra:{tool_name:a,tool_use_id:i}}),_==="error"?o.push({role:"tool",content:typeof n?.error=="string"?n.error:"",msg_type:"tool_result",extra:{tool_use_id:i,is_error:!0}}):o.push({role:"tool",content:typeof n?.output=="string"?n.output:"",msg_type:"tool_result",extra:{tool_use_id:i}})}else return[];return o.map((a,i)=>({native_message_id:i===0?d:`${d}#${i}`,native_parent_id:c,created_at:h,...a}))}async function C(t,s){if(!A.test(t.agent_session_id))throw new f(p.invalidAgentSession,`invalid agent_session_id: ${t.agent_session_id}`);const e=t.cursor?N(t.cursor):{t:0,id:""},r=I(t.limit),d=s?.dbPath??T(),c=R(d);if(!c)throw new f(p.sourceUnreadable,`failed to open opencode database: ${d}`);try{if(c.all("SELECT id FROM session WHERE id = ?",t.agent_session_id).length===0)throw new f(p.sessionNotFound,`opencode session not found: ${t.agent_session_id}`);let o;try{o=c.all(`SELECT p.id, p.message_id, p.time_created, p.data,
|
|
2
|
+
json_extract(m.data, '$.role') AS role
|
|
3
|
+
FROM part p
|
|
4
|
+
LEFT JOIN message m ON m.id = p.message_id
|
|
5
|
+
WHERE p.session_id = ?
|
|
6
|
+
AND (p.time_created > ? OR (p.time_created = ? AND p.id > ?))
|
|
7
|
+
ORDER BY p.time_created, p.id
|
|
8
|
+
LIMIT ?`,t.agent_session_id,e.t,e.t,e.id,r+1)}catch(l){throw new f(p.sourceUnreadable,`failed to query opencode parts for ${t.agent_session_id}: ${l}`)}if(o.length===0)return{messages:[],has_more:!1,next_cursor:t.cursor??g(e),...t.sync_run_id?{sync_run_id:t.sync_run_id}:{}};let a=o.length>r;const i=o.slice(0,r),n=[],_=[];for(const l of i){if(D(l)){a=!1;break}n.push(l),_.push(...P(l,`part-${l.time_created}-${n.length-1}`))}const u=n[n.length-1];return{messages:_,has_more:a,next_cursor:u?g({t:u.time_created,id:u.id}):t.cursor??g(e),...t.sync_run_id?{sync_run_id:t.sync_run_id}:{}}}finally{c.close()}}b("opencode",C);export{C as readOpenCodeSessionHistory};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{isAbsolute as x,join as S,resolve as u,sep as k}from"node:path";import{readJsonlHistoryPage as A,registerSessionHistoryReader as b,SESSION_HISTORY_ERROR_CODES as d,SessionHistoryError as _}from"../shared/session-history.js";import{resolvePiAgentDir as R}from"./session-scanner.js";function c(t){return typeof t=="object"&&t!==null&&!Array.isArray(t)}function I(t){if(typeof t=="string")return t;if(Array.isArray(t)){const r=[];for(const n of t)c(n)&&n.type==="text"&&typeof n.text=="string"&&r.push(n.text);return r.join(`
|
|
2
|
+
`)}return t==null?"":String(t)}function v(t,r){if((typeof t.type=="string"?t.type:"")!=="message")return[];const e=c(t.message)?t.message:void 0;if(!e)return[];const p=typeof e.role=="string"?e.role:"",y=typeof t.id=="string"&&t.id?t.id:r,m=typeof t.parentId=="string"?t.parentId:null,h=typeof t.timestamp=="string"?t.timestamp:typeof e.timestamp=="string"?e.timestamp:"",a=[];if(p==="user"){const s=e.content;if(typeof s=="string")a.push({role:"user",content:s,msg_type:"text"});else if(Array.isArray(s))for(const i of s){if(!c(i))continue;const l=i;l.type==="text"&&typeof l.text=="string"&&a.push({role:"user",content:l.text,msg_type:"text"})}}else if(p==="assistant"){const s=e.content,i=typeof e.model=="string"?e.model:void 0;if(typeof s=="string")a.push({role:"assistant",content:s,msg_type:"text",...i?{extra:{model:i}}:{}});else if(Array.isArray(s))for(const l of s){if(!c(l))continue;const o=l;if(o.type==="text"&&typeof o.text=="string")a.push({role:"assistant",content:o.text,msg_type:"text",...i?{extra:{model:i}}:{}});else if(o.type==="thinking"){const f=typeof o.thinking=="string"?o.thinking:typeof o.text=="string"?o.text:"";if(!f)continue;a.push({role:"assistant",content:f,msg_type:"thinking"})}else if(o.type==="toolCall"){const f={};typeof o.name=="string"&&(f.tool_name=o.name),typeof o.id=="string"&&(f.tool_use_id=o.id);let g="{}";try{g=JSON.stringify(o.arguments??{})}catch{}a.push({role:"assistant",content:g,msg_type:"tool_use",...Object.keys(f).length>0?{extra:f}:{}})}}}else if(p==="toolResult"){const s={};typeof e.toolCallId=="string"&&(s.tool_use_id=e.toolCallId),e.isError===!0&&(s.is_error=!0),a.push({role:"tool",content:I(e.content),msg_type:"tool_result",...Object.keys(s).length>0?{extra:s}:{}})}else return[];return a.map((s,i)=>({native_message_id:i===0?y:`${y}#${i}`,native_parent_id:m,created_at:h,...s}))}function w(t,r){const n=t.agent_session_id;if(!n||!x(n))throw new _(d.invalidAgentSession,`invalid pi agent_session_id (expect absolute session file path): ${t.agent_session_id}`);const e=u(r?.sessionsDir??S(R(),"sessions")),p=u(n);if(p!==e&&!p.startsWith(e+k))throw new _(d.invalidAgentSession,`pi session path escapes sessions dir: ${t.agent_session_id}`);return p}async function O(t,r){const n=w(t,r);return{...await A({filePath:n,cursor:t.cursor,limit:t.limit,mapLine:v,sourceLabel:"pi session transcript",logTag:"pi-session-history"}),...t.sync_run_id?{sync_run_id:t.sync_run_id}:{}}}b("pi",O);export{O as readPiSessionHistory};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
import{createReadStream as v,statSync as I}from"node:fs";import{createInterface as N}from"node:readline";import{log as R}from"../../core/log/index.js";const s={agentSessionRequired:"history_agent_session_required",invalidAgentSession:"history_invalid_agent_session",cwdRequired:"history_cwd_required",sessionNotFound:"history_session_not_found",invalidCursor:"history_invalid_cursor",sourceUnreadable:"history_source_unreadable",providerUnsupported:"history_provider_unsupported",runtimeError:"history_runtime_error"};class o extends Error{code;constructor(e,d){super(d),this.name="SessionHistoryError",this.code=e}}const b=new Map;function A(r,e){b.set(r,e)}function B(r){return b.get(r)}const C=50,O=200;function H(r){if(typeof r!="number"||!Number.isFinite(r)||r===0)return C;const e=Math.floor(r);return e<1?1:e>O?O:e}function $(r){const e={v:1,off:r};return Buffer.from(JSON.stringify(e),"utf8").toString("base64url")}function L(r){const e=T(r);if(e.v!==1||typeof e.off!="number"||!Number.isInteger(e.off)||e.off<0)throw new o(s.invalidCursor,`invalid sync_history cursor: ${r}`);return e.off}function J(r){return Buffer.from(JSON.stringify(r),"utf8").toString("base64url")}function T(r){try{const e=JSON.parse(Buffer.from(r,"base64url").toString("utf8"));if(!e||typeof e!="object"||Array.isArray(e))throw new Error("bad cursor payload");return e}catch(e){throw e instanceof o?e:new o(s.invalidCursor,`invalid sync_history cursor: ${r}`)}}async function z(r){const{filePath:e,mapLine:d,sourceLabel:n}=r,i=r.cursor?L(r.cursor):0,x=H(r.limit);let a;try{const t=I(e);if(t.isDirectory())throw new o(s.sourceUnreadable,`${n} is a directory: ${e}`);a=t.size}catch(t){throw t instanceof o?t:t.code==="ENOENT"?new o(s.sessionNotFound,`${n} not found: ${e}`):new o(s.sourceUnreadable,`failed to stat ${n} ${e}: ${t}`)}if(i>a)throw new o(s.invalidCursor,`cursor offset ${i} is beyond ${n} size ${a}: ${e}`);if(i===a)return{messages:[],has_more:!1,next_cursor:$(i)};const f=[];let l=!1,y=0,p=!1,c=i;const h=v(e,{start:i}),_=N({input:h,crlfDelay:1/0});try{for await(const t of _){const u=c,m=Buffer.byteLength(t,"utf8");if(u+m===a)break;c=u+m+1;const S=t.trim();if(!S)continue;let g;try{g=JSON.parse(S)}catch{y++;continue}const w=d(g,`line-${u}`);if(p){if(w.length>0){l=!0;break}continue}for(const E of w)f.push({message:E,lineEndOffset:c});f.length>=x&&(p=!0)}}catch(t){throw new o(s.sourceUnreadable,`failed to read ${n} ${e}: ${t}`)}finally{_.close(),h.destroy()}return y>0&&R.warn(r.logTag??n,`skipped ${y} malformed line(s) in ${e}`),{messages:f.map(t=>t.message),has_more:l,next_cursor:$(l?f[f.length-1].lineEndOffset:c)}}export{C as SESSION_HISTORY_DEFAULT_LIMIT,s as SESSION_HISTORY_ERROR_CODES,O as SESSION_HISTORY_MAX_LIMIT,o as SessionHistoryError,L as decodeOffsetCursor,T as decodeOpaqueCursor,$ as encodeOffsetCursor,J as encodeOpaqueCursor,B as getSessionHistoryReader,H as normalizeHistoryLimit,z as readJsonlHistoryPage,A as registerSessionHistoryReader};
|