@yuandc/aica 0.1.21 → 0.1.22

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.
@@ -0,0 +1 @@
1
+ async function c(e){const r=e.signal;if(!r)return e.startRequest();if(r.aborted)throw await n(e.onAbort),new Error(e.cancelledMessage);let t=null;const s=new Promise((a,o)=>{t=()=>{n(e.onAbort),o(new Error(e.cancelledMessage))},r.addEventListener("abort",t,{once:!0})});try{const a=Promise.resolve().then(()=>{if(r.aborted)throw new Error(e.cancelledMessage);return e.startRequest()});return await Promise.race([a,s])}finally{t&&r.removeEventListener("abort",t)}}async function n(e){try{await e()}catch{}}export{c as runAbortableRequest};
@@ -1 +1 @@
1
- import d from"node:fs";import{BoundedItemBuffer as I}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as v}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as A,statusFromAcpSessionUpdate as x}from"../../client/acp-events.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as S}from"./launch.js";import{CodexPromptPerformanceTracker as P,readCodexContextMaintenancePolicy as k}from"./context-maintenance.js";const M={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class N{id="codex-acp";name="Codex ACP";capabilities=M;async createSession(e){return new T(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),t=await w.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,...S(),onSessionUpdate:()=>{}});try{t.initialized||(await t.client.initialize(6e4),t.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const r=await t.client.forkSession({sessionId:s,cwd:e.cwd,config:g(e)},e.timeoutMs??12e4),n=p(r);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{t.release()}}}class T{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new I(16);content="";performanceTracker=null;constructor(e,s,t=E()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.performanceTracker=new P(s);let t=null,r=null,n=this.providerSessionId,i,c=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,r=a.started,await this.initialize(a),n=await this.establishSession(a,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),c=this.startSlowStatusTimer(),i=await this.promptWithAbort(t,{sessionId:n,prompt:v(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const u=b(i);u&&u!==n&&(n=u,this.providerSessionId=u,a.setProviderSession({providerSessionId:u,sessionResponse:this.sessionResponse}))}finally{c&&clearInterval(c),this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:r.command,processPid:r.child.pid??null,provider:"codex-acp",protocol:"acp"},command:r.command,args:r.args,cwd:e.cwd,exitCode:r.child.exitCode,signal:r.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:O(i),promptResponse:i}}async compactContext(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let t=this.providerSessionId,r;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const i=await this.acquireRuntime(e.cwd);this.lease=i,await this.initialize(i),t=await this.establishSession(i,e),await this.applyConfig(i.client,t,e);const c=U(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=b(r);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&_(i,e.signal)){const c=this.lease;this.lease=null,await c.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=l(r)??{};return{providerSessionId:t||this.providerSessionId||"",contextUsage:C(n.contextUsage),maintenance:l(n.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=S();return w.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,command:s.command,args:s.args,onSessionUpdate:t=>this.handleSessionUpdate(t),onClientRequest:async(t,r)=>{if(t==="session/request_permission")return this.requestPermission(r);if(t==="elicitation/create"||t==="session/request_user_input")return this.requestUserInput(r);throw new Error(`Unsupported Codex ACP client request: ${t}`)}})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,r=g(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:r},6e4);this.sessionResponse=l(c);const a=p(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch(c){if(s.requireSessionResume){const a=c instanceof Error?c.message:String(c);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await t.newSession({cwd:s.cwd,config:r},12e4);this.sessionResponse=l(n);const i=p(n);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,s,t){t.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:t.model},6e4).catch(()=>{});const r=f(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=h(t.mode);n&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[i,c]of Object.entries(t.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:s,configId:i,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){this.performanceTracker?.recordEvent(),this.updates.push({method:"session/update",params:e});const s=A(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=x(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=k().slowEventThresholdMs,s=setInterval(()=>{const t=this.performanceTracker,r=Date.now();if(!t?.shouldReportSlow(r,e))return;const n=t.snapshot(r),i=Math.max(1,Math.round(n.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${i} 分钟没有收到新的 ACP 事件`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){const t=e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP prompt cancelled");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP prompt cancelled"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async compactWithAbort(e,s){const t=e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs);if(!s.signal)return t;if(s.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("Codex ACP 上下文压缩已取消");let r=null;const n=new Promise((i,c)=>{r=()=>{e.cancel(s.sessionId),this.lease?.dispose().catch(()=>{}),c(new Error("Codex ACP 上下文压缩已取消"))},s.signal?.addEventListener("abort",r,{once:!0})});try{return await Promise.race([t,n])}finally{r&&s.signal.removeEventListener("abort",r)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s,n=(Array.isArray(t.options)?t.options:[]).map((i,c)=>{const a=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",R(t),"session/request_permission"),this.permissionCallback({requestId:String(t.requestId??t.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s;return this.emitStatus("requestPermission","等待输入",q(t),"elicitation/create"),this.userInputCallback({requestId:String(t.requestId??t.id??`codex-acp-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}emitStatus(e,s,t,r){const n={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},i=this.statusTimeline[this.statusTimeline.length-1];i&&i.phase===n.phase&&i.label===n.label&&i.detail===n.detail&&i.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function _(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function p(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function l(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function h(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function f(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function g(o){return{...o.model?{model:o.model}:{},...f(o.mode)?{approvalPolicy:f(o.mode)}:{},...h(o.mode)?{sandbox:h(o.mode)}:{},...o.configOptionValues??{}}}function R(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return y(t)}}function q(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return y(s)}}function y(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function E(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function U(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function b(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function O(o){const e=l(o),s=l(e?._meta);return C(s?.acaContextUsage)}function C(o){const e=l(o);if(!e)return null;const s=m(e.contextWindow),t=m(e.usedTokens),r=m(e.inputTokens),n=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:t,inputTokens:r,contextWindow:s,ratio:s>0?Math.max(0,m(e.ratio)||t/s):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{N as CodexAcpProvider,M as codexAcpCapabilities};
1
+ import d from"node:fs";import{runAbortableRequest as S}from"../../client/abortable-request.js";import{BoundedItemBuffer as A}from"../../client/bounded-buffer.js";import{promptBlocksToAcpContent as v}from"../../client/acp-content.js";import{eventFromAcpSessionUpdate as x,statusFromAcpSessionUpdate as P}from"../../client/acp-events.js";import{defaultAcpRuntimePool as w}from"../../client/acp-runtime-pool.js";import{resolveCodexAcpLaunch as g}from"./launch.js";import{CodexPromptPerformanceTracker as k,readCodexContextMaintenancePolicy as M}from"./context-maintenance.js";const R={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!0,plan:!0,diff:!0};class W{id="codex-acp";name="Codex ACP";capabilities=R;async createSession(e){return new T(e.cwd,e.providerSessionId??null,e.timeoutMs)}async forkSession(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=e.sourceProviderSessionId.trim(),t=await w.acquire({providerId:"codex-acp",cwd:e.cwd,providerSessionId:s,...g(),onSessionUpdate:()=>{}});try{t.initialized||(await t.client.initialize(6e4),t.markInitialized()),e.onStatus?.({phase:"forking",label:"正在分叉 Codex 会话",detail:s,updateType:"session/fork",atMs:Date.now()});const r=await t.client.forkSession({sessionId:s,cwd:e.cwd,config:y(e)},e.timeoutMs??12e4),n=p(r);if(!n)throw new Error("Codex ACP session/fork did not return sessionId");return{providerSessionId:n}}finally{t.release()}}}class T{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;abortHandler=null;statusTimeline=[];updates=new A(16);content="";performanceTracker=null;constructor(e,s,t=U()){this.cwd=e,this.providerSessionId=s,this.defaultTimeoutMs=t}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.performanceTracker=new k(s);let t=null,r=null,n=this.providerSessionId,i,c=null;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const a=await this.acquireRuntime(e.cwd);this.lease=a,t=a.client,r=a.started,await this.initialize(a),n=await this.establishSession(a,e),await this.applyConfig(t,n,e),this.emitStatus("thinking","思考中"),c=this.startSlowStatusTimer(),i=await this.promptWithAbort(t,{sessionId:n,prompt:v(e.prompt,e.promptBlocks),timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal});const u=C(i);u&&u!==n&&(n=u,this.providerSessionId=u,a.setProviderSession({providerSessionId:u,sessionResponse:this.sessionResponse}))}finally{c&&clearInterval(c),this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"codex",effectiveConfig:{model:e.model??null,mode:e.mode??null,configOptionValues:e.configOptionValues??null,command:r.command,processPid:r.child.pid??null,provider:"codex-acp",protocol:"acp"},command:r.command,args:r.args,cwd:e.cwd,exitCode:r.child.exitCode,signal:r.child.signalCode,durationMs:Date.now()-s,stdout:t.rpc.stdout,stderr:t.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],performance:this.performanceTracker?.snapshot(),contextUsage:D(i),promptResponse:i}}async compactContext(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const s=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.attachAbortSignal(e.signal);let t=this.providerSessionId,r;try{this.emitStatus("initializing","Codex ACP 启动中","codex-acp");const i=await this.acquireRuntime(e.cwd);this.lease=i,await this.initialize(i),t=await this.establishSession(i,e),await this.applyConfig(i.client,t,e);const c=O(e.timeoutMs);r=await this.compactWithAbort(i.client,{sessionId:t,allowRollover:e.allowRollover===!0,timeoutMs:c,signal:e.signal});const a=C(r);a&&a!==t&&(t=a,this.providerSessionId=a,i.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}))}catch(i){if(this.lease&&q(i,e.signal)){const c=this.lease;this.lease=null,await c.dispose().catch(()=>{})}throw this.emitStatus("failed","上下文压缩失败",i instanceof Error?i.message:String(i)),i}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}const n=l(r)??{};return{providerSessionId:t||this.providerSessionId||"",contextUsage:I(n.contextUsage),maintenance:l(n.maintenance)??{},durationMs:Date.now()-s}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const s=g();return w.acquire({providerId:"codex-acp",cwd:e,providerSessionId:this.providerSessionId,command:s.command,args:s.args,onSessionUpdate:t=>this.handleSessionUpdate(t),onClientRequest:async(t,r)=>{if(t==="session/request_permission")return this.requestPermission(r);if(t==="elicitation/create"||t==="session/request_user_input")return this.requestUserInput(r);throw new Error(`Unsupported Codex ACP client request: ${t}`)}})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,s){const t=e.client,r=y(s);if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","Codex ACP 会话恢复中","session/load");const c=await t.loadSession({sessionId:this.providerSessionId,cwd:s.cwd,config:r},6e4);this.sessionResponse=l(c);const a=p(c)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),a}catch(c){if(s.requireSessionResume){const a=c instanceof Error?c.message:String(c);throw new Error(`无法恢复导入的 Codex ACP Session ${this.providerSessionId}:${a}`)}this.providerSessionId=null}this.emitStatus("acp","Codex ACP 会话创建中","session/new");const n=await t.newSession({cwd:s.cwd,config:r},12e4);this.sessionResponse=l(n);const i=p(n);if(!i)throw new Error("Codex ACP session/new did not return sessionId");return this.providerSessionId=i,e.setProviderSession({providerSessionId:i,sessionResponse:this.sessionResponse}),i}async applyConfig(e,s,t){t.model&&await e.setSessionConfigOption({sessionId:s,configId:"model",value:t.model},6e4).catch(()=>{});const r=f(t.mode);r&&await e.setSessionConfigOption({sessionId:s,configId:"approvalPolicy",value:r},6e4).catch(()=>{});const n=h(t.mode);n&&await e.setSessionConfigOption({sessionId:s,configId:"sandbox",value:n},6e4).catch(()=>{});for(const[i,c]of Object.entries(t.configOptionValues??{}))["model","mode","sandbox","approvalPolicy"].includes(i)||await e.setSessionConfigOption({sessionId:s,configId:i,value:c},6e4).catch(()=>{})}handleSessionUpdate(e){this.performanceTracker?.recordEvent(),this.updates.push({method:"session/update",params:e});const s=x(e);s&&(s.type==="agent_message_chunk"&&(this.content+=s.text??""),this.updateCallback?.(s));const t=P(e);t&&this.emitStatus(t.phase,t.label,t.detail,t.updateType)}startSlowStatusTimer(){const e=M().slowEventThresholdMs,s=setInterval(()=>{const t=this.performanceTracker,r=Date.now();if(!t?.shouldReportSlow(r,e))return;const n=t.snapshot(r),i=Math.max(1,Math.round(n.silentForMs/6e4));this.emitStatus("thinking","模型长时间处理中",`已 ${i} 分钟没有收到新的 ACP 事件`,"aca.performance.slow")},Math.min(3e4,Math.max(1e3,Math.floor(e/4))));return s.unref(),s}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("Codex ACP prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,s){return S({signal:s.signal,cancelledMessage:"Codex ACP prompt cancelled",startRequest:()=>e.prompt({sessionId:s.sessionId,prompt:s.prompt},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})}async compactWithAbort(e,s){return S({signal:s.signal,cancelledMessage:"Codex ACP 上下文压缩已取消",startRequest:()=>e.compactSession({sessionId:s.sessionId,allowRollover:s.allowRollover,timeoutMs:s.timeoutMs},s.timeoutMs),onAbort:async()=>{e.cancel(s.sessionId),await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s,n=(Array.isArray(t.options)?t.options:[]).map((i,c)=>{const a=i&&typeof i=="object"&&!Array.isArray(i)?i:{};return{optionId:String(a.optionId??a.id??c),kind:String(a.kind??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",_(t),"session/request_permission"),this.permissionCallback({requestId:String(t.requestId??t.id??`codex-acp-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const s=e&&typeof e=="object"&&!Array.isArray(e)?e:{},t=s.request&&typeof s.request=="object"&&!Array.isArray(s.request)?s.request:s;return this.emitStatus("requestPermission","等待输入",E(t),"elicitation/create"),this.userInputCallback({requestId:String(t.requestId??t.id??`codex-acp-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}emitStatus(e,s,t,r){const n={phase:e,label:s,...t?{detail:t}:{},...r?{updateType:r}:{},atMs:Date.now()},i=this.statusTimeline[this.statusTimeline.length-1];i&&i.phase===n.phase&&i.label===n.label&&i.detail===n.detail&&i.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}releaseLease(){this.lease?.release(),this.lease=null}}function q(o,e){if(e?.aborted)return!0;const s=o instanceof Error?o.message:String(o||"");return/compact.*timed out|request timed out.*compact|adapter closed|aborted|cancelled/i.test(s)}function p(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function l(o){return o&&typeof o=="object"&&!Array.isArray(o)?o:null}function h(o){const e=String(o||"").trim();return e?e==="read-only"?"read-only":["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"danger-full-access":"workspace-write":null}function f(o){const e=String(o||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"never":e==="acceptEdits"||e==="accept-edits"?"on-failure":"on-request":null}function y(o){return{...o.model?{model:o.model}:{},...f(o.mode)?{approvalPolicy:f(o.mode)}:{},...h(o.mode)?{sandbox:h(o.mode)}:{},...o.configOptionValues??{}}}function _(o){const e=o.params&&typeof o.params=="object"&&!Array.isArray(o.params)?o.params:o;for(const s of["command","cwd","reason","grantRoot","method"]){const t=e[s];if(typeof t=="string"&&t.trim())return b(t)}}function E(o){for(const e of["prompt","message","question","title"]){const s=o[e];if(typeof s=="string"&&s.trim())return b(s)}}function b(o){const e=o.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function U(){const o=Number.parseInt(process.env.ACA_CODEX_ACP_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(o)&&o>=3e4?o:360*60*1e3}function O(o){const e=Number.isInteger(o)?Number(o):Number.parseInt(process.env.ACA_CODEX_ACP_COMPACT_TIMEOUT_MS??"",10);return Number.isFinite(e)&&e>=3e4?Math.min(e,6e5):12e4}function C(o){if(!o||typeof o!="object"||Array.isArray(o))return null;const e=o;return typeof e.sessionId=="string"&&e.sessionId.trim()?e.sessionId:null}function D(o){const e=l(o),s=l(e?._meta);return I(s?.acaContextUsage)}function I(o){const e=l(o);if(!e)return null;const s=m(e.contextWindow),t=m(e.usedTokens),r=m(e.inputTokens),n=String(e.state||"unknown"),i=["active","compacted","new_thread"].includes(n)?n:"unknown";return{usedTokens:t,inputTokens:r,contextWindow:s,ratio:s>0?Math.max(0,m(e.ratio)||t/s):0,observedAtMs:m(e.observedAtMs)||Date.now(),state:i,maintenanceAction:typeof e.maintenanceAction=="string"?e.maintenanceAction:null}}function m(o){return typeof o=="number"&&Number.isFinite(o)&&o>=0?o:0}export{W as CodexAcpProvider,R as codexAcpCapabilities};
@@ -1,5 +1,5 @@
1
- import d from"node:fs";import u from"node:path";import{BoundedItemBuffer as I}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as w}from"../../client/context-usage.js";import{defaultAcpRuntimePool as b}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as A,providerStatusFromAcpSessionUpdate as M}from"../standard-acp-events.js";const v={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!1};class H{id="mimocode";name="MimoCode";capabilities=v;async createSession(e){return new x(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class x{cwd;providerSessionId;defaultTimeoutMs;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new I(16);content="";latestContextUsage=null;contextWindow=0;constructor(e,t,s=O()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!d.existsSync(e.cwd)||!d.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=_(e.contextWindow);let s=null,o=null,n=this.providerSessionId,a;try{this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const r=await this.acquireRuntime(e.cwd);this.lease=r,s=r.client,o=r.started,await this.initialize(r),n=await this.establishSession(r,e.cwd),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中"),a=await s.prompt({sessionId:n,prompt:E(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs)}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:e.model||y(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:a}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_MIMO_COMMAND||"mimo",s=["acp","--cwd",e];return b.acquire({providerId:"mimocode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:s,onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n)=>this.handleClientRequest(o,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const a=await s.resumeSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(a);const r=m(a)||this.providerSessionId;return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}catch(a){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const r=await s.loadSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=f(r);const c=m(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{this.appendStderr(`MimoCode resume failed, creating new session: ${p(a)}
2
- `),this.providerSessionId=null}}this.emitStatus("acp","MimoCode 会话创建中","session/new");const o=await s.newSession({cwd:t},12e4);this.acpSessionResponse=f(o);const n=m(o);if(!n)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=n,e.setProviderSession({providerSessionId:n,sessionResponse:this.acpSessionResponse}),n}async applyConfig(e,t,s){const o=h(this.acpSessionResponse,"model")||"model",n=h(this.acpSessionResponse,"mode")||"mode",a=T(s.model||y(),s.configOptionValues,this.acpSessionResponse);a&&await e.setSessionConfigOption({sessionId:t,configId:o,value:a},6e4).catch(c=>{this.appendStderr(`MimoCode model config failed: ${p(c)}
3
- `)});const r=k(s.mode);r&&await e.setSessionMode({sessionId:t,modeId:r},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:t,configId:n,value:r},6e4).catch(c=>{this.appendStderr(`MimoCode mode config failed: ${p(c)}
4
- `)})});for(const[c,g]of Object.entries(s.configOptionValues??{}))["variant","reasoning_effort","effort","model","mode"].includes(c)||await e.setSessionConfigOption({sessionId:t,configId:c,value:g},6e4).catch(C=>{this.appendStderr(`MimoCode config ${c} failed: ${p(C)}
5
- `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=w(e,this.contextWindow)??this.latestContextUsage;const t=A("mimo",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=M("mimo",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath"));return{content:d.readFileSync(t,"utf8")}}writeTextFile(e){const t=S(this.cwd,l(e,"path")||l(e,"filePath")),s=l(e,"content")??l(e,"text")??"";return d.mkdirSync(u.dirname(t),{recursive:!0}),d.writeFileSync(t,s,"utf8"),{}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=(Array.isArray(t.options)?t.options:[]).map((n,a)=>{const r=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(r.optionId??r.id??a),kind:String(r.kind??r.outcome??"choice"),...typeof r.label=="string"?{label:r.label}:{}}});return this.permissionCallback({requestId:String(t.requestId??t.id??`mimo-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{};return this.userInputCallback({requestId:String(t.requestId??t.id??`mimo-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===n.phase&&a.label===n.label&&a.detail===n.detail&&a.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function m(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function f(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function _(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function h(i,e){const t=i?.configOptions;if(!Array.isArray(t))return null;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function T(i,e,t){const s=q(e?.variant,e?.reasoning_effort,e?.effort);if(!s||i.endsWith(`/${s}`))return i;const o=`${i}/${s}`;return R(t,o)?o:i}function R(i,e){const t=i?.configOptions;if(!Array.isArray(t))return!1;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>P(n)===e))return!0}return!1}function P(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const t of["value","id","modelId","model_id"]){const s=e[t];if(typeof s=="string"&&s.trim())return s.trim()}return null}function l(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const t=i[e];return typeof t=="string"&&t.trim()?t:null}function S(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=u.resolve(i,e),s=u.resolve(i);if(t!==s&&!t.startsWith(`${s}${u.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function k(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function q(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function p(i){return i instanceof Error?i.message:String(i)}function y(){return process.env.ACA_MIMO_DEFAULT_MODEL||"mimo/mimo-auto"}function E(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function O(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{H as MimoCodeProvider,v as mimoCodeCapabilities};
1
+ import u from"node:fs";import p from"node:path";import{runAbortableRequest as I}from"../../client/abortable-request.js";import{BoundedItemBuffer as w}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as b}from"../../client/context-usage.js";import{defaultAcpRuntimePool as A}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as M,providerStatusFromAcpSessionUpdate as v}from"../standard-acp-events.js";const x={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!1};class B{id="mimocode";name="MimoCode";capabilities=x;async createSession(e){return new _(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class _{cwd;providerSessionId;defaultTimeoutMs;lease=null;acpSessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new w(16);content="";latestContextUsage=null;contextWindow=0;constructor(e,t,s=U()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!u.existsSync(e.cwd)||!u.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=T(e.contextWindow);let s=null,o=null,n=this.providerSessionId,a;try{this.emitStatus("initializing","MimoCode ACP 启动中","mimo acp");const r=await this.acquireRuntime(e.cwd);this.lease=r,s=r.client,o=r.started,await this.initialize(r),n=await this.establishSession(r,e.cwd),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中");const c=s,d=n;if(!c||!d)throw new Error("MimoCode ACP session is unavailable");a=await I({signal:e.signal,cancelledMessage:"MimoCode prompt cancelled",startRequest:()=>c.prompt({sessionId:d,prompt:O(e.prompt,e.promptBlocks)},e.timeoutMs??this.defaultTimeoutMs),onAbort:async()=>{c.cancel(d),await this.lease?.dispose()}})}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"mimo",effectiveConfig:{model:e.model||g(),mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"mimocode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:a}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_MIMO_COMMAND||"mimo",s=["acp","--cwd",e];return A.acquire({providerId:"mimocode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:s,onSessionUpdate:o=>this.handleSessionUpdate(o),onClientRequest:(o,n)=>this.handleClientRequest(o,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client;if(e.providerSessionId&&e.sessionEstablished)return this.providerSessionId=e.providerSessionId,this.acpSessionResponse=e.sessionResponse,e.providerSessionId;if(this.providerSessionId)try{this.emitStatus("resuming","MimoCode 会话恢复中","session/resume");const a=await s.resumeSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=h(a);const r=f(a)||this.providerSessionId;return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.acpSessionResponse}),r}catch(a){try{this.emitStatus("resuming","MimoCode 会话加载中","session/load");const r=await s.loadSession({sessionId:this.providerSessionId,cwd:t},6e4);this.acpSessionResponse=h(r);const c=f(r)||this.providerSessionId;return this.providerSessionId=c,e.setProviderSession({providerSessionId:c,sessionResponse:this.acpSessionResponse}),c}catch{this.appendStderr(`MimoCode resume failed, creating new session: ${m(a)}
2
+ `),this.providerSessionId=null}}this.emitStatus("acp","MimoCode 会话创建中","session/new");const o=await s.newSession({cwd:t},12e4);this.acpSessionResponse=h(o);const n=f(o);if(!n)throw new Error("MimoCode ACP session/new did not return sessionId");return this.providerSessionId=n,e.setProviderSession({providerSessionId:n,sessionResponse:this.acpSessionResponse}),n}async applyConfig(e,t,s){const o=S(this.acpSessionResponse,"model")||"model",n=S(this.acpSessionResponse,"mode")||"mode",a=R(s.model||g(),s.configOptionValues,this.acpSessionResponse);a&&await e.setSessionConfigOption({sessionId:t,configId:o,value:a},6e4).catch(c=>{this.appendStderr(`MimoCode model config failed: ${m(c)}
3
+ `)});const r=k(s.mode);r&&await e.setSessionMode({sessionId:t,modeId:r},6e4).catch(async()=>{await e.setSessionConfigOption({sessionId:t,configId:n,value:r},6e4).catch(c=>{this.appendStderr(`MimoCode mode config failed: ${m(c)}
4
+ `)})});for(const[c,d]of Object.entries(s.configOptionValues??{}))["variant","reasoning_effort","effort","model","mode"].includes(c)||await e.setSessionConfigOption({sessionId:t,configId:c,value:d},6e4).catch(C=>{this.appendStderr(`MimoCode config ${c} failed: ${m(C)}
5
+ `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=b(e,this.contextWindow)??this.latestContextUsage;const t=M("mimo",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=v("mimo",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported ACP client request: ${e}`)}}readTextFile(e){const t=y(this.cwd,l(e,"path")||l(e,"filePath"));return{content:u.readFileSync(t,"utf8")}}writeTextFile(e){const t=y(this.cwd,l(e,"path")||l(e,"filePath")),s=l(e,"content")??l(e,"text")??"";return u.mkdirSync(p.dirname(t),{recursive:!0}),u.writeFileSync(t,s,"utf8"),{}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},o=(Array.isArray(t.options)?t.options:[]).map((n,a)=>{const r=n&&typeof n=="object"&&!Array.isArray(n)?n:{};return{optionId:String(r.optionId??r.id??a),kind:String(r.kind??r.outcome??"choice"),...typeof r.label=="string"?{label:r.label}:{}}});return this.permissionCallback({requestId:String(t.requestId??t.id??`mimo-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=e&&typeof e=="object"&&!Array.isArray(e)?e:{};return this.userInputCallback({requestId:String(t.requestId??t.id??`mimo-input-${Date.now()}`),prompt:String(t.prompt??t.message??t.question??""),params:e,...typeof t.defaultValue=="string"?{defaultValue:t.defaultValue}:{}})}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("MimoCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},atMs:Date.now()},a=this.statusTimeline[this.statusTimeline.length-1];a&&a.phase===n.phase&&a.label===n.label&&a.detail===n.detail&&a.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}requireClient(){if(!this.lease)throw new Error("MimoCode ACP client is not started");return this.lease.client}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function f(i){if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i.sessionId;return typeof e=="string"&&e.trim()?e.trim():null}function h(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function T(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function S(i,e){const t=i?.configOptions;if(!Array.isArray(t))return null;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(o.category===e&&typeof o.id=="string"&&o.id.trim())return o.id.trim()}return null}function R(i,e,t){const s=E(e?.variant,e?.reasoning_effort,e?.effort);if(!s||i.endsWith(`/${s}`))return i;const o=`${i}/${s}`;return P(t,o)?o:i}function P(i,e){const t=i?.configOptions;if(!Array.isArray(t))return!1;for(const s of t){if(!s||typeof s!="object"||Array.isArray(s))continue;const o=s;if(!(o.category!=="model"||!Array.isArray(o.options))&&o.options.some(n=>q(n)===e))return!0}return!1}function q(i){if(typeof i=="string"&&i.trim())return i.trim();if(!i||typeof i!="object"||Array.isArray(i))return null;const e=i;for(const t of["value","id","modelId","model_id"]){const s=e[t];if(typeof s=="string"&&s.trim())return s.trim()}return null}function l(i,e){if(!i||typeof i!="object"||Array.isArray(i))return null;const t=i[e];return typeof t=="string"&&t.trim()?t:null}function y(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=p.resolve(i,e),s=p.resolve(i);if(t!==s&&!t.startsWith(`${s}${p.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function k(i){const e=String(i||"").trim();return e?["agent","agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function E(...i){for(const e of i)if(typeof e=="string"&&e.trim())return e.trim();return null}function m(i){return i instanceof Error?i.message:String(i)}function g(){return process.env.ACA_MIMO_DEFAULT_MODEL||"mimo/mimo-auto"}function O(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function U(){const i=Number.parseInt(process.env.ACA_MIMO_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{B as MimoCodeProvider,x as mimoCodeCapabilities};
@@ -1,4 +1,4 @@
1
- import p from"node:fs";import m from"node:path";import{BoundedItemBuffer as y}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as x}from"../../client/context-usage.js";import{defaultAcpRuntimePool as E}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as O,providerStatusFromAcpSessionUpdate as P}from"../standard-acp-events.js";const T={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!0};class j{id="opencode";name="OpenCode";capabilities=T;async createSession(e){return new _(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class _{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new y(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,t,s=$()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=q(e.contextWindow);let s=null,n=null,o=this.providerSessionId,r;try{this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const l=await this.acquireRuntime(e.cwd);this.lease=l,s=l.client,n=l.started,await this.initialize(l),o=await this.establishSession(l,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中");const a=D(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(f){if(!this.resumedExistingSession||!U(f))throw f;this.appendStderr(`OpenCode resumed session rejected session/prompt with Invalid params; creating a fresh session and retrying once.
2
- `),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const u=await this.acquireRuntime(e.cwd);this.lease=u,s=u.client,n=u.started,await this.initialize(u),o=await this.establishSession(u,e),await this.applyConfig(s,o,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(s,{sessionId:o,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:o,cliType:"builtin",agentType:"opencode",effectiveConfig:{model:h(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:n.command,processPid:n.child.pid??null,provider:"opencode",protocol:"acp"},command:n.command,args:n.args,cwd:e.cwd,exitCode:n.child.exitCode,signal:n.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_OPENCODE_COMMAND||"opencode";return E.acquire({providerId:"opencode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:["acp","--cwd",e],onSessionUpdate:s=>this.handleSessionUpdate(s),onClientRequest:(s,n)=>this.handleClientRequest(s,n)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,n=R(t);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished){if(S(e.sessionResponse,t.model))return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.resumedExistingSession=!0,e.providerSessionId;this.providerSessionId=null}else if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const l=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd},6e4);if(this.sessionResponse=d(l),S(this.sessionResponse,t.model)){const a=g(l)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,a}this.appendStderr(`OpenCode model changed from ${C(this.sessionResponse)??"unknown"} to ${h(t.model)||"default"}, creating new session
3
- `),this.providerSessionId=null}catch{this.providerSessionId=null}this.emitStatus("acp","OpenCode 会话创建中","session/new");const o=await s.newSession({cwd:t.cwd,config:n},12e4);this.sessionResponse=d(o);const r=g(o);if(!r)throw new Error("OpenCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.sessionResponse}),r}async applyConfig(e,t,s){const n=w(s.mode);n&&await e.setSessionMode({sessionId:t,modeId:n},6e4).catch(o=>{this.appendStderr(`OpenCode mode config failed: ${v(o)}
4
- `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=x(e,this.contextWindow)??this.latestContextUsage;const t=O("opencode",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=P("opencode",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath"));return{content:p.readFileSync(t,"utf8")}}writeTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath")),s=c(e,"content")??c(e,"text")??"";return p.mkdirSync(m.dirname(t),{recursive:!0}),p.writeFileSync(t,s,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){const s=e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs);if(!t.signal)return s;if(t.signal.aborted)throw await this.lease?.dispose().catch(()=>{}),new Error("OpenCode prompt cancelled");let n=null;const o=new Promise((r,l)=>{n=()=>{e.cancel(t.sessionId),this.lease?.dispose().catch(()=>{}),l(new Error("OpenCode prompt cancelled"))},t.signal?.addEventListener("abort",n,{once:!0})});try{return await Promise.race([s,o])}finally{n&&t.signal.removeEventListener("abort",n)}}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t,o=(Array.isArray(s.options)?s.options:[]).map((r,l)=>{const a=d(r)??{};return{optionId:String(a.optionId??a.id??l),kind:String(a.kind??a.outcome??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",M(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`opencode-permission-${Date.now()}`),params:e,options:o})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=d(e)??{},s=d(t.request)??t;return this.emitStatus("requestPermission","等待输入",k(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`opencode-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,n){const o={phase:e,label:t,...s?{detail:s}:{},...n?{updateType:n}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===o.phase&&r.label===o.label&&r.detail===o.detail&&r.updateType===o.updateType||(this.statusTimeline.push(o),this.statusCallback?.(o))}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function g(i){const t=d(i)?.sessionId;return typeof t=="string"&&t.trim()?t.trim():null}function d(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function q(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function R(i){const e=h(i.model),t=w(i.mode);return{...A(i.configOptionValues),...e?{model:e}:{},...t?{mode:t}:{}}}function A(i){const e={};for(const[t,s]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(t)||(e[t]=s);return e}function S(i,e){const t=h(e);if(!t)return!0;const s=C(i);return!s||s===t}function C(i){const t=d(i?.models)?.currentModelId;return typeof t=="string"&&t.trim()?t.trim():null}function w(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function c(i,e){const s=d(i)?.[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=m.resolve(i,e),s=m.resolve(i);if(t!==s&&!t.startsWith(`${s}${m.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function M(i){const e=d(i.params)??i;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return b(s)}}function k(i){for(const e of["prompt","message","question","title"]){const t=i[e];if(typeof t=="string"&&t.trim())return b(t)}}function b(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function v(i){return i instanceof Error?i.message:String(i)}function U(i){return/\binvalid params\b/i.test(v(i))}function D(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function F(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function h(i){const e=String(i||F()).trim();return e==="default"||e==="opencode-default"?"":e}function $(){const i=Number.parseInt(process.env.ACA_OPENCODE_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{j as OpenCodeProvider,T as openCodeCapabilities};
1
+ import p from"node:fs";import m from"node:path";import{runAbortableRequest as v}from"../../client/abortable-request.js";import{BoundedItemBuffer as x}from"../../client/bounded-buffer.js";import{contextUsageFromAcpSessionUpdate as O}from"../../client/context-usage.js";import{defaultAcpRuntimePool as q}from"../../client/acp-runtime-pool.js";import{providerEventFromAcpSessionUpdate as E,providerStatusFromAcpSessionUpdate as R}from"../standard-acp-events.js";const T={sessionResume:!0,imageInput:!0,fileAttachment:!0,filesystem:!0,terminal:!0,permissionRequest:!0,configOptions:!0,usage:!0,contextUsage:!0,contextCompaction:!1,plan:!0,diff:!0};class G{id="opencode";name="OpenCode";capabilities=T;async createSession(e){return new A(e.cwd,e.providerSessionId??null,e.timeoutMs)}}class A{cwd;providerSessionId;defaultTimeoutMs;lease=null;sessionResponse=null;statusCallback=null;updateCallback=null;permissionCallback=null;userInputCallback=null;canExecuteTools=!0;abortHandler=null;statusTimeline=[];updates=new x(16);content="";latestContextUsage=null;contextWindow=0;resumedExistingSession=!1;constructor(e,t,s=z()){this.cwd=e,this.providerSessionId=t,this.defaultTimeoutMs=s}async sendPrompt(e){if(!p.existsSync(e.cwd)||!p.statSync(e.cwd).isDirectory())throw new Error(`Project root does not exist or is not a directory: ${e.cwd}`);const t=Date.now();this.statusCallback=e.onStatus??null,this.updateCallback=e.onUpdate??null,this.permissionCallback=e.onPermissionRequest??null,this.userInputCallback=e.onUserInputRequest??null,this.canExecuteTools=e.canExecuteTools!==!1,this.attachAbortSignal(e.signal),this.content="",this.updates.clear(),this.statusTimeline.length=0,this.latestContextUsage=null,this.contextWindow=M(e.contextWindow);let s=null,o=null,n=this.providerSessionId,r;try{this.emitStatus("initializing","OpenCode ACP 启动中","opencode acp");const d=await this.acquireRuntime(e.cwd);this.lease=d,s=d.client,o=d.started,await this.initialize(d),n=await this.establishSession(d,e),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中");const a=F(e.prompt,e.promptBlocks);try{r=await this.promptWithAbort(s,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}catch(f){if(!this.resumedExistingSession||!D(f))throw f;this.appendStderr(`OpenCode resumed session rejected session/prompt with Invalid params; creating a fresh session and retrying once.
2
+ `),await this.lease?.dispose().catch(()=>{}),this.lease=null,this.providerSessionId=null,this.sessionResponse=null,this.resumedExistingSession=!1,this.emitStatus("resuming","OpenCode 会话已失效,正在新建","session/prompt invalid params");const u=await this.acquireRuntime(e.cwd);this.lease=u,s=u.client,o=u.started,await this.initialize(u),n=await this.establishSession(u,e),await this.applyConfig(s,n,e),this.emitStatus("thinking","思考中"),r=await this.promptWithAbort(s,{sessionId:n,prompt:a,timeoutMs:e.timeoutMs??this.defaultTimeoutMs,signal:e.signal})}}finally{this.abortHandler&&e.signal&&(e.signal.removeEventListener("abort",this.abortHandler),this.abortHandler=null),this.releaseLease()}return this.emitStatus("completed","已完成"),{providerSessionId:n,cliType:"builtin",agentType:"opencode",effectiveConfig:{model:h(e.model)||null,mode:e.mode||null,configOptionValues:e.configOptionValues??null,command:o.command,processPid:o.child.pid??null,provider:"opencode",protocol:"acp"},command:o.command,args:o.args,cwd:e.cwd,exitCode:o.child.exitCode,signal:o.child.signalCode,durationMs:Date.now()-t,stdout:s.rpc.stdout,stderr:s.rpc.stderr,content:this.content.trim(),updateCount:this.updates.count(),updates:this.updates.snapshot(),statusTimeline:[...this.statusTimeline],contextUsage:this.latestContextUsage,promptResponse:r}}async cancel(){this.providerSessionId&&this.lease?.client.cancel(this.providerSessionId)}async close(){this.statusCallback=null,this.updateCallback=null,this.permissionCallback=null,this.userInputCallback=null,this.canExecuteTools=!0,this.abortHandler&&(this.abortHandler=null),this.releaseLease()}async acquireRuntime(e){const t=process.env.ACA_OPENCODE_COMMAND||"opencode";return q.acquire({providerId:"opencode",cwd:e,providerSessionId:this.providerSessionId,command:t,args:["acp","--cwd",e],onSessionUpdate:s=>this.handleSessionUpdate(s),onClientRequest:(s,o)=>this.handleClientRequest(s,o)})}async initialize(e){e.initialized||(await e.client.initialize(6e4),e.markInitialized())}async establishSession(e,t){const s=e.client,o=P(t);if(this.resumedExistingSession=!1,e.providerSessionId&&e.sessionEstablished){if(S(e.sessionResponse,t.model))return this.providerSessionId=e.providerSessionId,this.sessionResponse=e.sessionResponse,this.resumedExistingSession=!0,e.providerSessionId;this.providerSessionId=null}else if(this.providerSessionId)try{this.emitStatus("resuming","OpenCode 会话恢复中","session/load");const d=await s.loadSession({sessionId:this.providerSessionId,cwd:t.cwd},6e4);if(this.sessionResponse=l(d),S(this.sessionResponse,t.model)){const a=g(d)||this.providerSessionId;return this.providerSessionId=a,e.setProviderSession({providerSessionId:a,sessionResponse:this.sessionResponse}),this.resumedExistingSession=!0,a}this.appendStderr(`OpenCode model changed from ${C(this.sessionResponse)??"unknown"} to ${h(t.model)||"default"}, creating new session
3
+ `),this.providerSessionId=null}catch{this.providerSessionId=null}this.emitStatus("acp","OpenCode 会话创建中","session/new");const n=await s.newSession({cwd:t.cwd,config:o},12e4);this.sessionResponse=l(n);const r=g(n);if(!r)throw new Error("OpenCode ACP session/new did not return sessionId");return this.providerSessionId=r,e.setProviderSession({providerSessionId:r,sessionResponse:this.sessionResponse}),r}async applyConfig(e,t,s){const o=w(s.mode);o&&await e.setSessionMode({sessionId:t,modeId:o},6e4).catch(n=>{this.appendStderr(`OpenCode mode config failed: ${y(n)}
4
+ `)})}handleSessionUpdate(e){this.updates.push({method:"session/update",params:e}),this.latestContextUsage=O(e,this.contextWindow)??this.latestContextUsage;const t=E("opencode",e);t&&(t.type==="agent_message_chunk"&&(this.content+=t.text??""),this.updateCallback?.(t));const s=R("opencode",e);s&&this.emitStatus(s.phase,s.label,s.detail,s.updateType)}async handleClientRequest(e,t){if(!this.canExecuteTools&&(e==="fs/read_text_file"||e==="fs/write_text_file"))throw new Error(`Chat Room role policy disabled tool execution: ${e}`);switch(e){case"fs/read_text_file":return this.readTextFile(t);case"fs/write_text_file":return this.writeTextFile(t);case"session/request_permission":return this.requestPermission(t);case"elicitation/create":case"session/request_user_input":return this.requestUserInput(t);default:throw new Error(`Unsupported OpenCode ACP client request: ${e}`)}}readTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath"));return{content:p.readFileSync(t,"utf8")}}writeTextFile(e){const t=I(this.cwd,c(e,"path")||c(e,"filePath")),s=c(e,"content")??c(e,"text")??"";return p.mkdirSync(m.dirname(t),{recursive:!0}),p.writeFileSync(t,s,"utf8"),{}}attachAbortSignal(e){if(e){if(this.abortHandler=()=>{this.cancel()},e.aborted)throw new Error("OpenCode prompt cancelled");e.addEventListener("abort",this.abortHandler,{once:!0})}}async promptWithAbort(e,t){return v({signal:t.signal,cancelledMessage:"OpenCode prompt cancelled",startRequest:()=>e.prompt({sessionId:t.sessionId,prompt:t.prompt},t.timeoutMs),onAbort:async()=>{e.cancel(t.sessionId),await this.lease?.dispose()}})}async requestPermission(e){if(!this.permissionCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},s=l(t.request)??t,n=(Array.isArray(s.options)?s.options:[]).map((r,d)=>{const a=l(r)??{};return{optionId:String(a.optionId??a.id??d),kind:String(a.kind??a.outcome??"choice"),...typeof a.label=="string"?{label:a.label}:{}}});return this.emitStatus("requestPermission","等待授权",k(s),"session/request_permission"),this.permissionCallback({requestId:String(s.requestId??s.id??`opencode-permission-${Date.now()}`),params:e,options:n})}async requestUserInput(e){if(!this.userInputCallback)return{outcome:{outcome:"cancelled"}};const t=l(e)??{},s=l(t.request)??t;return this.emitStatus("requestPermission","等待输入",U(s),"elicitation/create"),this.userInputCallback({requestId:String(s.requestId??s.id??`opencode-input-${Date.now()}`),prompt:String(s.prompt??s.message??s.question??""),params:e,...typeof s.defaultValue=="string"?{defaultValue:s.defaultValue}:{}})}emitStatus(e,t,s,o){const n={phase:e,label:t,...s?{detail:s}:{},...o?{updateType:o}:{},atMs:Date.now()},r=this.statusTimeline[this.statusTimeline.length-1];r&&r.phase===n.phase&&r.label===n.label&&r.detail===n.detail&&r.updateType===n.updateType||(this.statusTimeline.push(n),this.statusCallback?.(n))}appendStderr(e){const t=this.lease?.client;t&&(t.rpc.stderr+=e)}releaseLease(){this.lease?.release(),this.lease=null}}function g(i){const t=l(i)?.sessionId;return typeof t=="string"&&t.trim()?t.trim():null}function l(i){return i&&typeof i=="object"&&!Array.isArray(i)?i:null}function M(i){return typeof i=="number"&&Number.isFinite(i)&&i>0?i:0}function P(i){const e=h(i.model),t=w(i.mode);return{..._(i.configOptionValues),...e?{model:e}:{},...t?{mode:t}:{}}}function _(i){const e={};for(const[t,s]of Object.entries(i??{}))["model","mode","reasoning_effort","effort","variant"].includes(t)||(e[t]=s);return e}function S(i,e){const t=h(e);if(!t)return!0;const s=C(i);return!s||s===t}function C(i){const t=l(i?.models)?.currentModelId;return typeof t=="string"&&t.trim()?t.trim():null}function w(i){const e=String(i||"").trim();return e?["agent-full-access","full-access","bypassPermissions","danger-full-access"].includes(e)?"build":e==="read-only"?"plan":e:null}function c(i,e){const s=l(i)?.[e];return typeof s=="string"&&s.trim()?s:null}function I(i,e){if(!e)throw new Error("ACP file request did not include a path");const t=m.resolve(i,e),s=m.resolve(i);if(t!==s&&!t.startsWith(`${s}${m.sep}`))throw new Error(`ACP file request is outside project root: ${e}`);return t}function k(i){const e=l(i.params)??i;for(const t of["command","cwd","reason","grantRoot","method"]){const s=e[t];if(typeof s=="string"&&s.trim())return b(s)}}function U(i){for(const e of["prompt","message","question","title"]){const t=i[e];if(typeof t=="string"&&t.trim())return b(t)}}function b(i){const e=i.replace(/\s+/g," ").trim();return e.length>120?`${e.slice(0,117)}...`:e}function y(i){return i instanceof Error?i.message:String(i)}function D(i){return/\binvalid params\b/i.test(y(i))}function F(i,e){return(e?.length?e:[{type:"text",text:i}]).map(s=>s.type==="image"?{type:"image",data:s.data,mimeType:s.mimeType}:{type:"text",text:s.text})}function $(){return process.env.ACA_OPENCODE_DEFAULT_MODEL||""}function h(i){const e=String(i||$()).trim();return e==="default"||e==="opencode-default"?"":e}function z(){const i=Number.parseInt(process.env.ACA_OPENCODE_PROMPT_TIMEOUT_MS??process.env.ACA_ACP_PROMPT_TIMEOUT_MS??"",10);return Number.isInteger(i)&&i>=3e4?i:360*60*1e3}export{G as OpenCodeProvider,T as openCodeCapabilities};
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{Command as a}from"commander";import m from"node:path";import{createStartCommand as n}from"./commands/start.js";import{createDaemonCommand as t}from"./commands/daemon.js";import{createWorkerAuthCommand as s}from"./commands/worker-auth.js";import{createWorkerCodexCommand as c}from"./commands/worker-codex.js";import{createWorkerProjectCommand as i}from"./commands/worker-project.js";import{DEFAULT_ACA_SERVER_URL as d}from"./core/aca-config.js";process.env.ACA_DEFAULT_SERVER_URL||=d;async function p(r=process.argv){const o=new a;o.name("aica").description("AICA remote Agent worker").version("0.1.21").enablePositionalOptions(),o.addCommand(n()),o.addCommand(t()),o.addCommand(s()),o.addCommand(i()),o.addCommand(c()),await o.parseAsync(r)}const C=process.argv[1]||"",e=m.basename(C).replace(/\.(?:js|ts|cjs|mjs)$/i,"");(e==="worker-cli"||e==="aica"||e==="aca")&&p().catch(r=>{const o=r instanceof Error?r.message:String(r);console.error(o),process.exitCode=1});export{p as runWorkerCli};
2
+ import{Command as a}from"commander";import m from"node:path";import{createStartCommand as n}from"./commands/start.js";import{createDaemonCommand as t}from"./commands/daemon.js";import{createWorkerAuthCommand as s}from"./commands/worker-auth.js";import{createWorkerCodexCommand as c}from"./commands/worker-codex.js";import{createWorkerProjectCommand as i}from"./commands/worker-project.js";import{DEFAULT_ACA_SERVER_URL as d}from"./core/aca-config.js";process.env.ACA_DEFAULT_SERVER_URL||=d;async function p(r=process.argv){const o=new a;o.name("aica").description("AICA remote Agent worker").version("0.1.22").enablePositionalOptions(),o.addCommand(n()),o.addCommand(t()),o.addCommand(s()),o.addCommand(i()),o.addCommand(c()),await o.parseAsync(r)}const C=process.argv[1]||"",e=m.basename(C).replace(/\.(?:js|ts|cjs|mjs)$/i,"");(e==="worker-cli"||e==="aica"||e==="aca")&&p().catch(r=>{const o=r instanceof Error?r.message:String(r);console.error(o),process.exitCode=1});export{p as runWorkerCli};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yuandc/aica",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "AICA remote Agent worker",
5
5
  "author": "yuandc",
6
6
  "type": "module",