@openadapter/koda-agent-core 1.0.0-beta.3
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/README.md +488 -0
- package/dist/agent-loop.d.ts +23 -0
- package/dist/agent-loop.js +1 -0
- package/dist/agent.d.ts +117 -0
- package/dist/agent.js +1 -0
- package/dist/harness/agent-harness.d.ts +94 -0
- package/dist/harness/agent-harness.js +1 -0
- package/dist/harness/compaction/branch-summarization.d.ts +52 -0
- package/dist/harness/compaction/branch-summarization.js +38 -0
- package/dist/harness/compaction/compaction.d.ts +94 -0
- package/dist/harness/compaction/compaction.js +106 -0
- package/dist/harness/compaction/utils.d.ts +24 -0
- package/dist/harness/compaction/utils.js +17 -0
- package/dist/harness/env/nodejs.d.ts +50 -0
- package/dist/harness/env/nodejs.js +1 -0
- package/dist/harness/messages.d.ts +50 -0
- package/dist/harness/messages.js +17 -0
- package/dist/harness/prompt-templates.d.ts +47 -0
- package/dist/harness/prompt-templates.js +5 -0
- package/dist/harness/session/jsonl-repo.d.ts +25 -0
- package/dist/harness/session/jsonl-repo.js +1 -0
- package/dist/harness/session/jsonl-storage.d.ts +32 -0
- package/dist/harness/session/jsonl-storage.js +5 -0
- package/dist/harness/session/memory-repo.d.ts +17 -0
- package/dist/harness/session/memory-repo.js +1 -0
- package/dist/harness/session/memory-storage.d.ts +24 -0
- package/dist/harness/session/memory-storage.js +1 -0
- package/dist/harness/session/repo-utils.d.ts +10 -0
- package/dist/harness/session/repo-utils.js +1 -0
- package/dist/harness/session/session.d.ts +32 -0
- package/dist/harness/session/session.js +1 -0
- package/dist/harness/session/uuid.d.ts +1 -0
- package/dist/harness/session/uuid.js +1 -0
- package/dist/harness/skills.d.ts +43 -0
- package/dist/harness/skills.js +10 -0
- package/dist/harness/system-prompt.d.ts +2 -0
- package/dist/harness/system-prompt.js +2 -0
- package/dist/harness/types.d.ts +614 -0
- package/dist/harness/types.js +1 -0
- package/dist/harness/utils/shell-output.d.ts +13 -0
- package/dist/harness/utils/shell-output.js +1 -0
- package/dist/harness/utils/truncate.d.ts +69 -0
- package/dist/harness/utils/truncate.js +5 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +1 -0
- package/dist/node.d.ts +2 -0
- package/dist/node.js +1 -0
- package/dist/proxy.d.ts +68 -0
- package/dist/proxy.js +2 -0
- package/dist/types.d.ts +392 -0
- package/dist/types.js +0 -0
- package/package.json +68 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type AssistantMessage, type ImageContent, type Model } from "@openadapter/koda-ai";
|
|
2
|
+
import type { AgentMessage, AgentTool, QueueMode, ThinkingLevel } from "../types.ts";
|
|
3
|
+
import type { AbortResult, AgentHarnessEvent, AgentHarnessEventResultMap, AgentHarnessOptions, AgentHarnessOwnEvent, AgentHarnessResources, AgentHarnessStreamOptions, ExecutionEnv, NavigateTreeResult, PromptTemplate, Skill } from "./types.ts";
|
|
4
|
+
export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate, TTool extends AgentTool = AgentTool> {
|
|
5
|
+
readonly env: ExecutionEnv;
|
|
6
|
+
private session;
|
|
7
|
+
private phase;
|
|
8
|
+
private runAbortController?;
|
|
9
|
+
private runPromise?;
|
|
10
|
+
private pendingSessionWrites;
|
|
11
|
+
private model;
|
|
12
|
+
private thinkingLevel;
|
|
13
|
+
private systemPrompt;
|
|
14
|
+
private streamOptions;
|
|
15
|
+
private getApiKeyAndHeaders?;
|
|
16
|
+
private resources;
|
|
17
|
+
private tools;
|
|
18
|
+
private activeToolNames;
|
|
19
|
+
private steerQueue;
|
|
20
|
+
private steeringQueueMode;
|
|
21
|
+
private followUpQueue;
|
|
22
|
+
private followUpQueueMode;
|
|
23
|
+
private nextTurnQueue;
|
|
24
|
+
private handlers;
|
|
25
|
+
constructor(options: AgentHarnessOptions<TSkill, TPromptTemplate, TTool>);
|
|
26
|
+
private getHandlers;
|
|
27
|
+
private emitOwn;
|
|
28
|
+
private emitAny;
|
|
29
|
+
private emitHook;
|
|
30
|
+
private emitBeforeProviderRequest;
|
|
31
|
+
private emitBeforeProviderPayload;
|
|
32
|
+
private emitQueueUpdate;
|
|
33
|
+
private startRunPromise;
|
|
34
|
+
private createTurnState;
|
|
35
|
+
private createContext;
|
|
36
|
+
private createStreamFn;
|
|
37
|
+
private drainQueuedMessages;
|
|
38
|
+
private createLoopConfig;
|
|
39
|
+
private validateUniqueNames;
|
|
40
|
+
private validateToolNames;
|
|
41
|
+
private flushPendingSessionWrites;
|
|
42
|
+
private handleAgentEvent;
|
|
43
|
+
private emitRunFailure;
|
|
44
|
+
private executeTurn;
|
|
45
|
+
prompt(text: string, options?: {
|
|
46
|
+
images?: ImageContent[];
|
|
47
|
+
}): Promise<AssistantMessage>;
|
|
48
|
+
skill(name: string, additionalInstructions?: string): Promise<AssistantMessage>;
|
|
49
|
+
promptFromTemplate(name: string, args?: string[]): Promise<AssistantMessage>;
|
|
50
|
+
steer(text: string, options?: {
|
|
51
|
+
images?: ImageContent[];
|
|
52
|
+
}): Promise<void>;
|
|
53
|
+
followUp(text: string, options?: {
|
|
54
|
+
images?: ImageContent[];
|
|
55
|
+
}): Promise<void>;
|
|
56
|
+
nextTurn(text: string, options?: {
|
|
57
|
+
images?: ImageContent[];
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
appendMessage(message: AgentMessage): Promise<void>;
|
|
60
|
+
compact(customInstructions?: string): Promise<{
|
|
61
|
+
summary: string;
|
|
62
|
+
firstKeptEntryId: string;
|
|
63
|
+
tokensBefore: number;
|
|
64
|
+
details?: unknown;
|
|
65
|
+
}>;
|
|
66
|
+
navigateTree(targetId: string, options?: {
|
|
67
|
+
summarize?: boolean;
|
|
68
|
+
customInstructions?: string;
|
|
69
|
+
replaceInstructions?: boolean;
|
|
70
|
+
label?: string;
|
|
71
|
+
}): Promise<NavigateTreeResult>;
|
|
72
|
+
getModel(): Model<any>;
|
|
73
|
+
setModel(model: Model<any>): Promise<void>;
|
|
74
|
+
getThinkingLevel(): ThinkingLevel;
|
|
75
|
+
setThinkingLevel(level: ThinkingLevel): Promise<void>;
|
|
76
|
+
getTools(): TTool[];
|
|
77
|
+
setTools(tools: TTool[], activeToolNames?: string[]): Promise<void>;
|
|
78
|
+
getActiveTools(): TTool[];
|
|
79
|
+
setActiveTools(toolNames: string[]): Promise<void>;
|
|
80
|
+
getSteeringMode(): QueueMode;
|
|
81
|
+
setSteeringMode(mode: QueueMode): Promise<void>;
|
|
82
|
+
getFollowUpMode(): QueueMode;
|
|
83
|
+
setFollowUpMode(mode: QueueMode): Promise<void>;
|
|
84
|
+
getResources(): AgentHarnessResources<TSkill, TPromptTemplate>;
|
|
85
|
+
setResources(resources: AgentHarnessResources<TSkill, TPromptTemplate>): Promise<void>;
|
|
86
|
+
getStreamOptions(): AgentHarnessStreamOptions;
|
|
87
|
+
setStreamOptions(streamOptions: AgentHarnessStreamOptions): Promise<void>;
|
|
88
|
+
abort(): Promise<AbortResult>;
|
|
89
|
+
waitForIdle(): Promise<void>;
|
|
90
|
+
subscribe(listener: (event: AgentHarnessEvent<TSkill, TPromptTemplate>, signal?: AbortSignal) => Promise<void> | void): () => void;
|
|
91
|
+
on<TType extends keyof AgentHarnessEventResultMap>(type: TType, handler: (event: Extract<AgentHarnessOwnEvent, {
|
|
92
|
+
type: TType;
|
|
93
|
+
}>) => Promise<AgentHarnessEventResultMap[TType]> | AgentHarnessEventResultMap[TType]): () => void;
|
|
94
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var O=Object.defineProperty;var c=(o,e)=>O(o,"name",{value:e,configurable:!0});import{streamSimple as R}from"@openadapter/koda-ai";import{runAgentLoop as x}from"../agent-loop.js";import{collectEntriesForBranchSummary as N,generateBranchSummary as P}from"./compaction/branch-summarization.js";import{compact as Q,DEFAULT_COMPACTION_SETTINGS as E,prepareCompaction as C}from"./compaction/compaction.js";import{convertToLlm as U}from"./messages.js";import{formatPromptTemplateInvocation as L}from"./prompt-templates.js";import{formatSkillInvocation as I}from"./skills.js";import{AgentHarnessError as l,BranchSummaryError as H,CompactionError as F,SessionError as W,toError as _}from"./types.js";function M(o,e){const t=[{type:"text",text:o}];return e&&t.push(...e),{role:"user",content:t,timestamp:Date.now()}}c(M,"createUserMessage");function j(o,e,t){return{role:"assistant",content:[{type:"text",text:""}],api:o.api,provider:o.provider,model:o.id,stopReason:t?"aborted":"error",errorMessage:e instanceof Error?e.message:String(e),timestamp:Date.now(),usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}}}}c(j,"createFailureMessage");function T(o){return{...o,headers:o?.headers?{...o.headers}:void 0,metadata:o?.metadata?{...o.metadata}:void 0}}c(T,"cloneStreamOptions");function D(...o){const e={};let t=!1;for(const i of o)i&&(Object.assign(e,i),t=!0);return t?e:void 0}c(D,"mergeHeaders");function q(o){const e=new Set,t=new Set;for(const i of o)e.has(i)&&t.add(i),e.add(i);return[...t]}c(q,"findDuplicateNames");function K(o,e){const t=T(o);if(!e)return t;if(Object.hasOwn(e,"transport")&&(t.transport=e.transport),Object.hasOwn(e,"timeoutMs")&&(t.timeoutMs=e.timeoutMs),Object.hasOwn(e,"maxRetries")&&(t.maxRetries=e.maxRetries),Object.hasOwn(e,"maxRetryDelayMs")&&(t.maxRetryDelayMs=e.maxRetryDelayMs),Object.hasOwn(e,"cacheRetention")&&(t.cacheRetention=e.cacheRetention),Object.hasOwn(e,"headers"))if(e.headers===void 0)t.headers=void 0;else{const i={...t.headers??{}};for(const[s,n]of Object.entries(e.headers))n===void 0?delete i[s]:i[s]=n;t.headers=Object.keys(i).length>0?i:void 0}if(Object.hasOwn(e,"metadata"))if(e.metadata===void 0)t.metadata=void 0;else{const i={...t.metadata??{}};for(const[s,n]of Object.entries(e.metadata))n===void 0?delete i[s]:i[s]=n;t.metadata=Object.keys(i).length>0?i:void 0}return t}c(K,"applyStreamOptionsPatch");const S="*";function y(o,e){if(o instanceof l)return o;const t=_(o);return t instanceof W?new l("session",t.message,t):t instanceof F?new l("compaction",t.message,t):t instanceof H?new l("branch_summary",t.message,t):new l(e,t.message,t)}c(y,"normalizeHarnessError");function k(o){return y(o,"hook")}c(k,"normalizeHookError");class ee{static{c(this,"AgentHarness")}env;session;phase="idle";runAbortController;runPromise;pendingSessionWrites=[];model;thinkingLevel;systemPrompt;streamOptions;getApiKeyAndHeaders;resources;tools=new Map;activeToolNames;steerQueue=[];steeringQueueMode;followUpQueue=[];followUpQueueMode;nextTurnQueue=[];handlers=new Map;constructor(e){this.env=e.env,this.session=e.session,this.resources=e.resources??{},this.streamOptions=T(e.streamOptions),this.systemPrompt=e.systemPrompt,this.getApiKeyAndHeaders=e.getApiKeyAndHeaders,this.validateUniqueNames((e.tools??[]).map(t=>t.name),"Duplicate tool name(s)");for(const t of e.tools??[])this.tools.set(t.name,t);this.model=e.model,this.thinkingLevel=e.thinkingLevel??"off",this.activeToolNames=e.activeToolNames?[...e.activeToolNames]:(e.tools??[]).map(t=>t.name),this.validateUniqueNames(this.activeToolNames,"Duplicate active tool name(s)"),this.validateToolNames(this.activeToolNames),this.steeringQueueMode=e.steeringMode??"one-at-a-time",this.followUpQueueMode=e.followUpMode??"one-at-a-time"}getHandlers(e){return this.handlers.get(e)}async emitOwn(e,t){for(const i of this.getHandlers(S)??[])try{await i(e,t)}catch(s){throw k(s)}}async emitAny(e,t){for(const i of this.getHandlers(S)??[])try{await i(e,t)}catch(s){throw k(s)}}async emitHook(e){const t=this.getHandlers(e.type);if(!t||t.size===0)return;let i;for(const s of t)try{const n=await s(e);n!==void 0&&(i=n)}catch(n){throw k(n)}return i}async emitBeforeProviderRequest(e,t,i){const s=this.getHandlers("before_provider_request");let n=T(i);if(!s||s.size===0)return n;for(const a of s)try{const r=await a({type:"before_provider_request",model:e,sessionId:t,streamOptions:T(n)});r?.streamOptions&&(n=K(n,r.streamOptions))}catch(r){throw k(r)}return n}async emitBeforeProviderPayload(e,t){const i=this.getHandlers("before_provider_payload");let s=t;if(!i||i.size===0)return s;for(const n of i)try{const a=await n({type:"before_provider_payload",model:e,payload:s});a!==void 0&&(s=a.payload)}catch(a){throw k(a)}return s}async emitQueueUpdate(){await this.emitOwn({type:"queue_update",steer:[...this.steerQueue],followUp:[...this.followUpQueue],nextTurn:[...this.nextTurnQueue]})}startRunPromise(){let e=c(()=>{},"finish");return this.runPromise=new Promise(t=>{e=t}),()=>{this.runPromise=void 0,e()}}async createTurnState(){const e=await this.session.buildContext(),t=this.getResources(),i=await this.session.getMetadata(),s=[...this.tools.values()],n=this.activeToolNames.map(r=>this.tools.get(r)).filter(r=>r!==void 0);let a="You are a helpful assistant.";return typeof this.systemPrompt=="string"?a=this.systemPrompt:this.systemPrompt&&(a=await this.systemPrompt({env:this.env,session:this.session,model:this.model,thinkingLevel:this.thinkingLevel,activeTools:n,resources:t})),{messages:e.messages,resources:t,streamOptions:T(this.streamOptions),sessionId:i.id,systemPrompt:a,model:this.model,thinkingLevel:this.thinkingLevel,tools:s,activeTools:n}}createContext(e,t){return{systemPrompt:t??e.systemPrompt,messages:e.messages.slice(),tools:e.activeTools.slice()}}createStreamFn(e){return async(t,i,s)=>{const n=e(),a=await this.getApiKeyAndHeaders?.(t),r={...n.streamOptions,headers:D(n.streamOptions.headers,a?.headers)},h=await this.emitBeforeProviderRequest(t,n.sessionId,r);return R(t,i,{cacheRetention:h.cacheRetention,headers:h.headers,maxRetries:h.maxRetries,maxRetryDelayMs:h.maxRetryDelayMs,metadata:h.metadata,onPayload:c(async m=>await this.emitBeforeProviderPayload(t,m),"onPayload"),onResponse:c(async m=>{const p={...m.headers};await this.emitOwn({type:"after_provider_response",status:m.status,headers:p},s?.signal)},"onResponse"),reasoning:s?.reasoning,signal:s?.signal,sessionId:n.sessionId,timeoutMs:h.timeoutMs,transport:h.transport,apiKey:a?.apiKey})}}async drainQueuedMessages(e,t){const i=t==="all"?e.splice(0):e.splice(0,1);if(i.length===0)return i;try{return await this.emitQueueUpdate(),i}catch(s){throw e.unshift(...i),k(s)}}createLoopConfig(e,t){const i=e();return{model:i.model,reasoning:i.thinkingLevel==="off"?void 0:i.thinkingLevel,convertToLlm:U,transformContext:c(async s=>(await this.emitHook({type:"context",messages:[...s]}))?.messages??s,"transformContext"),beforeToolCall:c(async({toolCall:s,args:n})=>{const a=await this.emitHook({type:"tool_call",toolCallId:s.id,toolName:s.name,input:n});return a?{block:a.block,reason:a.reason}:void 0},"beforeToolCall"),afterToolCall:c(async({toolCall:s,args:n,result:a,isError:r})=>{const h=await this.emitHook({type:"tool_result",toolCallId:s.id,toolName:s.name,input:n,content:a.content,details:a.details,isError:r});return h?{content:h.content,details:h.details,isError:h.isError,terminate:h.terminate}:void 0},"afterToolCall"),prepareNextTurn:c(async()=>{await this.flushPendingSessionWrites();const s=await this.createTurnState();return t(s),{context:this.createContext(s),model:s.model,thinkingLevel:s.thinkingLevel}},"prepareNextTurn"),getSteeringMessages:c(async()=>this.drainQueuedMessages(this.steerQueue,this.steeringQueueMode),"getSteeringMessages"),getFollowUpMessages:c(async()=>this.drainQueuedMessages(this.followUpQueue,this.followUpQueueMode),"getFollowUpMessages")}}validateUniqueNames(e,t){const i=q(e);if(i.length>0)throw new l("invalid_argument",`${t}: ${i.join(", ")}`)}validateToolNames(e,t=this.tools){this.validateUniqueNames(e,"Duplicate active tool name(s)");const i=e.filter(s=>!t.has(s));if(i.length>0)throw new l("invalid_argument",`Unknown tool(s): ${i.join(", ")}`)}async flushPendingSessionWrites(){for(;this.pendingSessionWrites.length>0;){const e=this.pendingSessionWrites[0];e.type==="message"?await this.session.appendMessage(e.message):e.type==="model_change"?await this.session.appendModelChange(e.provider,e.modelId):e.type==="thinking_level_change"?await this.session.appendThinkingLevelChange(e.thinkingLevel):e.type==="active_tools_change"?await this.session.appendActiveToolsChange(e.activeToolNames):e.type==="custom"?await this.session.appendCustomEntry(e.customType,e.data):e.type==="custom_message"?await this.session.appendCustomMessageEntry(e.customType,e.content,e.display,e.details):e.type==="label"?await this.session.appendLabel(e.targetId,e.label):e.type==="session_info"?await this.session.appendSessionName(e.name??""):e.type==="leaf"&&await this.session.getStorage().setLeafId(e.targetId),this.pendingSessionWrites.shift()}}async handleAgentEvent(e,t){if(e.type==="message_end"){await this.session.appendMessage(e.message),await this.emitAny(e,t);return}if(e.type==="turn_end"){let i;try{await this.emitAny(e,t)}catch(n){i=n}const s=this.pendingSessionWrites.length>0;if(await this.flushPendingSessionWrites(),i)throw i;await this.emitOwn({type:"save_point",hadPendingMutations:s});return}if(e.type==="agent_end"){await this.flushPendingSessionWrites(),this.phase="idle",await this.emitAny(e,t),await this.emitOwn({type:"settled",nextTurnCount:this.nextTurnQueue.length},t);return}await this.emitAny(e,t)}async emitRunFailure(e,t,i,s){const n=j(e,t,i);return await this.handleAgentEvent({type:"message_start",message:n},s),await this.handleAgentEvent({type:"message_end",message:n},s),await this.handleAgentEvent({type:"turn_end",message:n,toolResults:[]},s),await this.handleAgentEvent({type:"agent_end",messages:[n]},s),[n]}async executeTurn(e,t,i){let s=e,n=[M(t,i?.images)];if(this.nextTurnQueue.length>0){const u=this.nextTurnQueue.splice(0);try{await this.emitQueueUpdate()}catch(f){throw this.nextTurnQueue.unshift(...u),k(f)}n=[...u,n[0]]}const a=await this.emitHook({type:"before_agent_start",prompt:t,images:i?.images,systemPrompt:e.systemPrompt,resources:e.resources});a?.messages&&(n=[...n,...a.messages]);const r=new AbortController,h=c(()=>s,"getTurnState"),m=c(u=>{s=u},"setTurnState");this.runAbortController=r;const p=(async()=>{try{return await x(n,this.createContext(e,a?.systemPrompt),this.createLoopConfig(h,m),u=>this.handleAgentEvent(u,r.signal),r.signal,this.createStreamFn(h))}catch(u){try{return await this.emitRunFailure(s.model,u,r.signal.aborted,r.signal)}catch(f){const w=new AggregateError([_(u),_(f)],"Agent run failed and failure reporting failed");throw new l("unknown",w.message,w)}}})();try{const u=await p;for(let f=u.length-1;f>=0;f--){const w=u[f];if(w.role==="assistant")return w}throw new l("invalid_state","AgentHarness prompt completed without an assistant message")}finally{try{await this.flushPendingSessionWrites()}finally{this.runAbortController=void 0}}}async prompt(e,t){if(this.phase!=="idle")throw new l("busy","AgentHarness is busy");this.phase="turn";const i=this.startRunPromise();try{const s=await this.createTurnState();return await this.executeTurn(s,e,t)}catch(s){throw this.phase="idle",y(s,"unknown")}finally{i()}}async skill(e,t){if(this.phase!=="idle")throw new l("busy","AgentHarness is busy");this.phase="turn";const i=this.startRunPromise();try{const s=await this.createTurnState(),n=(s.resources.skills??[]).find(a=>a.name===e);if(!n)throw new l("invalid_argument",`Unknown skill: ${e}`);return await this.executeTurn(s,I(n,t))}catch(s){throw this.phase="idle",y(s,"unknown")}finally{i()}}async promptFromTemplate(e,t=[]){if(this.phase!=="idle")throw new l("busy","AgentHarness is busy");this.phase="turn";const i=this.startRunPromise();try{const s=await this.createTurnState(),n=(s.resources.promptTemplates??[]).find(a=>a.name===e);if(!n)throw new l("invalid_argument",`Unknown prompt template: ${e}`);return await this.executeTurn(s,L(n,t))}catch(s){throw this.phase="idle",y(s,"unknown")}finally{i()}}async steer(e,t){if(this.phase==="idle")throw new l("invalid_state","Cannot steer while idle");this.steerQueue.push(M(e,t?.images)),await this.emitQueueUpdate()}async followUp(e,t){if(this.phase==="idle")throw new l("invalid_state","Cannot follow up while idle");this.followUpQueue.push(M(e,t?.images)),await this.emitQueueUpdate()}async nextTurn(e,t){this.nextTurnQueue.push(M(e,t?.images)),await this.emitQueueUpdate()}async appendMessage(e){try{this.phase==="idle"?await this.session.appendMessage(e):this.pendingSessionWrites.push({type:"message",message:e})}catch(t){throw y(t,"session")}}async compact(e){if(this.phase!=="idle")throw new l("busy","compact() requires idle harness");this.phase="compaction";try{const t=this.model;if(!t)throw new l("invalid_state","No model set for compaction");const i=await this.getApiKeyAndHeaders?.(t);if(!i)throw new l("auth","No auth available for compaction");const s=await this.session.getBranch(),n=C(s,E);if(!n.ok)throw n.error;const a=n.value;if(!a)throw new l("compaction","Nothing to compact");const r=await this.emitHook({type:"session_before_compact",preparation:a,branchEntries:s,customInstructions:e,signal:new AbortController().signal});if(r?.cancel)throw new l("compaction","Compaction cancelled");const h=r?.compaction,m=h?{ok:!0,value:h}:await Q(a,t,i.apiKey,i.headers,e,void 0,this.thinkingLevel);if(!m.ok)throw m.error;const p=m.value,u=await this.session.appendCompaction(p.summary,p.firstKeptEntryId,p.tokensBefore,p.details,h!==void 0),f=await this.session.getEntry(u);return f?.type==="compaction"&&await this.emitOwn({type:"session_compact",compactionEntry:f,fromHook:h!==void 0}),p}catch(t){throw y(t,"compaction")}finally{this.phase="idle"}}async navigateTree(e,t){if(this.phase!=="idle")throw new l("busy","navigateTree() requires idle harness");this.phase="branch_summary";try{const i=await this.session.getLeafId();if(i===e)return{cancelled:!1};const s=await this.session.getEntry(e);if(!s)throw new l("invalid_argument",`Entry ${e} not found`);const{entries:n,commonAncestorId:a}=await N(this.session,i,e),r={targetId:e,oldLeafId:i,commonAncestorId:a,entriesToSummarize:n,userWantsSummary:t?.summarize??!1,customInstructions:t?.customInstructions,replaceInstructions:t?.replaceInstructions,label:t?.label},h=new AbortController().signal,m=await this.emitHook({type:"session_before_tree",preparation:r,signal:h});if(m?.cancel)return{cancelled:!0};let p,u=m?.summary?.summary,f=m?.summary?.details;if(!u&&t?.summarize&&n.length>0){const d=this.model;if(!d)throw new l("invalid_state","No model set for branch summary");const g=await this.getApiKeyAndHeaders?.(d);if(!g)throw new l("auth","No auth available for branch summary");const v=await P(n,{model:d,apiKey:g.apiKey,headers:g.headers,signal:new AbortController().signal,customInstructions:m?.customInstructions??t?.customInstructions,replaceInstructions:m?.replaceInstructions??t?.replaceInstructions});if(!v.ok){if(v.error.code==="aborted")return{cancelled:!0};throw new l("branch_summary",v.error.message,v.error)}u=v.value.summary,f={readFiles:v.value.readFiles,modifiedFiles:v.value.modifiedFiles}}let w,b;if(s.type==="message"&&s.message.role==="user"){b=s.parentId;const d=s.message.content;w=typeof d=="string"?d:d.filter(g=>g.type==="text").map(g=>g.text).join("")}else s.type==="custom_message"?(b=s.parentId,w=typeof s.content=="string"?s.content:s.content.filter(d=>d.type==="text").map(d=>d.text).join("")):b=e;const A=await this.session.moveTo(b,u?{summary:u,details:f,fromHook:m?.summary!==void 0}:void 0);if(A){const d=await this.session.getEntry(A);d?.type==="branch_summary"&&(p=d)}return await this.emitOwn({type:"session_tree",newLeafId:await this.session.getLeafId(),oldLeafId:i,summaryEntry:p,fromHook:m?.summary!==void 0}),{cancelled:!1,editorText:w,summaryEntry:p}}catch(i){throw y(i,"branch_summary")}finally{this.phase="idle"}}getModel(){return this.model}async setModel(e){try{const t=this.model;this.phase==="idle"?await this.session.appendModelChange(e.provider,e.id):this.pendingSessionWrites.push({type:"model_change",provider:e.provider,modelId:e.id}),this.model=e,await this.emitOwn({type:"model_update",model:e,previousModel:t,source:"set"})}catch(t){throw y(t,"session")}}getThinkingLevel(){return this.thinkingLevel}async setThinkingLevel(e){try{const t=this.thinkingLevel;this.phase==="idle"?await this.session.appendThinkingLevelChange(e):this.pendingSessionWrites.push({type:"thinking_level_change",thinkingLevel:e}),this.thinkingLevel=e,await this.emitOwn({type:"thinking_level_update",level:e,previousLevel:t})}catch(t){throw y(t,"session")}}getTools(){return[...this.tools.values()]}async setTools(e,t){try{this.validateUniqueNames(e.map(r=>r.name),"Duplicate tool name(s)");const i=new Map(e.map(r=>[r.name,r])),s=t?[...t]:this.activeToolNames;this.validateToolNames(s,i);const n=[...this.tools.keys()],a=[...this.activeToolNames];this.phase==="idle"?await this.session.appendActiveToolsChange(s):this.pendingSessionWrites.push({type:"active_tools_change",activeToolNames:[...s]}),this.tools=i,this.activeToolNames=[...s],await this.emitOwn({type:"tools_update",toolNames:[...this.tools.keys()],previousToolNames:n,activeToolNames:[...this.activeToolNames],previousActiveToolNames:a,source:"set"})}catch(i){throw y(i,"invalid_argument")}}getActiveTools(){return this.activeToolNames.map(e=>this.tools.get(e))}async setActiveTools(e){try{this.validateToolNames(e);const t=[...this.tools.keys()],i=[...this.activeToolNames];this.phase==="idle"?await this.session.appendActiveToolsChange(e):this.pendingSessionWrites.push({type:"active_tools_change",activeToolNames:[...e]}),this.activeToolNames=[...e],await this.emitOwn({type:"tools_update",toolNames:[...this.tools.keys()],previousToolNames:t,activeToolNames:[...this.activeToolNames],previousActiveToolNames:i,source:"set"})}catch(t){throw y(t,"invalid_argument")}}getSteeringMode(){return this.steeringQueueMode}async setSteeringMode(e){this.steeringQueueMode=e}getFollowUpMode(){return this.followUpQueueMode}async setFollowUpMode(e){this.followUpQueueMode=e}getResources(){return{skills:this.resources.skills?.slice(),promptTemplates:this.resources.promptTemplates?.slice()}}async setResources(e){const t=this.getResources();this.resources={skills:e.skills?.slice(),promptTemplates:e.promptTemplates?.slice()},await this.emitOwn({type:"resources_update",resources:this.getResources(),previousResources:t})}getStreamOptions(){return T(this.streamOptions)}async setStreamOptions(e){this.streamOptions=T(e)}async abort(){const e=[...this.steerQueue],t=[...this.followUpQueue];this.steerQueue=[],this.followUpQueue=[],this.runAbortController?.abort();const i=[];try{await this.emitQueueUpdate()}catch(s){i.push(_(s))}try{await this.waitForIdle()}catch(s){i.push(_(s))}try{await this.emitOwn({type:"abort",clearedSteer:e,clearedFollowUp:t})}catch(s){i.push(_(s))}if(i.length>0){const s=i.length===1?i[0]:new AggregateError(i,"Abort completed with errors");throw y(s,"hook")}return{clearedSteer:e,clearedFollowUp:t}}async waitForIdle(){await this.runPromise}subscribe(e){let t=this.handlers.get(S);return t||(t=new Set,this.handlers.set(S,t)),t.add(e),()=>t.delete(e)}on(e,t){let i=this.handlers.get(e);return i||(i=new Set,this.handlers.set(e,i)),i.add(t),()=>i.delete(t)}}export{ee as AgentHarness};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Model } from "@openadapter/koda-ai";
|
|
2
|
+
import type { AgentMessage } from "../../types.ts";
|
|
3
|
+
import type { BranchSummaryResult, Session, SessionTreeEntry } from "../types.ts";
|
|
4
|
+
import { BranchSummaryError, type Result } from "../types.ts";
|
|
5
|
+
import { type FileOperations } from "./utils.ts";
|
|
6
|
+
/** File-operation details stored on generated branch summary entries. */
|
|
7
|
+
export interface BranchSummaryDetails {
|
|
8
|
+
/** Files read while exploring the summarized branch. */
|
|
9
|
+
readFiles: string[];
|
|
10
|
+
/** Files modified while exploring the summarized branch. */
|
|
11
|
+
modifiedFiles: string[];
|
|
12
|
+
}
|
|
13
|
+
export type { FileOperations } from "./utils.ts";
|
|
14
|
+
/** Prepared branch content for summarization. */
|
|
15
|
+
export interface BranchPreparation {
|
|
16
|
+
/** Messages selected for the branch summary. */
|
|
17
|
+
messages: AgentMessage[];
|
|
18
|
+
/** File operations extracted from the branch. */
|
|
19
|
+
fileOps: FileOperations;
|
|
20
|
+
/** Estimated token count for selected messages. */
|
|
21
|
+
totalTokens: number;
|
|
22
|
+
}
|
|
23
|
+
/** Entries selected for branch summarization. */
|
|
24
|
+
export interface CollectEntriesResult {
|
|
25
|
+
/** Entries to summarize in chronological order. */
|
|
26
|
+
entries: SessionTreeEntry[];
|
|
27
|
+
/** Deepest common ancestor between the previous leaf and target entry. */
|
|
28
|
+
commonAncestorId: string | null;
|
|
29
|
+
}
|
|
30
|
+
/** Options for generating a branch summary. */
|
|
31
|
+
export interface GenerateBranchSummaryOptions {
|
|
32
|
+
/** Model used for summarization. */
|
|
33
|
+
model: Model<any>;
|
|
34
|
+
/** API key forwarded to the provider. */
|
|
35
|
+
apiKey: string;
|
|
36
|
+
/** Optional request headers forwarded to the provider. */
|
|
37
|
+
headers?: Record<string, string>;
|
|
38
|
+
/** Abort signal for the summarization request. */
|
|
39
|
+
signal: AbortSignal;
|
|
40
|
+
/** Optional instructions appended to or replacing the default prompt. */
|
|
41
|
+
customInstructions?: string;
|
|
42
|
+
/** Replace the default prompt with custom instructions instead of appending them. */
|
|
43
|
+
replaceInstructions?: boolean;
|
|
44
|
+
/** Tokens reserved for prompt and model output. Defaults to 16384. */
|
|
45
|
+
reserveTokens?: number;
|
|
46
|
+
}
|
|
47
|
+
/** Collect entries that should be summarized before navigating to a different session tree entry. */
|
|
48
|
+
export declare function collectEntriesForBranchSummary(session: Session, oldLeafId: string | null, targetId: string): Promise<CollectEntriesResult>;
|
|
49
|
+
/** Prepare branch entries for summarization within an optional token budget. */
|
|
50
|
+
export declare function prepareBranchEntries(entries: SessionTreeEntry[], tokenBudget?: number): BranchPreparation;
|
|
51
|
+
/** Generate a summary for abandoned branch entries. */
|
|
52
|
+
export declare function generateBranchSummary(entries: SessionTreeEntry[], options: GenerateBranchSummaryOptions): Promise<Result<BranchSummaryResult, BranchSummaryError>>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var T=Object.defineProperty;var l=(e,o)=>T(e,"name",{value:o,configurable:!0});import{completeSimple as b}from"@openadapter/koda-ai";import{convertToLlm as v,createBranchSummaryMessage as B,createCompactionSummaryMessage as P,createCustomMessage as E}from"../messages.js";import{BranchSummaryError as y,err as x,ok as w,SessionError as R}from"../types.js";import{estimateTokens as C,SUMMARIZATION_SYSTEM_PROMPT as O}from"./compaction.js";import{computeFileLists as I,createFileOps as N,extractFileOpsFromMessage as W,formatFileOperations as $,serializeConversation as U}from"./utils.js";async function J(e,o,i){if(!o)return{entries:[],commonAncestorId:null};const c=new Set((await e.getBranch(o)).map(r=>r.id)),a=await e.getBranch(i);let n=null;for(let r=a.length-1;r>=0;r--)if(c.has(a[r].id)){n=a[r].id;break}const t=[];let s=o;for(;s&&s!==n;){const r=await e.getEntry(s);if(!r)throw new R("invalid_session",`Entry ${s} not found`);t.push(r),s=r.parentId}return t.reverse(),{entries:t,commonAncestorId:n}}l(J,"collectEntriesForBranchSummary");function z(e){switch(e.type){case"message":return e.message.role==="toolResult"?void 0:e.message;case"custom_message":return E(e.customType,e.content,e.display,e.details,e.timestamp);case"branch_summary":return B(e.summary,e.fromId,e.timestamp);case"compaction":return P(e.summary,e.tokensBefore,e.timestamp);case"thinking_level_change":case"model_change":case"active_tools_change":case"custom":case"label":case"session_info":case"leaf":return}}l(z,"getMessageFromEntry");function D(e,o=0){const i=[],c=N();let a=0;for(const n of e)if(n.type==="branch_summary"&&!n.fromHook&&n.details){const t=n.details;if(Array.isArray(t.readFiles))for(const s of t.readFiles)c.read.add(s);if(Array.isArray(t.modifiedFiles))for(const s of t.modifiedFiles)c.edited.add(s)}for(let n=e.length-1;n>=0;n--){const t=e[n],s=z(t);if(!s)continue;W(s,c);const r=C(s);if(o>0&&a+r>o){(t.type==="compaction"||t.type==="branch_summary")&&a<o*.9&&(i.unshift(s),a+=r);break}i.unshift(s),a+=r}return{messages:i,fileOps:c,totalTokens:a}}l(D,"prepareBranchEntries");const H=`The user explored a different conversation branch before returning here.
|
|
2
|
+
Summary of that exploration:
|
|
3
|
+
|
|
4
|
+
`,M=`Create a structured summary of this conversation branch for context when returning later.
|
|
5
|
+
|
|
6
|
+
Use this EXACT format:
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
[What was the user trying to accomplish in this branch?]
|
|
10
|
+
|
|
11
|
+
## Constraints & Preferences
|
|
12
|
+
- [Any constraints, preferences, or requirements mentioned]
|
|
13
|
+
- [Or "(none)" if none were mentioned]
|
|
14
|
+
|
|
15
|
+
## Progress
|
|
16
|
+
### Done
|
|
17
|
+
- [x] [Completed tasks/changes]
|
|
18
|
+
|
|
19
|
+
### In Progress
|
|
20
|
+
- [ ] [Work that was started but not finished]
|
|
21
|
+
|
|
22
|
+
### Blocked
|
|
23
|
+
- [Issues preventing progress, if any]
|
|
24
|
+
|
|
25
|
+
## Key Decisions
|
|
26
|
+
- **[Decision]**: [Brief rationale]
|
|
27
|
+
|
|
28
|
+
## Next Steps
|
|
29
|
+
1. [What should happen next to continue this work]
|
|
30
|
+
|
|
31
|
+
Keep each section concise. Preserve exact file paths, function names, and error messages.`;async function Q(e,o){const{model:i,apiKey:c,headers:a,signal:n,customInstructions:t,replaceInstructions:s,reserveTokens:r=16384}=o,_=(i.contextWindow||128e3)-r,{messages:d,fileOps:A}=D(e,_);if(d.length===0)return w({summary:"No content to summarize",readFiles:[],modifiedFiles:[]});const k=v(d),F=U(k);let u;s&&t?u=t:t?u=`${M}
|
|
32
|
+
|
|
33
|
+
Additional focus: ${t}`:u=M;const S=[{role:"user",content:[{type:"text",text:`<conversation>
|
|
34
|
+
${F}
|
|
35
|
+
</conversation>
|
|
36
|
+
|
|
37
|
+
${u}`}],timestamp:Date.now()}],m=await b(i,{systemPrompt:O,messages:S},{apiKey:c,headers:a,signal:n,maxTokens:2048});if(m.stopReason==="aborted")return x(new y("aborted",m.errorMessage||"Branch summary aborted"));if(m.stopReason==="error")return x(new y("summarization_failed",`Branch summary failed: ${m.errorMessage||"Unknown error"}`));let f=m.content.filter(p=>p.type==="text").map(p=>p.text).join(`
|
|
38
|
+
`);f=H+f;const{readFiles:h,modifiedFiles:g}=I(A);return f+=$(h,g),w({summary:f||"No summary generated",readFiles:h,modifiedFiles:g})}l(Q,"generateBranchSummary");export{J as collectEntriesForBranchSummary,Q as generateBranchSummary,D as prepareBranchEntries};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { Model, Usage } from "@openadapter/koda-ai";
|
|
2
|
+
import type { AgentMessage, ThinkingLevel } from "../../types.ts";
|
|
3
|
+
import { CompactionError, type Result, type SessionTreeEntry } from "../types.ts";
|
|
4
|
+
import { type FileOperations } from "./utils.ts";
|
|
5
|
+
/** File-operation details stored on generated compaction entries. */
|
|
6
|
+
export interface CompactionDetails {
|
|
7
|
+
/** Files read in the compacted history. */
|
|
8
|
+
readFiles: string[];
|
|
9
|
+
/** Files modified in the compacted history. */
|
|
10
|
+
modifiedFiles: string[];
|
|
11
|
+
}
|
|
12
|
+
/** Generated compaction data ready to be persisted as a compaction entry. */
|
|
13
|
+
export interface CompactionResult<T = unknown> {
|
|
14
|
+
/** Summary text that replaces compacted history in future context. */
|
|
15
|
+
summary: string;
|
|
16
|
+
/** Entry id where retained history starts. */
|
|
17
|
+
firstKeptEntryId: string;
|
|
18
|
+
/** Estimated context tokens before compaction. */
|
|
19
|
+
tokensBefore: number;
|
|
20
|
+
/** Optional implementation-specific details stored with the compaction entry. */
|
|
21
|
+
details?: T;
|
|
22
|
+
}
|
|
23
|
+
/** Compaction thresholds and retention settings. */
|
|
24
|
+
export interface CompactionSettings {
|
|
25
|
+
/** Enable automatic compaction decisions. */
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
/** Tokens reserved for summary prompt and output. */
|
|
28
|
+
reserveTokens: number;
|
|
29
|
+
/** Approximate recent-context tokens to keep after compaction. */
|
|
30
|
+
keepRecentTokens: number;
|
|
31
|
+
}
|
|
32
|
+
/** Default compaction settings used by the harness. */
|
|
33
|
+
export declare const DEFAULT_COMPACTION_SETTINGS: CompactionSettings;
|
|
34
|
+
/** Calculate total context tokens from provider usage. */
|
|
35
|
+
export declare function calculateContextTokens(usage: Usage): number;
|
|
36
|
+
/** Return usage from the last successful assistant message in session entries. */
|
|
37
|
+
export declare function getLastAssistantUsage(entries: SessionTreeEntry[]): Usage | undefined;
|
|
38
|
+
/** Estimated context-token usage for a message list. */
|
|
39
|
+
export interface ContextUsageEstimate {
|
|
40
|
+
/** Estimated total context tokens. */
|
|
41
|
+
tokens: number;
|
|
42
|
+
/** Tokens reported by the most recent assistant usage block. */
|
|
43
|
+
usageTokens: number;
|
|
44
|
+
/** Estimated tokens after the most recent assistant usage block. */
|
|
45
|
+
trailingTokens: number;
|
|
46
|
+
/** Index of the message that provided usage, or null when none exists. */
|
|
47
|
+
lastUsageIndex: number | null;
|
|
48
|
+
}
|
|
49
|
+
/** Estimate context tokens for messages using provider usage when available. */
|
|
50
|
+
export declare function estimateContextTokens(messages: AgentMessage[]): ContextUsageEstimate;
|
|
51
|
+
/** Return whether context usage exceeds the configured compaction threshold. */
|
|
52
|
+
export declare function shouldCompact(contextTokens: number, contextWindow: number, settings: CompactionSettings): boolean;
|
|
53
|
+
/** Estimate token count for one message using a conservative character heuristic. */
|
|
54
|
+
export declare function estimateTokens(message: AgentMessage): number;
|
|
55
|
+
/** Find the user-visible message that starts the turn containing an entry. */
|
|
56
|
+
export declare function findTurnStartIndex(entries: SessionTreeEntry[], entryIndex: number, startIndex: number): number;
|
|
57
|
+
/** Cut point selected for compaction. */
|
|
58
|
+
export interface CutPointResult {
|
|
59
|
+
/** Index of the first entry retained after compaction. */
|
|
60
|
+
firstKeptEntryIndex: number;
|
|
61
|
+
/** Index of the turn-start entry when the cut splits a turn, otherwise -1. */
|
|
62
|
+
turnStartIndex: number;
|
|
63
|
+
/** Whether the selected cut point splits an in-progress turn. */
|
|
64
|
+
isSplitTurn: boolean;
|
|
65
|
+
}
|
|
66
|
+
/** Find the compaction cut point that keeps approximately the requested recent-token budget. */
|
|
67
|
+
export declare function findCutPoint(entries: SessionTreeEntry[], startIndex: number, endIndex: number, keepRecentTokens: number): CutPointResult;
|
|
68
|
+
export declare const SUMMARIZATION_SYSTEM_PROMPT = "You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.";
|
|
69
|
+
/** Generate or update a conversation summary for compaction. */
|
|
70
|
+
export declare function generateSummary(currentMessages: AgentMessage[], model: Model<any>, reserveTokens: number, apiKey: string, headers?: Record<string, string>, signal?: AbortSignal, customInstructions?: string, previousSummary?: string, thinkingLevel?: ThinkingLevel): Promise<Result<string, CompactionError>>;
|
|
71
|
+
/** Prepared inputs for a compaction run. */
|
|
72
|
+
export interface CompactionPreparation {
|
|
73
|
+
/** Entry id where retained history starts. */
|
|
74
|
+
firstKeptEntryId: string;
|
|
75
|
+
/** Messages summarized into the history summary. */
|
|
76
|
+
messagesToSummarize: AgentMessage[];
|
|
77
|
+
/** Prefix messages summarized separately when compaction splits a turn. */
|
|
78
|
+
turnPrefixMessages: AgentMessage[];
|
|
79
|
+
/** Whether compaction splits a turn. */
|
|
80
|
+
isSplitTurn: boolean;
|
|
81
|
+
/** Estimated context tokens before compaction. */
|
|
82
|
+
tokensBefore: number;
|
|
83
|
+
/** Previous compaction summary used for iterative updates. */
|
|
84
|
+
previousSummary?: string;
|
|
85
|
+
/** File operations extracted from summarized history. */
|
|
86
|
+
fileOps: FileOperations;
|
|
87
|
+
/** Settings used to prepare compaction. */
|
|
88
|
+
settings: CompactionSettings;
|
|
89
|
+
}
|
|
90
|
+
/** Prepare session entries for compaction, or return undefined when compaction is not applicable. */
|
|
91
|
+
export declare function prepareCompaction(pathEntries: SessionTreeEntry[], settings: CompactionSettings): Result<CompactionPreparation | undefined, CompactionError>;
|
|
92
|
+
export { serializeConversation } from "./utils.ts";
|
|
93
|
+
/** Generate compaction summary data from prepared session history. */
|
|
94
|
+
export declare function compact(preparation: CompactionPreparation, model: Model<any>, apiKey: string, headers?: Record<string, string>, customInstructions?: string, signal?: AbortSignal, thinkingLevel?: ThinkingLevel): Promise<Result<CompactionResult, CompactionError>>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
var U=Object.defineProperty;var u=(e,t)=>U(e,"name",{value:t,configurable:!0});import{completeSimple as P}from"@openadapter/koda-ai";import{convertToLlm as M,createBranchSummaryMessage as F,createCompactionSummaryMessage as N,createCustomMessage as D}from"../messages.js";import{buildSessionContext as z}from"../session/session.js";import{CompactionError as k,err as T,ok as S}from"../types.js";import{computeFileLists as K,createFileOps as $,extractFileOpsFromMessage as w,formatFileOperations as B,serializeConversation as C}from"./utils.js";function Y(e){try{return JSON.stringify(e)??"undefined"}catch{return"[unserializable]"}}u(Y,"safeJsonStringify");function V(e,t,r){const n=$();if(r>=0){const s=t[r];if(!s.fromHook&&s.details){const o=s.details;if(Array.isArray(o.readFiles))for(const a of o.readFiles)n.read.add(a);if(Array.isArray(o.modifiedFiles))for(const a of o.modifiedFiles)n.edited.add(a)}}for(const s of e)w(s,n);return n}u(V,"extractFileOperations");function X(e){if(e.type==="message")return e.message;if(e.type==="custom_message")return D(e.customType,e.content,e.display,e.details,e.timestamp);if(e.type==="branch_summary")return F(e.summary,e.fromId,e.timestamp);if(e.type==="compaction")return N(e.summary,e.tokensBefore,e.timestamp)}u(X,"getMessageFromEntry");function A(e){if(e.type!=="compaction")return X(e)}u(A,"getMessageFromEntryForCompaction");const ce={enabled:!0,reserveTokens:16384,keepRecentTokens:2e4};function G(e){return e.totalTokens||e.input+e.output+e.cacheRead+e.cacheWrite}u(G,"calculateContextTokens");function R(e){if(e.role==="assistant"&&"usage"in e){const t=e;if(t.stopReason!=="aborted"&&t.stopReason!=="error"&&t.usage)return t.usage}}u(R,"getAssistantUsage");function ue(e){for(let t=e.length-1;t>=0;t--){const r=e[t];if(r.type==="message"){const n=R(r.message);if(n)return n}}}u(ue,"getLastAssistantUsage");function W(e){for(let t=e.length-1;t>=0;t--){const r=R(e[t]);if(r)return{usage:r,index:t}}}u(W,"getLastAssistantUsageInfo");function Z(e){const t=W(e);if(!t){let s=0;for(const o of e)s+=b(o);return{tokens:s,usageTokens:0,trailingTokens:s,lastUsageIndex:null}}const r=G(t.usage);let n=0;for(let s=t.index+1;s<e.length;s++)n+=b(e[s]);return{tokens:r+n,usageTokens:r,trailingTokens:n,lastUsageIndex:t.index}}u(Z,"estimateContextTokens");function me(e,t,r){return r.enabled?e>t-r.reserveTokens:!1}u(me,"shouldCompact");const q=4800;function _(e){if(typeof e=="string")return e.length;let t=0;for(const r of e)r.type==="text"&&r.text?t+=r.text.length:r.type==="image"&&(t+=q);return t}u(_,"estimateTextAndImageContentChars");function b(e){let t=0;switch(e.role){case"user":return t=_(e.content),Math.ceil(t/4);case"assistant":{const r=e;for(const n of r.content)n.type==="text"?t+=n.text.length:n.type==="thinking"?t+=n.thinking.length:n.type==="toolCall"&&(t+=n.name.length+Y(n.arguments).length);return Math.ceil(t/4)}case"custom":case"toolResult":return t=_(e.content),Math.ceil(t/4);case"bashExecution":return t=e.command.length+e.output.length,Math.ceil(t/4);case"branchSummary":case"compactionSummary":return t=e.summary.length,Math.ceil(t/4)}return 0}u(b,"estimateTokens");function L(e,t,r){const n=[];for(let s=t;s<r;s++){const o=e[s];switch(o.type){case"message":{switch(o.message.role){case"bashExecution":case"custom":case"branchSummary":case"compactionSummary":case"user":case"assistant":n.push(s);break;case"toolResult":break}break}case"thinking_level_change":case"model_change":case"active_tools_change":case"compaction":case"branch_summary":case"custom":case"custom_message":case"label":case"session_info":case"leaf":break}(o.type==="branch_summary"||o.type==="custom_message")&&n.push(s)}return n}u(L,"findValidCutPoints");function j(e,t,r){for(let n=t;n>=r;n--){const s=e[n];if(s.type==="branch_summary"||s.type==="custom_message")return n;if(s.type==="message"){const o=s.message.role;if(o==="user"||o==="bashExecution")return n}}return-1}u(j,"findTurnStartIndex");function H(e,t,r,n){const s=L(e,t,r);if(s.length===0)return{firstKeptEntryIndex:t,turnStartIndex:-1,isSplitTurn:!1};let o=0,a=s[0];for(let f=r-1;f>=t;f--){const g=e[f];if(g.type!=="message")continue;const p=b(g.message);if(o+=p,o>=n){for(let m=0;m<s.length;m++)if(s[m]>=f){a=s[m];break}break}}for(;a>t;){const f=e[a-1];if(f.type==="compaction"||f.type==="message")break;a--}const c=e[a],l=c.type==="message"&&c.message.role==="user",x=l?-1:j(e,a,t);return{firstKeptEntryIndex:a,turnStartIndex:x,isSplitTurn:!l&&x!==-1}}u(H,"findCutPoint");const E=`You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.
|
|
2
|
+
|
|
3
|
+
Do NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`,J=`The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.
|
|
4
|
+
|
|
5
|
+
Use this EXACT format:
|
|
6
|
+
|
|
7
|
+
## Goal
|
|
8
|
+
[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]
|
|
9
|
+
|
|
10
|
+
## Constraints & Preferences
|
|
11
|
+
- [Any constraints, preferences, or requirements mentioned by user]
|
|
12
|
+
- [Or "(none)" if none were mentioned]
|
|
13
|
+
|
|
14
|
+
## Progress
|
|
15
|
+
### Done
|
|
16
|
+
- [x] [Completed tasks/changes]
|
|
17
|
+
|
|
18
|
+
### In Progress
|
|
19
|
+
- [ ] [Current work]
|
|
20
|
+
|
|
21
|
+
### Blocked
|
|
22
|
+
- [Issues preventing progress, if any]
|
|
23
|
+
|
|
24
|
+
## Key Decisions
|
|
25
|
+
- **[Decision]**: [Brief rationale]
|
|
26
|
+
|
|
27
|
+
## Next Steps
|
|
28
|
+
1. [Ordered list of what should happen next]
|
|
29
|
+
|
|
30
|
+
## Critical Context
|
|
31
|
+
- [Any data, examples, or references needed to continue]
|
|
32
|
+
- [Or "(none)" if not applicable]
|
|
33
|
+
|
|
34
|
+
Keep each section concise. Preserve exact file paths, function names, and error messages.`,Q=`The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.
|
|
35
|
+
|
|
36
|
+
Update the existing structured summary with new information. RULES:
|
|
37
|
+
- PRESERVE all existing information from the previous summary
|
|
38
|
+
- ADD new progress, decisions, and context from the new messages
|
|
39
|
+
- UPDATE the Progress section: move items from "In Progress" to "Done" when completed
|
|
40
|
+
- UPDATE "Next Steps" based on what was accomplished
|
|
41
|
+
- PRESERVE exact file paths, function names, and error messages
|
|
42
|
+
- If something is no longer relevant, you may remove it
|
|
43
|
+
|
|
44
|
+
Use this EXACT format:
|
|
45
|
+
|
|
46
|
+
## Goal
|
|
47
|
+
[Preserve existing goals, add new ones if the task expanded]
|
|
48
|
+
|
|
49
|
+
## Constraints & Preferences
|
|
50
|
+
- [Preserve existing, add new ones discovered]
|
|
51
|
+
|
|
52
|
+
## Progress
|
|
53
|
+
### Done
|
|
54
|
+
- [x] [Include previously done items AND newly completed items]
|
|
55
|
+
|
|
56
|
+
### In Progress
|
|
57
|
+
- [ ] [Current work - update based on progress]
|
|
58
|
+
|
|
59
|
+
### Blocked
|
|
60
|
+
- [Current blockers - remove if resolved]
|
|
61
|
+
|
|
62
|
+
## Key Decisions
|
|
63
|
+
- **[Decision]**: [Brief rationale] (preserve all previous, add new)
|
|
64
|
+
|
|
65
|
+
## Next Steps
|
|
66
|
+
1. [Update based on current state]
|
|
67
|
+
|
|
68
|
+
## Critical Context
|
|
69
|
+
- [Preserve important context, add new if needed]
|
|
70
|
+
|
|
71
|
+
Keep each section concise. Preserve exact file paths, function names, and error messages.`;async function O(e,t,r,n,s,o,a,c,l){const x=Math.min(Math.floor(.8*r),t.maxTokens>0?t.maxTokens:Number.POSITIVE_INFINITY);let f=c?Q:J;a&&(f=`${f}
|
|
72
|
+
|
|
73
|
+
Additional focus: ${a}`);const g=M(e);let m=`<conversation>
|
|
74
|
+
${C(g)}
|
|
75
|
+
</conversation>
|
|
76
|
+
|
|
77
|
+
`;c&&(m+=`<previous-summary>
|
|
78
|
+
${c}
|
|
79
|
+
</previous-summary>
|
|
80
|
+
|
|
81
|
+
`),m+=f;const i=[{role:"user",content:[{type:"text",text:m}],timestamp:Date.now()}],d=t.reasoning&&l&&l!=="off"?{maxTokens:x,signal:o,apiKey:n,headers:s,reasoning:l}:{maxTokens:x,signal:o,apiKey:n,headers:s},y=await P(t,{systemPrompt:E,messages:i},d);if(y.stopReason==="aborted")return T(new k("aborted",y.errorMessage||"Summarization aborted"));if(y.stopReason==="error")return T(new k("summarization_failed",`Summarization failed: ${y.errorMessage||"Unknown error"}`));const v=y.content.filter(h=>h.type==="text").map(h=>h.text).join(`
|
|
82
|
+
`);return S(v)}u(O,"generateSummary");function fe(e,t){if(e.length===0||e[e.length-1].type==="compaction")return S(void 0);let r=-1;for(let i=e.length-1;i>=0;i--)if(e[i].type==="compaction"){r=i;break}let n,s=0;if(r>=0){const i=e[r];n=i.summary;const d=e.findIndex(y=>y.id===i.firstKeptEntryId);s=d>=0?d:r+1}const o=e.length,a=Z(z(e).messages).tokens,c=H(e,s,o,t.keepRecentTokens),l=e[c.firstKeptEntryIndex];if(!l?.id)return T(new k("invalid_session","First kept entry has no UUID - session may need migration"));const x=l.id,f=c.isSplitTurn?c.turnStartIndex:c.firstKeptEntryIndex,g=[];for(let i=s;i<f;i++){const d=A(e[i]);d&&g.push(d)}const p=[];if(c.isSplitTurn)for(let i=c.turnStartIndex;i<c.firstKeptEntryIndex;i++){const d=A(e[i]);d&&p.push(d)}const m=V(g,e,r);if(c.isSplitTurn)for(const i of p)w(i,m);return S({firstKeptEntryId:x,messagesToSummarize:g,turnPrefixMessages:p,isSplitTurn:c.isSplitTurn,tokensBefore:a,previousSummary:n,fileOps:m,settings:t})}u(fe,"prepareCompaction");const ee=`This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.
|
|
83
|
+
|
|
84
|
+
Summarize the prefix to provide context for the retained suffix:
|
|
85
|
+
|
|
86
|
+
## Original Request
|
|
87
|
+
[What did the user ask for in this turn?]
|
|
88
|
+
|
|
89
|
+
## Early Progress
|
|
90
|
+
- [Key decisions and work done in the prefix]
|
|
91
|
+
|
|
92
|
+
## Context for Suffix
|
|
93
|
+
- [Information needed to understand the retained recent work]
|
|
94
|
+
|
|
95
|
+
Be concise. Focus on what's needed to understand the kept suffix.`;import{serializeConversation as ge}from"./utils.js";async function le(e,t,r,n,s,o,a){const{firstKeptEntryId:c,messagesToSummarize:l,turnPrefixMessages:x,isSplitTurn:f,tokensBefore:g,previousSummary:p,fileOps:m,settings:i}=e;if(!c)return T(new k("invalid_session","First kept entry has no UUID - session may need migration"));let d;if(f&&x.length>0){const[h,I]=await Promise.all([l.length>0?O(l,t,i.reserveTokens,r,n,o,s,p,a):Promise.resolve(S("No prior history.")),te(x,t,i.reserveTokens,r,n,o,a)]);if(!h.ok)return T(h.error);if(!I.ok)return T(I.error);d=`${h.value}
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
**Turn Context (split turn):**
|
|
100
|
+
|
|
101
|
+
${I.value}`}else{const h=await O(l,t,i.reserveTokens,r,n,o,s,p,a);if(!h.ok)return T(h.error);d=h.value}const{readFiles:y,modifiedFiles:v}=K(m);return d+=B(y,v),S({summary:d,firstKeptEntryId:c,tokensBefore:g,details:{readFiles:y,modifiedFiles:v}})}u(le,"compact");async function te(e,t,r,n,s,o,a){const c=Math.min(Math.floor(.5*r),t.maxTokens>0?t.maxTokens:Number.POSITIVE_INFINITY),l=M(e),g=[{role:"user",content:[{type:"text",text:`<conversation>
|
|
102
|
+
${C(l)}
|
|
103
|
+
</conversation>
|
|
104
|
+
|
|
105
|
+
${ee}`}],timestamp:Date.now()}],p=await P(t,{systemPrompt:E,messages:g},t.reasoning&&a&&a!=="off"?{maxTokens:c,signal:o,apiKey:n,headers:s,reasoning:a}:{maxTokens:c,signal:o,apiKey:n,headers:s});return p.stopReason==="aborted"?T(new k("aborted",p.errorMessage||"Turn prefix summarization aborted")):p.stopReason==="error"?T(new k("summarization_failed",`Turn prefix summarization failed: ${p.errorMessage||"Unknown error"}`)):S(p.content.filter(m=>m.type==="text").map(m=>m.text).join(`
|
|
106
|
+
`))}u(te,"generateTurnPrefixSummary");export{ce as DEFAULT_COMPACTION_SETTINGS,E as SUMMARIZATION_SYSTEM_PROMPT,G as calculateContextTokens,le as compact,Z as estimateContextTokens,b as estimateTokens,H as findCutPoint,j as findTurnStartIndex,O as generateSummary,ue as getLastAssistantUsage,fe as prepareCompaction,ge as serializeConversation,me as shouldCompact};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Message } from "@openadapter/koda-ai";
|
|
2
|
+
import type { AgentMessage } from "../../types.ts";
|
|
3
|
+
/** File paths touched by a session branch or compaction range. */
|
|
4
|
+
export interface FileOperations {
|
|
5
|
+
/** Files read but not necessarily modified. */
|
|
6
|
+
read: Set<string>;
|
|
7
|
+
/** Files written by full-file write operations. */
|
|
8
|
+
written: Set<string>;
|
|
9
|
+
/** Files modified by edit operations. */
|
|
10
|
+
edited: Set<string>;
|
|
11
|
+
}
|
|
12
|
+
/** Create an empty file-operation accumulator. */
|
|
13
|
+
export declare function createFileOps(): FileOperations;
|
|
14
|
+
/** Add file operations from assistant tool calls to an accumulator. */
|
|
15
|
+
export declare function extractFileOpsFromMessage(message: AgentMessage, fileOps: FileOperations): void;
|
|
16
|
+
/** Compute sorted read-only and modified file lists from accumulated operations. */
|
|
17
|
+
export declare function computeFileLists(fileOps: FileOperations): {
|
|
18
|
+
readFiles: string[];
|
|
19
|
+
modifiedFiles: string[];
|
|
20
|
+
};
|
|
21
|
+
/** Format file lists as summary metadata tags. */
|
|
22
|
+
export declare function formatFileOperations(readFiles: string[], modifiedFiles: string[]): string;
|
|
23
|
+
/** Serialize LLM messages to plain text for summarization prompts. */
|
|
24
|
+
export declare function serializeConversation(messages: Message[]): string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
var d=Object.defineProperty;var r=(n,e)=>d(n,"name",{value:e,configurable:!0});function m(){return{read:new Set,written:new Set,edited:new Set}}r(m,"createFileOps");function $(n,e){if(n.role==="assistant"&&!(!("content"in n)||!Array.isArray(n.content)))for(const t of n.content){if(typeof t!="object"||t===null||!("type"in t)||t.type!=="toolCall"||!("arguments"in t)||!("name"in t))continue;const o=t.arguments;if(!o)continue;const i=typeof o.path=="string"?o.path:void 0;if(i)switch(t.name){case"read":e.read.add(i);break;case"write":e.written.add(i);break;case"edit":e.edited.add(i);break}}}r($,"extractFileOpsFromMessage");function j(n){const e=new Set([...n.edited,...n.written]),t=[...n.read].filter(i=>!e.has(i)).sort(),o=[...e].sort();return{readFiles:t,modifiedFiles:o}}r(j,"computeFileLists");function S(n,e){const t=[];return n.length>0&&t.push(`<read-files>
|
|
2
|
+
${n.join(`
|
|
3
|
+
`)}
|
|
4
|
+
</read-files>`),e.length>0&&t.push(`<modified-files>
|
|
5
|
+
${e.join(`
|
|
6
|
+
`)}
|
|
7
|
+
</modified-files>`),t.length===0?"":`
|
|
8
|
+
|
|
9
|
+
${t.join(`
|
|
10
|
+
|
|
11
|
+
`)}`}r(S,"formatFileOperations");const p=2e3;function h(n){try{return JSON.stringify(n)??"undefined"}catch{return"[unserializable]"}}r(h,"safeJsonStringify");function g(n,e){if(n.length<=e)return n;const t=n.length-e;return`${n.slice(0,e)}
|
|
12
|
+
|
|
13
|
+
[... ${t} more characters truncated]`}r(g,"truncateForSummary");function k(n){const e=[];for(const t of n)if(t.role==="user"){const o=typeof t.content=="string"?t.content:t.content.filter(i=>i.type==="text").map(i=>i.text).join("");o&&e.push(`[User]: ${o}`)}else if(t.role==="assistant"){const o=[],i=[],c=[];for(const s of t.content)if(s.type==="text")o.push(s.text);else if(s.type==="thinking")i.push(s.thinking);else if(s.type==="toolCall"){const a=s.arguments,f=Object.entries(a).map(([l,u])=>`${l}=${h(u)}`).join(", ");c.push(`${s.name}(${f})`)}i.length>0&&e.push(`[Assistant thinking]: ${i.join(`
|
|
14
|
+
`)}`),o.length>0&&e.push(`[Assistant]: ${o.join(`
|
|
15
|
+
`)}`),c.length>0&&e.push(`[Assistant tool calls]: ${c.join("; ")}`)}else if(t.role==="toolResult"){const o=t.content.filter(i=>i.type==="text").map(i=>i.text).join("");o&&e.push(`[Tool result]: ${g(o,p)}`)}return e.join(`
|
|
16
|
+
|
|
17
|
+
`)}r(k,"serializeConversation");export{j as computeFileLists,m as createFileOps,$ as extractFileOpsFromMessage,S as formatFileOperations,k as serializeConversation};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type ExecutionEnv, ExecutionError, FileError, type FileInfo, type Result } from "../types.ts";
|
|
2
|
+
export declare class NodeExecutionEnv implements ExecutionEnv {
|
|
3
|
+
cwd: string;
|
|
4
|
+
private shellPath?;
|
|
5
|
+
private shellEnv?;
|
|
6
|
+
constructor(options: {
|
|
7
|
+
cwd: string;
|
|
8
|
+
shellPath?: string;
|
|
9
|
+
shellEnv?: NodeJS.ProcessEnv;
|
|
10
|
+
});
|
|
11
|
+
absolutePath(path: string): Promise<Result<string, FileError>>;
|
|
12
|
+
joinPath(parts: string[]): Promise<Result<string, FileError>>;
|
|
13
|
+
exec(command: string, options?: {
|
|
14
|
+
cwd?: string;
|
|
15
|
+
env?: Record<string, string>;
|
|
16
|
+
timeout?: number;
|
|
17
|
+
abortSignal?: AbortSignal;
|
|
18
|
+
onStdout?: (chunk: string) => void;
|
|
19
|
+
onStderr?: (chunk: string) => void;
|
|
20
|
+
}): Promise<Result<{
|
|
21
|
+
stdout: string;
|
|
22
|
+
stderr: string;
|
|
23
|
+
exitCode: number;
|
|
24
|
+
}, ExecutionError>>;
|
|
25
|
+
readTextFile(path: string, abortSignal?: AbortSignal): Promise<Result<string, FileError>>;
|
|
26
|
+
readTextLines(path: string, options?: {
|
|
27
|
+
maxLines?: number;
|
|
28
|
+
abortSignal?: AbortSignal;
|
|
29
|
+
}): Promise<Result<string[], FileError>>;
|
|
30
|
+
readBinaryFile(path: string, abortSignal?: AbortSignal): Promise<Result<Uint8Array, FileError>>;
|
|
31
|
+
writeFile(path: string, content: string | Uint8Array, abortSignal?: AbortSignal): Promise<Result<void, FileError>>;
|
|
32
|
+
appendFile(path: string, content: string | Uint8Array): Promise<Result<void, FileError>>;
|
|
33
|
+
fileInfo(path: string): Promise<Result<FileInfo, FileError>>;
|
|
34
|
+
listDir(path: string, abortSignal?: AbortSignal): Promise<Result<FileInfo[], FileError>>;
|
|
35
|
+
canonicalPath(path: string): Promise<Result<string, FileError>>;
|
|
36
|
+
exists(path: string): Promise<Result<boolean, FileError>>;
|
|
37
|
+
createDir(path: string, options?: {
|
|
38
|
+
recursive?: boolean;
|
|
39
|
+
}): Promise<Result<void, FileError>>;
|
|
40
|
+
remove(path: string, options?: {
|
|
41
|
+
recursive?: boolean;
|
|
42
|
+
force?: boolean;
|
|
43
|
+
}): Promise<Result<void, FileError>>;
|
|
44
|
+
createTempDir(prefix?: string): Promise<Result<string, FileError>>;
|
|
45
|
+
createTempFile(options?: {
|
|
46
|
+
prefix?: string;
|
|
47
|
+
suffix?: string;
|
|
48
|
+
}): Promise<Result<string, FileError>>;
|
|
49
|
+
cleanup(): Promise<void>;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var G=Object.defineProperty;var u=(n,r)=>G(n,"name",{value:r,configurable:!0});import{spawn as L}from"node:child_process";import{randomUUID as K}from"node:crypto";import{constants as B,createReadStream as H}from"node:fs";import{access as U,appendFile as j,lstat as A,mkdir as _,mkdtemp as z,readdir as V,readFile as O,realpath as X,rm as q,writeFile as $}from"node:fs/promises";import{tmpdir as J}from"node:os";import{isAbsolute as Q,join as D,resolve as P}from"node:path";import{createInterface as W}from"node:readline";import{ExecutionError as b,err as s,FileError as y,ok as o,toError as I}from"../types.js";function h(n,r){return Q(r)?r:P(n,r)}u(h,"resolvePath");function Y(n){if(n.isFile())return"file";if(n.isDirectory())return"directory";if(n.isSymbolicLink())return"symlink"}u(Y,"fileKindFromStats");function C(n,r){const e=Y(r);return e?o({name:n.replace(/\/+$/,"").split("/").pop()??n,path:n,kind:e,size:r.size,mtimeMs:r.mtimeMs}):s(new y("invalid","Unsupported file type",n))}u(C,"fileInfoFromStats");function Z(n){return n instanceof Error&&"code"in n}u(Z,"isNodeError");function f(n,r){if(n instanceof y)return n;const e=I(n);if(Z(n)){const t=n.message;switch(n.code){case"ABORT_ERR":return new y("aborted",t,r,e);case"ENOENT":return new y("not_found",t,r,e);case"EACCES":case"EPERM":return new y("permission_denied",t,r,e);case"ENOTDIR":return new y("not_directory",t,r,e);case"EISDIR":return new y("is_directory",t,r,e);case"EINVAL":return new y("invalid",t,r,e)}}return new y("unknown",e.message,r,e)}u(f,"toFileError");function g(n,r){return n?.aborted?s(new y("aborted","aborted",r)):void 0}u(g,"abortResult");async function S(n){try{return await U(n,B.F_OK),!0}catch{return!1}}u(S,"pathExists");async function N(n,r,e){return await new Promise(t=>{let i="",a;try{a=L(n,r,{stdio:["ignore","pipe","ignore"],windowsHide:!0})}catch{t({stdout:"",status:null});return}const c=setTimeout(()=>{a.pid&&R(a.pid)},e);a.stdout?.setEncoding("utf8"),a.stdout?.on("data",l=>{i+=l}),a.on("error",()=>{clearTimeout(c),t({stdout:"",status:null})}),a.on("close",l=>{clearTimeout(c),t({stdout:i,status:l})})})}u(N,"runCommand");async function M(){const n=process.platform==="win32"?await N("where",["bash.exe"],5e3):await N("which",["bash"],5e3);if(n.status!==0||!n.stdout)return null;const r=n.stdout.trim().split(/\r?\n/)[0];return r&&await S(r)?r:null}u(M,"findBashOnPath");async function ee(n){if(n)return await S(n)?o({shell:n,args:["-c"]}):s(new b("shell_unavailable",`Custom shell path not found: ${n}`));if(process.platform==="win32"){const e=[],t=process.env.ProgramFiles;t&&e.push(`${t}\\Git\\bin\\bash.exe`);const i=process.env["ProgramFiles(x86)"];i&&e.push(`${i}\\Git\\bin\\bash.exe`);for(const c of e)if(await S(c))return o({shell:c,args:["-c"]});const a=await M();return a?o({shell:a,args:["-c"]}):s(new b("shell_unavailable","No bash shell found"))}if(await S("/bin/bash"))return o({shell:"/bin/bash",args:["-c"]});const r=await M();return r?o({shell:r,args:["-c"]}):o({shell:"sh",args:["-c"]})}u(ee,"getShellConfig");function re(n,r){return{...process.env,...n,...r}}u(re,"getShellEnv");function R(n){if(process.platform==="win32"){try{L("taskkill",["/F","/T","/PID",String(n)],{stdio:"ignore",detached:!0,windowsHide:!0})}catch{}return}try{process.kill(-n,"SIGKILL")}catch{try{process.kill(n,"SIGKILL")}catch{}}}u(R,"killProcessTree");class de{static{u(this,"NodeExecutionEnv")}cwd;shellPath;shellEnv;constructor(r){this.cwd=r.cwd,this.shellPath=r.shellPath,this.shellEnv=r.shellEnv}async absolutePath(r){return o(h(this.cwd,r))}async joinPath(r){return o(D(...r))}async exec(r,e){if(e?.abortSignal?.aborted)return s(new b("aborted","aborted"));const t=e?.cwd?h(this.cwd,e.cwd):this.cwd,i=await ee(this.shellPath);return i.ok?await new Promise(a=>{let c="",l="",v=!1,p=!1,w,m,T;const k=u(()=>{m?.pid&&R(m.pid)},"onAbort"),E=u(d=>{T&&clearTimeout(T),e?.abortSignal&&e.abortSignal.removeEventListener("abort",k),!v&&(v=!0,a(d))},"settle");try{m=L(i.value.shell,[...i.value.args,r],{cwd:t,detached:process.platform!=="win32",env:re(this.shellEnv,e?.env),stdio:["ignore","pipe","pipe"],windowsHide:!0})}catch(d){const F=I(d);E(s(new b("spawn_error",F.message,F)));return}T=typeof e?.timeout=="number"?setTimeout(()=>{p=!0,m?.pid&&R(m.pid)},e.timeout*1e3):void 0,e?.abortSignal&&(e.abortSignal.aborted?k():e.abortSignal.addEventListener("abort",k,{once:!0})),m.stdout?.setEncoding("utf8"),m.stderr?.setEncoding("utf8"),m.stdout?.on("data",d=>{c+=d;try{e?.onStdout?.(d)}catch(F){const x=I(F);w=new b("callback_error",x.message,x),k()}}),m.stderr?.on("data",d=>{l+=d;try{e?.onStderr?.(d)}catch(F){const x=I(F);w=new b("callback_error",x.message,x),k()}}),m.on("error",d=>{E(s(new b("spawn_error",d.message,d)))}),m.on("close",d=>{if(w){E(s(w));return}if(p){E(s(new b("timeout",`timeout:${e?.timeout}`)));return}if(e?.abortSignal?.aborted){E(s(new b("aborted","aborted")));return}E(o({stdout:c,stderr:l,exitCode:d??0}))})}):i}async readTextFile(r,e){const t=h(this.cwd,r),i=g(e,t);if(i)return i;try{return o(await O(t,{encoding:"utf8",signal:e}))}catch(a){return s(f(a,t))}}async readTextLines(r,e){const t=h(this.cwd,r),i=g(e?.abortSignal,t);if(i)return i;if(e?.maxLines!==void 0&&e.maxLines<=0)return o([]);let a,c;try{a=H(t,{encoding:"utf8",signal:e?.abortSignal}),c=W({input:a,crlfDelay:1/0});const l=[];for await(const p of c){const w=g(e?.abortSignal,t);if(w)return w;if(l.push(p),e?.maxLines!==void 0&&l.length>=e.maxLines)break}const v=g(e?.abortSignal,t);return v||o(l)}catch(l){return s(f(l,t))}finally{c?.close(),a?.destroy()}}async readBinaryFile(r,e){const t=h(this.cwd,r),i=g(e,t);if(i)return i;try{return o(await O(t,{signal:e}))}catch(a){return s(f(a,t))}}async writeFile(r,e,t){const i=h(this.cwd,r),a=g(t,i);if(a)return a;try{await _(P(i,".."),{recursive:!0});const c=g(t,i);return c||(await $(i,e,{signal:t}),o(void 0))}catch(c){return s(f(c,i))}}async appendFile(r,e){const t=h(this.cwd,r);try{return await _(P(t,".."),{recursive:!0}),await j(t,e),o(void 0)}catch(i){return s(f(i,t))}}async fileInfo(r){const e=h(this.cwd,r);try{return C(e,await A(e))}catch(t){return s(f(t,e))}}async listDir(r,e){const t=h(this.cwd,r),i=g(e,t);if(i)return i;try{const a=await V(t,{withFileTypes:!0}),c=[];for(const l of a){const v=g(e,t);if(v)return v;const p=P(t,l.name);try{const w=C(p,await A(p));w.ok&&c.push(w.value)}catch(w){return s(f(w,p))}}return o(c)}catch(a){return s(f(a,t))}}async canonicalPath(r){const e=h(this.cwd,r);try{return o(await X(e))}catch(t){return s(f(t,e))}}async exists(r){const e=await this.fileInfo(r);return e.ok?o(!0):e.error.code==="not_found"?o(!1):s(e.error)}async createDir(r,e){const t=h(this.cwd,r);try{return await _(t,{recursive:e?.recursive??!0}),o(void 0)}catch(i){return s(f(i,t))}}async remove(r,e){const t=h(this.cwd,r);try{return await q(t,{recursive:e?.recursive??!1,force:e?.force??!1}),o(void 0)}catch(i){return s(f(i,t))}}async createTempDir(r="tmp-"){try{return o(await z(D(J(),r)))}catch(e){return s(f(e))}}async createTempFile(r){const e=await this.createTempDir("tmp-");if(!e.ok)return e;const t=D(e.value,`${r?.prefix??""}${K()}${r?.suffix??""}`);try{return await $(t,""),o(t)}catch(i){return s(f(i,t))}}async cleanup(){}}export{de as NodeExecutionEnv};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ImageContent, Message, TextContent } from "@openadapter/koda-ai";
|
|
2
|
+
import type { AgentMessage } from "../types.ts";
|
|
3
|
+
export declare const COMPACTION_SUMMARY_PREFIX = "The conversation history before this point was compacted into the following summary:\n\n<summary>\n";
|
|
4
|
+
export declare const COMPACTION_SUMMARY_SUFFIX = "\n</summary>";
|
|
5
|
+
export declare const BRANCH_SUMMARY_PREFIX = "The following is a summary of a branch that this conversation came back from:\n\n<summary>\n";
|
|
6
|
+
export declare const BRANCH_SUMMARY_SUFFIX = "</summary>";
|
|
7
|
+
export interface BashExecutionMessage {
|
|
8
|
+
role: "bashExecution";
|
|
9
|
+
command: string;
|
|
10
|
+
output: string;
|
|
11
|
+
exitCode: number | undefined;
|
|
12
|
+
cancelled: boolean;
|
|
13
|
+
truncated: boolean;
|
|
14
|
+
fullOutputPath?: string;
|
|
15
|
+
timestamp: number;
|
|
16
|
+
excludeFromContext?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface CustomMessage<T = unknown> {
|
|
19
|
+
role: "custom";
|
|
20
|
+
customType: string;
|
|
21
|
+
content: string | (TextContent | ImageContent)[];
|
|
22
|
+
display: boolean;
|
|
23
|
+
details?: T;
|
|
24
|
+
timestamp: number;
|
|
25
|
+
}
|
|
26
|
+
export interface BranchSummaryMessage {
|
|
27
|
+
role: "branchSummary";
|
|
28
|
+
summary: string;
|
|
29
|
+
fromId: string;
|
|
30
|
+
timestamp: number;
|
|
31
|
+
}
|
|
32
|
+
export interface CompactionSummaryMessage {
|
|
33
|
+
role: "compactionSummary";
|
|
34
|
+
summary: string;
|
|
35
|
+
tokensBefore: number;
|
|
36
|
+
timestamp: number;
|
|
37
|
+
}
|
|
38
|
+
declare module "../types.ts" {
|
|
39
|
+
interface CustomAgentMessages {
|
|
40
|
+
bashExecution: BashExecutionMessage;
|
|
41
|
+
custom: CustomMessage;
|
|
42
|
+
branchSummary: BranchSummaryMessage;
|
|
43
|
+
compactionSummary: CompactionSummaryMessage;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export declare function bashExecutionToText(msg: BashExecutionMessage): string;
|
|
47
|
+
export declare function createBranchSummaryMessage(summary: string, fromId: string, timestamp: string): BranchSummaryMessage;
|
|
48
|
+
export declare function createCompactionSummaryMessage(summary: string, tokensBefore: number, timestamp: string): CompactionSummaryMessage;
|
|
49
|
+
export declare function createCustomMessage(customType: string, content: string | (TextContent | ImageContent)[], display: boolean, details: unknown | undefined, timestamp: string): CustomMessage;
|
|
50
|
+
export declare function convertToLlm(messages: AgentMessage[]): Message[];
|