@roll-agent/runtime 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/agent-session.d.ts +36 -2
- package/dist/engine/agent-session.js +1 -1
- package/dist/engine/compactor.d.ts +21 -0
- package/dist/engine/compactor.js +1 -0
- package/dist/engine/context-window.d.ts +2 -0
- package/dist/engine/context-window.js +1 -0
- package/dist/engine/conversation-engine.d.ts +8 -1
- package/dist/engine/conversation-engine.js +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +1 -1
- package/dist/server/protocol.d.ts +8 -0
- package/dist/server/protocol.js +1 -1
- package/dist/server/runtime-server.js +1 -1
- package/dist/store/thread-store.d.ts +1 -0
- package/dist/store/thread-store.js +1 -1
- package/dist/types/events.d.ts +26 -0
- package/package.json +2 -2
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import { type ModelMessage } from "ai";
|
|
2
|
-
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
3
|
-
import type { SessionEvent } from "../types/events.ts";
|
|
2
|
+
import type { LanguageModelV3, SharedV3ProviderOptions } from "@ai-sdk/provider";
|
|
3
|
+
import type { ContextCompactionReason, ContextCompactionStrategy, SessionEvent, SessionTokenUsage } from "../types/events.ts";
|
|
4
4
|
import type { ToolPolicy } from "../types/policy.ts";
|
|
5
5
|
import { type AgentToolSource } from "../tool-bridge/build-tools.ts";
|
|
6
|
+
export interface SessionCompactionSettings {
|
|
7
|
+
readonly enabled: boolean;
|
|
8
|
+
readonly strategy: ContextCompactionStrategy;
|
|
9
|
+
readonly threshold: number;
|
|
10
|
+
readonly keepRecentTurns: number;
|
|
11
|
+
readonly keepRecentTokens: number;
|
|
12
|
+
}
|
|
6
13
|
export interface AgentSessionOptions {
|
|
7
14
|
readonly id: string;
|
|
8
15
|
readonly model: LanguageModelV3;
|
|
@@ -11,6 +18,12 @@ export interface AgentSessionOptions {
|
|
|
11
18
|
readonly policy?: ToolPolicy;
|
|
12
19
|
readonly initialMessages?: readonly ModelMessage[];
|
|
13
20
|
readonly onPersist?: (messages: readonly ModelMessage[]) => void;
|
|
21
|
+
readonly onReplace?: (messages: readonly ModelMessage[]) => void;
|
|
22
|
+
readonly contextWindow?: number;
|
|
23
|
+
readonly compaction?: SessionCompactionSettings;
|
|
24
|
+
readonly turnTimeoutMs?: number;
|
|
25
|
+
readonly providerOptions?: SharedV3ProviderOptions;
|
|
26
|
+
readonly debugEvents?: boolean;
|
|
14
27
|
}
|
|
15
28
|
export declare class AgentSession {
|
|
16
29
|
readonly id: string;
|
|
@@ -18,17 +31,38 @@ export declare class AgentSession {
|
|
|
18
31
|
private readonly maxSteps;
|
|
19
32
|
private readonly messages;
|
|
20
33
|
private readonly onPersist;
|
|
34
|
+
private readonly onReplace;
|
|
35
|
+
private readonly contextWindow;
|
|
36
|
+
private readonly compaction;
|
|
37
|
+
private readonly turnTimeoutMs;
|
|
38
|
+
private providerOptions;
|
|
39
|
+
private readonly debugEvents;
|
|
21
40
|
private readonly gate;
|
|
22
41
|
private readonly tools;
|
|
23
42
|
private readonly registry;
|
|
24
43
|
private emit;
|
|
25
44
|
private activeTurn;
|
|
45
|
+
private sessionUsage;
|
|
46
|
+
private lastInputTokens;
|
|
47
|
+
private needsCompaction;
|
|
26
48
|
constructor(options: AgentSessionOptions);
|
|
27
49
|
send(input: string): AsyncIterable<SessionEvent>;
|
|
50
|
+
compact(reason?: ContextCompactionReason): AsyncIterable<SessionEvent>;
|
|
28
51
|
private runTurn;
|
|
29
52
|
private requestApproval;
|
|
30
53
|
approve(approvalId: string): boolean;
|
|
31
54
|
reject(approvalId: string, reason?: string): boolean;
|
|
32
55
|
getMessages(): readonly ModelMessage[];
|
|
56
|
+
getContextWindow(): number | undefined;
|
|
57
|
+
getSessionUsage(): SessionTokenUsage;
|
|
58
|
+
setProviderOptions(providerOptions: SharedV3ProviderOptions | undefined): void;
|
|
59
|
+
private shouldAutoCompact;
|
|
60
|
+
private runCompactionTurn;
|
|
61
|
+
private runCompaction;
|
|
62
|
+
private recoverFromContextError;
|
|
63
|
+
private isTurnAborted;
|
|
33
64
|
abort(): void;
|
|
65
|
+
private debug;
|
|
66
|
+
private scheduleDebug;
|
|
67
|
+
private clearDebugTimer;
|
|
34
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{stepCountIs as t,streamText as s}from"ai";import{buildAgentToolset as o}from"../tool-bridge/build-tools.js";import{readIsError as r}from"../tool-bridge/normalize-result.js";import{ApprovalGate as a}from"../approval/approval-gate.js";import{AsyncEventQueue as i}from"./event-queue.js";function l(e){return e instanceof Error?e.message:String(e)}function n(e){return{...void 0!==e.inputTokens?{inputTokens:e.inputTokens}:{},...void 0!==e.outputTokens?{outputTokens:e.outputTokens}:{},...void 0!==e.totalTokens?{totalTokens:e.totalTokens}:{}}}export class AgentSession{id;model;maxSteps;messages;onPersist;gate=new a;tools;registry;emit;activeTurn;constructor(e){this.id=e.id,this.model=e.model,this.maxSteps=e.maxSteps,this.messages=e.initialMessages?[...e.initialMessages]:[],this.onPersist=e.onPersist;const t=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)});this.tools=t.tools,this.registry=t.registry}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new i;this.emit=e=>t.push(e);const s=this.messages.length;this.messages.push({role:"user",content:e});const o={abortController:new AbortController,aborted:!1};this.activeTurn=o,this.runTurn(t,o,s).catch(e=>{t.push({type:"error",stage:"execute",message:l(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===o&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async runTurn(o,a,i){try{o.push({type:"message-start",messageId:e()});const p=s({model:this.model,messages:this.messages,tools:this.tools,stopWhen:t(this.maxSteps),abortSignal:a.abortController.signal});let u,m,h="";for await(const e of p.fullStream)switch(e.type){case"text-delta":h+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{const t=this.registry.resolve(e.toolName);o.push({type:"tool-call",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,input:e.input});break}case"tool-result":{const t=this.registry.resolve(e.toolName);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:e.output,isError:r(e.output)});break}case"tool-error":{const t=this.registry.resolve(e.toolName);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:l(e.error),isError:!0});break}case"finish-step":o.push({type:"step-finish",finishReason:e.finishReason,usage:n(e.usage)});break;case"finish":u=n(e.totalUsage);break;case"error":o.push({type:"error",stage:"execute",message:l(e.error)});break;case"abort":a.aborted=!0}if(a.aborted||a.abortController.signal.aborted)return this.messages.splice(i),void o.push({type:"error",stage:"execute",message:"aborted"});try{m=(await p.response).messages}catch(e){return this.messages.splice(i),void o.push({type:"error",stage:"execute",message:l(e)})}this.messages.push(...m),this.onPersist?.(this.messages.slice(i)),o.push({type:"message-finish",text:h,...u?{totalUsage:u}:{}})}catch(e){this.messages.splice(i),o.push({type:"error",stage:"execute",message:l(e)})}finally{this.activeTurn===a&&(this.activeTurn=void 0),o.close()}}requestApproval(t){const s=e(),o=this.gate.request(s);return void 0===this.emit?(this.gate.resolve(s,{approved:!1,reason:"approval event could not be delivered"}),o):(this.emit({type:"confirmation-required",approvalId:s,agentName:t.agentName,toolName:t.toolName,input:t.input,...t.reason?{reason:t.reason}:{}}),o)}approve(e){return this.gate.resolve(e,{approved:!0})}reject(e,t){return this.gate.resolve(e,{approved:!1,...t?{reason:t}:{}})}getMessages(){return this.messages}abort(){this.activeTurn&&(this.activeTurn.aborted=!0),this.gate.abortAll(),this.activeTurn?.abortController.abort()}}
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{stepCountIs as t,streamText as s}from"ai";import{buildAgentToolset as o}from"../tool-bridge/build-tools.js";import{readIsError as n}from"../tool-bridge/normalize-result.js";import{ApprovalGate as i}from"../approval/approval-gate.js";import{compactMessages as a}from"./compactor.js";import{AsyncEventQueue as r}from"./event-queue.js";const l="你是 Roll chat 的运行时助手。你可以使用模型的 thinking/reasoning 能力完成内部推理,但必须把给用户看的最终回复写入普通 text 输出通道;不要只在 reasoning 中写最终答案。 工具调用完成后,也要在普通 text 输出通道给出简洁结论。最终回复不要重复。 不要复述用户输入;如果需要调用工具,直接调用工具,不要先输出用户原文或无意义前置文本。";function u(e){return e instanceof Error?e.message:String(e)}function p(e){return"object"==typeof e&&null!==e&&"output"in e}function h(e){return p(e)?u(e.output):u(e)}function c(e){const t=h(e);return t.startsWith("已取消执行")||t.startsWith("策略拒绝执行")?t:void 0}function g(e){return/context[_ -]?length|context window|maximum context|token limit|too many tokens|prompt is too long|input is too long/i.test(u(e))}function m(e){const t=e.inputTokenDetails?.cacheReadTokens,s=e.outputTokenDetails?.reasoningTokens;return{...void 0!==e.inputTokens?{inputTokens:e.inputTokens}:{},...void 0!==e.outputTokens?{outputTokens:e.outputTokens}:{},...void 0!==e.totalTokens?{totalTokens:e.totalTokens}:{},...void 0!==t?{cachedInputTokens:t}:{},...void 0!==s?{reasoningTokens:s}:{}}}function d(e,t){return void 0===e&&void 0===t?void 0:(e??0)+(t??0)}function v(e,t){const s=d(e.cachedInputTokens,t.cachedInputTokens),o=d(e.reasoningTokens,t.reasoningTokens);return{inputTokens:(e.inputTokens??0)+(t.inputTokens??0),outputTokens:(e.outputTokens??0)+(t.outputTokens??0),totalTokens:(e.totalTokens??0)+(t.totalTokens??0),...void 0!==s?{cachedInputTokens:s}:{},...void 0!==o?{reasoningTokens:o}:{}}}function T(e,t){return void 0===t?e:void 0===e?t:Math.max(e,t)}function b(e,t){const s=e.trim(),o=t.trim();return s.length>0&&o.length>0&&o.startsWith(s)}function k(e){return e.flatMap(e=>{if("assistant"!==e.role||!Array.isArray(e.content))return[e];const t=e.content.filter(e=>"reasoning"!==e.type);return 0===t.length?[]:t.length===e.content.length?[e]:[{...e,content:t}]})}export class AgentSession{id;model;maxSteps;messages;onPersist;onReplace;contextWindow;compaction;turnTimeoutMs;providerOptions;debugEvents;gate=new i;tools;registry;emit;activeTurn;sessionUsage={inputTokens:0,outputTokens:0,totalTokens:0};lastInputTokens;needsCompaction=!1;constructor(e){this.id=e.id,this.model=e.model,this.maxSteps=e.maxSteps,this.messages=e.initialMessages?k(e.initialMessages):[],this.onPersist=e.onPersist,this.onReplace=e.onReplace,this.contextWindow=e.contextWindow,this.compaction=e.compaction,this.turnTimeoutMs=e.turnTimeoutMs,this.providerOptions=e.providerOptions,this.debugEvents=e.debugEvents??!1;const t=o(e.sources,{...e.policy?{policy:e.policy}:{},requestApproval:e=>this.requestApproval(e)});this.tools=t.tools,this.registry=t.registry}async*send(e){if(this.activeTurn)throw new Error("session already has an active turn");const t=new r;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"execute",message:u(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async*compact(e="manual"){if(this.activeTurn)throw new Error("session already has an active turn");const t=new r;this.emit=e=>t.push(e);const s={abortController:new AbortController,aborted:!1};this.activeTurn=s,this.runCompactionTurn(t,s,e).catch(e=>{t.push({type:"error",stage:"plan",message:u(e)}),t.close()});try{for await(const e of t)yield e}finally{this.activeTurn===s&&this.abort(),void 0!==this.emit&&(this.emit=void 0)}}async runTurn(o,i,a){const r=Date.now();let p;try{if(this.debug(o,"turn","start",r,{messages:this.messages.length,tools:Object.keys(this.tools).length,maxSteps:this.maxSteps,...void 0!==this.contextWindow?{contextWindow:this.contextWindow}:{},...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}}),this.shouldAutoCompact()){this.debug(o,"compaction","auto requested before turn",r,{messages:this.messages.length,...void 0!==this.lastInputTokens?{lastInputTokens:this.lastInputTokens}:{}});try{await this.runCompaction(o,"auto",i)}catch(e){return void o.push({type:"error",stage:"plan",message:u(e)})}}if(i.aborted||i.abortController.signal.aborted)return void o.push({type:"error",stage:"execute",message:"aborted"});p=this.messages.length,this.messages.push({role:"user",content:a}),o.push({type:"message-start",messageId:e()}),this.debug(o,"model","calling streamText",r,{messages:this.messages.length,tools:Object.keys(this.tools).length,...void 0!==this.turnTimeoutMs?{timeoutMs:this.turnTimeoutMs}:{}});const d=s({model:this.model,system:l,messages:this.messages,tools:this.tools,stopWhen:t(this.maxSteps),abortSignal:i.abortController.signal,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==this.turnTimeoutMs?{timeout:{totalMs:this.turnTimeoutMs}}:{}});this.debug(o,"model","streamText returned",r);let y,f,x,w,C,I="",N="",M=!1,R=0,A=!1;const S=this.scheduleDebug(o,"model","waiting for first stream event",r,{messages:this.messages.length});try{for await(const e of d.fullStream){switch(A||(A=!0,this.clearDebugTimer(S),this.debug(o,"model","first stream event",r,{part:e.type})),e.type){case"text-delta":if(!M){const t=N+e.text;if(b(t,a)){N=t;break}if(N.length>0){const t=N+e.text;N="",I+=t,o.push({type:"text-delta",delta:t});break}}I+=e.text,o.push({type:"text-delta",delta:e.text});break;case"tool-call":{M=!0,N="";const t=this.registry.resolve(e.toolName);o.push({type:"tool-call",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,input:e.input});break}case"tool-result":{const t=this.registry.resolve(e.toolName),s=n(e.output);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:e.output,isError:s}),i.aborted||i.abortController.signal.aborted||!s||(C=c(e.output));break}case"tool-error":{const t=this.registry.resolve(e.toolName),s=h(e.error);o.push({type:"tool-result",toolCallId:e.toolCallId,agentName:t?.agentName??e.toolName,toolName:t?.toolName??e.toolName,output:s,isError:!0}),i.aborted||i.abortController.signal.aborted||(C=c(e.error));break}case"finish-step":{const t=m(e.usage);f=T(f,t.inputTokens),R+=1,x=e.finishReason,o.push({type:"step-finish",finishReason:e.finishReason,usage:t});break}case"finish":!M&&N.length>0&&(I+=N,o.push({type:"text-delta",delta:N}),N=""),y=m(e.totalUsage);break;case"error":g(e.error)&&(this.needsCompaction=!0),w=u(e.error),o.push({type:"error",stage:"execute",message:w});break;case"abort":i.aborted=!0}if(void 0!==C)break}}finally{this.clearDebugTimer(S)}if(this.debug(o,"model","fullStream finished",r,{textChars:I.length}),void 0!==w)return this.messages.splice(p),void(this.needsCompaction&&await this.recoverFromContextError(o,i));if(i.aborted||i.abortController.signal.aborted)return this.messages.splice(p),void o.push({type:"error",stage:"execute",message:"aborted"});if(void 0!==C){const e=C,t=0===I.length?e:`\n${e}`;o.push({type:"text-delta",delta:t}),this.messages.push({role:"assistant",content:e}),this.debug(o,"persist","persisting messages",r,{appendedMessages:this.messages.length-p}),this.onPersist?.(this.messages.slice(p)),this.debug(o,"persist","messages persisted",r,{totalMessages:this.messages.length}),y&&(this.sessionUsage=v(this.sessionUsage,y));const s=f??y?.inputTokens;return void 0!==s&&(this.lastInputTokens=s),void o.push({type:"message-finish",text:e,...y?{totalUsage:y}:{},sessionUsage:{...this.sessionUsage},...void 0!==f?{contextInputTokens:f}:{}})}let E;this.debug(o,"model","awaiting response messages",r);const U=this.scheduleDebug(o,"model","still awaiting response messages",r);try{E=(await d.response).messages}catch(e){return this.clearDebugTimer(U),g(e)&&(this.needsCompaction=!0),this.messages.splice(p),o.push({type:"error",stage:"execute",message:u(e)}),void(this.needsCompaction&&await this.recoverFromContextError(o,i))}finally{this.clearDebugTimer(U)}this.debug(o,"model","response messages ready",r,{responseMessages:E.length});const W=k(E);this.messages.push(...W),this.debug(o,"persist","persisting messages",r,{appendedMessages:this.messages.length-p}),this.onPersist?.(this.messages.slice(p)),this.debug(o,"persist","messages persisted",r,{totalMessages:this.messages.length}),y&&(this.sessionUsage=v(this.sessionUsage,y));const D=f??y?.inputTokens;void 0!==D&&(this.lastInputTokens=D);const O=R>=this.maxSteps&&"tool-calls"===x;o.push({type:"message-finish",text:I,...y?{totalUsage:y}:{},sessionUsage:{...this.sessionUsage},...void 0!==f?{contextInputTokens:f}:{},...O?{stoppedAtStepLimit:!0}:{}})}catch(e){void 0!==p&&this.messages.splice(p),g(e)&&(this.needsCompaction=!0),this.debug(o,"turn","error",r,{message:u(e)}),o.push({type:"error",stage:"execute",message:u(e)}),this.needsCompaction&&await this.recoverFromContextError(o,i)}finally{this.activeTurn===i&&(this.activeTurn=void 0),o.close()}}requestApproval(t){const s=e(),o=this.gate.request(s);return void 0===this.emit?(this.gate.resolve(s,{approved:!1,reason:"approval event could not be delivered"}),o):(this.emit({type:"confirmation-required",approvalId:s,agentName:t.agentName,toolName:t.toolName,input:t.input,...t.reason?{reason:t.reason}:{}}),o)}approve(e){return this.gate.resolve(e,{approved:!0})}reject(e,t){return this.gate.resolve(e,{approved:!1,...t?{reason:t}:{}})}getMessages(){return this.messages}getContextWindow(){return this.contextWindow}getSessionUsage(){return{...this.sessionUsage}}setProviderOptions(e){this.providerOptions=e}shouldAutoCompact(){const e=this.compaction;return void 0!==e&&e.enabled&&(this.needsCompaction||void 0!==this.contextWindow&&void 0!==this.lastInputTokens&&this.lastInputTokens/this.contextWindow>=e.threshold)}async runCompactionTurn(e,t,s){try{await this.runCompaction(e,s,t)}catch(t){e.push({type:"error",stage:"plan",message:u(t)})}finally{this.activeTurn===t&&(this.activeTurn=void 0),e.close()}}async runCompaction(e,t,s){const o=Date.now(),n=this.compaction,i=n?.strategy??"summarize";if(this.debug(e,"compaction","start",o,{reason:t,messages:this.messages.length}),e.push({type:"compaction-start",reason:t}),this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if(!n)return void e.push({type:"context-compacted",reason:t,strategy:i,removed:0,kept:this.messages.length});const r=this.lastInputTokens;let l=n.strategy;const p=s?.abortController.signal;let h;try{h=await a({messages:this.messages,strategy:l,keepRecentTurns:n.keepRecentTurns,keepRecentTokens:n.keepRecentTokens,model:this.model,...p?{abortSignal:p}:{}})}catch(t){if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});if("summarize"!==l)throw t;this.debug(e,"compaction","summarize failed, fallback to truncate",o,{message:u(t)}),l="truncate",h=await a({messages:this.messages,strategy:l,keepRecentTurns:n.keepRecentTurns,keepRecentTokens:n.keepRecentTokens,model:this.model,...p?{abortSignal:p}:{}})}if(this.isTurnAborted(s))return void e.push({type:"error",stage:"plan",message:"aborted"});const c=h.removed>0||h.truncatedTools>0;c&&(this.onReplace?.(h.messages),this.messages.splice(0,this.messages.length,...h.messages),this.lastInputTokens=void 0,this.needsCompaction=!1),e.push({type:"context-compacted",reason:t,strategy:l,removed:h.removed,kept:h.kept,...h.truncatedTools>0?{truncatedTools:h.truncatedTools}:{},...void 0!==r?{beforeInputTokens:r}:{}}),this.debug(e,"compaction","finish",o,{reason:t,strategy:l,removed:h.removed,kept:h.kept,truncatedTools:h.truncatedTools,progressed:c})}async recoverFromContextError(e,t){if(this.compaction?.enabled)try{await this.runCompaction(e,"auto",t)}catch(t){e.push({type:"error",stage:"plan",message:u(t)})}}isTurnAborted(e){return!0===e?.aborted||!0===e?.abortController.signal.aborted}abort(){this.activeTurn&&(this.activeTurn.aborted=!0),this.gate.abortAll(),this.activeTurn?.abortController.abort()}debug(e,t,s,o,n){this.debugEvents&&e.push({type:"debug",stage:t,message:s,...void 0!==o?{elapsedMs:Date.now()-o}:{},...void 0!==n?{data:n}:{}})}scheduleDebug(e,t,s,o,n){if(this.debugEvents)return setTimeout(()=>{this.debug(e,t,s,o,n)},5e3)}clearDebugTimer(e){void 0!==e&&clearTimeout(e)}}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ModelMessage } from "ai";
|
|
2
|
+
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
3
|
+
import type { ContextCompactionStrategy } from "../types/events.ts";
|
|
4
|
+
export interface CompactionInput {
|
|
5
|
+
readonly messages: readonly ModelMessage[];
|
|
6
|
+
readonly strategy: ContextCompactionStrategy;
|
|
7
|
+
readonly keepRecentTurns: number;
|
|
8
|
+
readonly keepRecentTokens: number;
|
|
9
|
+
readonly model: LanguageModelV3;
|
|
10
|
+
readonly abortSignal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export interface CompactionResult {
|
|
13
|
+
readonly messages: ModelMessage[];
|
|
14
|
+
readonly removed: number;
|
|
15
|
+
readonly kept: number;
|
|
16
|
+
readonly truncatedTools: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const SUMMARY_PREFIX = "\u4EE5\u4E0B\u6458\u8981\u7531\u53E6\u4E00\u4E2A\u8BED\u8A00\u6A21\u578B\u5728\u538B\u7F29\u65E9\u524D\u5BF9\u8BDD\u540E\u4EA7\u51FA\u3002\u8BF7\u636E\u6B64\u7EE7\u7EED\u63A8\u8FDB\u3001\u907F\u514D\u91CD\u590D\u5DF2\u5B8C\u6210\u7684\u5DE5\u4F5C:";
|
|
19
|
+
export declare const SUMMARY_ACK = "\u597D\u7684,\u6211\u5DF2\u8BFB\u53D6\u4E4B\u524D\u5DE5\u4F5C\u7684\u4EA4\u63A5\u6458\u8981,\u7EE7\u7EED\u63A8\u8FDB\u3002";
|
|
20
|
+
export declare function findTurnBoundaries(messages: readonly ModelMessage[]): number[];
|
|
21
|
+
export declare function compactMessages(input: CompactionInput): Promise<CompactionResult>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{generateText as t}from"ai";const e=2e3,n=15e3,o=3.5,r="你在执行上下文 checkpoint 压缩,为另一个将接手这项任务的语言模型撰写交接摘要。请包含:当前进度与已做的关键决定;重要约束、上下文与用户偏好;尚未完成的事项与明确的下一步;继续工作所需的关键数据、示例或引用。简洁、结构化,聚焦于让下一个模型无缝接续,不要寒暄,直接输出摘要正文。";export const SUMMARY_PREFIX="以下摘要由另一个语言模型在压缩早前对话后产出。请据此继续推进、避免重复已完成的工作:";export const SUMMARY_ACK="好的,我已读取之前工作的交接摘要,继续推进。";export function findTurnBoundaries(t){const e=[];return t.forEach((t,n)=>{"user"===t.role&&e.push(n)}),e}function s(t){return Math.ceil(JSON.stringify(t).length/o)}function u(t,e){return t.length<=e?0:t[t.length-e]??0}function a(t,e,n){let o=e[e.length-1]??0,r=0;for(let u=e.length-1;u>=0;u-=1){const a=e[u]??0,l=u+1<e.length?e[u+1]??t.length:t.length;let c=0;for(let e=a;e<l;e+=1){const n=t[e];void 0!==n&&(c+=s(n))}if(u<e.length-1&&r+c>n)return e[u+1]??a;r+=c,o=a}return o}function l(t,e,n){const o=findTurnBoundaries(t);return 0===o.length?0:Math.min(a(t,o,n),u(o,e))}function c(t){if("object"!=typeof t||null===t)return"";const e=t;return"text"===e.type&&"string"==typeof e.text?e.text:"tool-call"===e.type&&"string"==typeof e.toolName?`[调用 ${e.toolName}]`:"tool-result"===e.type?"[工具结果]":""}function i(t){const{content:e}=t;if("string"==typeof e)return`${t.role}: ${e}`;const n=e.map(c).filter(t=>t.length>0).join(" ");return`${t.role}: ${n}`}function g(t){if(t?.aborted)throw new Error("aborted")}async function f(e,o,s){const u=e.map(i).join("\n\n"),{text:a}=await t({model:o,system:r,prompt:u,maxOutputTokens:1024,maxRetries:0,...s?{abortSignal:s}:{},timeout:{totalMs:n}});return[{role:"user",content:`${SUMMARY_PREFIX}\n\n${a}`},{role:"assistant",content:SUMMARY_ACK}]}function m(t){let n=0;return{messages:t.map(t=>{if("tool"!==t.role)return t;const o=t.content.map(t=>{if("tool-result"!==t.type)return t;const o=JSON.stringify(t.output);return o.length<=e?t:(n+=1,{...t,output:{type:"text",value:`[已省略 ${String(o.length)} 字符的工具结果,如需最新内容请重新调用对应工具]`}})});return{...t,content:o}}),truncated:n}}export async function compactMessages(t){g(t.abortSignal);const e=l(t.messages,t.keepRecentTurns,t.keepRecentTokens);if(0===e){const{messages:e,truncated:n}=m(t.messages);return{messages:e,removed:0,kept:e.length,truncatedTools:n}}const n=t.messages.slice(0,e),o=t.messages.slice(e),{messages:r,truncated:s}=m(o),u="summarize"===t.strategy?await f(n,t.model,t.abortSignal):[];return g(t.abortSignal),{messages:[...u,...r],removed:n.length,kept:r.length,truncatedTools:s}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const w=[{match:"claude-fable-5",window:1e6},{match:"claude-mythos-5",window:1e6},{match:"claude-opus-4-8",window:1e6},{match:"claude-sonnet-4-6",window:1e6},{match:"claude-haiku-4-5",window:2e5},{match:"claude",window:2e5},{match:"gpt-5.5",window:105e4},{match:"gpt-5.4-mini",window:4e5},{match:"gpt-5.4-nano",window:4e5},{match:"gpt-5.4",window:105e4},{match:"gpt-5-mini",window:4e5},{match:"gpt-5-nano",window:4e5},{match:"gpt-5",window:4e5},{match:"gpt-4.1",window:128e3},{match:"gpt-4o",window:128e3},{match:"gpt-4-turbo",window:128e3},{match:"o3",window:2e5},{match:"o1-mini",window:128e3},{match:"o1",window:2e5},{match:"deepseek-v4",window:1e6},{match:"deepseek-chat",window:1e6},{match:"deepseek-reasoner",window:1e6},{match:"deepseek",window:128e3},{match:"qwen-long",window:1e7},{match:"qwen3-coder",window:1e6},{match:"qwen3.7-plus",window:1e6},{match:"qwen3.7-max",window:1e6},{match:"qwen3.6-plus",window:1e6},{match:"qwen3.5-plus",window:1e6},{match:"qwen3.5-flash",window:1e6},{match:"qwen-plus",window:1e6},{match:"qwen-flash",window:1e6},{match:"qwen3-max",window:262144},{match:"qwen3-vl-plus",window:262144},{match:"qwen-vl-plus",window:131072},{match:"qwen",window:131072},{match:"gemini",window:1e6},{match:"kimi",window:128e3},{match:"glm",window:128e3}];export function lookupContextWindow(e){const n=e.toLowerCase();return w.find(w=>n.includes(w.match))?.window}export function resolveContextWindow(w,e){return void 0!==e?e:lookupContextWindow(w)}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { LanguageModelV3 } from "@ai-sdk/provider";
|
|
1
|
+
import type { LanguageModelV3, SharedV3ProviderOptions } from "@ai-sdk/provider";
|
|
2
2
|
import { McpClientManager } from "@roll-agent/core/mcp/client-manager";
|
|
3
3
|
import type { RollConfig } from "@roll-agent/core/config/schema";
|
|
4
4
|
import type { RegisteredAgent } from "@roll-agent/core/types/agent";
|
|
@@ -6,6 +6,7 @@ import type { AgentToolSource } from "../tool-bridge/build-tools.ts";
|
|
|
6
6
|
import type { ToolPolicy } from "../types/policy.ts";
|
|
7
7
|
import type { ThreadStore } from "../store/thread-store.ts";
|
|
8
8
|
import { AgentSession } from "./agent-session.ts";
|
|
9
|
+
export type EnsureAgentReady = (agent: RegisteredAgent, env: Readonly<Record<string, string>> | undefined) => Promise<void>;
|
|
9
10
|
export interface ConversationEngineOptions {
|
|
10
11
|
readonly config: RollConfig;
|
|
11
12
|
readonly agents?: readonly RegisteredAgent[];
|
|
@@ -15,6 +16,9 @@ export interface ConversationEngineOptions {
|
|
|
15
16
|
readonly store?: ThreadStore;
|
|
16
17
|
readonly policy?: ToolPolicy;
|
|
17
18
|
readonly maxSteps?: number;
|
|
19
|
+
readonly providerOptions?: SharedV3ProviderOptions;
|
|
20
|
+
readonly ensureAgentReady?: EnsureAgentReady;
|
|
21
|
+
readonly debugEvents?: boolean;
|
|
18
22
|
readonly onAgentBootstrapIssue?: (issue: AgentBootstrapIssue) => void;
|
|
19
23
|
}
|
|
20
24
|
export interface CreateSessionInput {
|
|
@@ -30,6 +34,9 @@ export declare class ConversationEngine {
|
|
|
30
34
|
private readonly store;
|
|
31
35
|
private readonly policy;
|
|
32
36
|
private readonly maxSteps;
|
|
37
|
+
private readonly providerOptions;
|
|
38
|
+
private readonly ensureAgentReady;
|
|
39
|
+
private readonly debugEvents;
|
|
33
40
|
private readonly explicitAgents;
|
|
34
41
|
private readonly explicitModel;
|
|
35
42
|
private readonly explicitSources;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{randomUUID as e}from"node:crypto";import{McpClientManager as t}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as s}from"@roll-agent/core/llm/providers";import{AgentStore as
|
|
1
|
+
import{randomUUID as e}from"node:crypto";import{McpClientManager as t}from"@roll-agent/core/mcp/client-manager";import{createProviderModel as s}from"@roll-agent/core/llm/providers";import{AgentStore as i}from"@roll-agent/core/registry/store";import{resolveTransportWithDevSpawnSpec as o}from"@roll-agent/core/registry/dev-spawn";import{getAgentPid as r,startAgent as n,waitForAgentReady as a}from"@roll-agent/core/registry/process-manager";import{normalizeListedTools as l}from"@roll-agent/core/cli/utils/agent-tools";import{getAgentEnv as c}from"@roll-agent/core/config/helpers";import{AgentSession as d}from"./agent-session.js";import{resolveContextWindow as p}from"./context-window.js";const h=80;function m(e){if("object"!=typeof e||null===e||!("annotations"in e))return;const t=e.annotations;if("object"!=typeof t||null===t)return;const s={};return"readOnlyHint"in t&&"boolean"==typeof t.readOnlyHint&&(s.readOnlyHint=t.readOnlyHint),"destructiveHint"in t&&"boolean"==typeof t.destructiveHint&&(s.destructiveHint=t.destructiveHint),void 0===s.readOnlyHint&&void 0===s.destructiveHint?void 0:s}async function u(e,t,s){"core-managed"===e.runtime.ownership&&(void 0===r(t,e.skill.name)&&n(e,t,s),await a(e))}export class ConversationEngine{config;clientManager;store;policy;maxSteps;providerOptions;ensureAgentReady;debugEvents;explicitAgents;explicitModel;explicitSources;onAgentBootstrapIssue;ready;constructor(e){this.config=e.config,this.clientManager=e.clientManager??new t,this.store=e.store,this.policy=e.policy,this.maxSteps=e.maxSteps??h,this.providerOptions=e.providerOptions,this.ensureAgentReady=e.ensureAgentReady??((e,t)=>u(e,this.config.agents.dataDir,t)),this.debugEvents=e.debugEvents??!1,this.explicitAgents=e.agents,this.explicitModel=e.model,this.explicitSources=e.sources,this.onAgentBootstrapIssue=e.onAgentBootstrapIssue}async createSession(t={}){const s=await this.ensureReady(),i=this.store?this.store.createThread({...t.title?{title:t.title}:{},model:this.resolveModelName()}):e();return this.buildSession(s,i,[])}async resumeSession(e){if(!this.store)throw new Error("resumeSession requires a ThreadStore");if(!this.store.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const t=await this.ensureReady();return this.buildSession(t,e,this.store.getMessages(e))}buildSession(e,t,s){const i=this.store,o=p(this.resolveModelName(),this.config.runtime.contextWindow);return new d({id:t,model:e.model,sources:e.sources,maxSteps:this.maxSteps,compaction:this.config.runtime.compaction,turnTimeoutMs:this.config.runtime.turnTimeoutMs,debugEvents:this.debugEvents,...this.providerOptions?{providerOptions:this.providerOptions}:{},...void 0!==o?{contextWindow:o}:{},...this.policy?{policy:this.policy}:{},initialMessages:s,...i?{onPersist:e=>i.appendMessages(t,e),onReplace:e=>i.replaceMessages(t,e)}:{}})}ensureReady(){return this.ready||(this.ready=this.bootstrap()),this.ready}async bootstrap(){const e=this.explicitModel??this.resolveModel();if(this.explicitSources)return{model:e,sources:this.explicitSources};const t=this.explicitAgents??new i(this.config.agents.dataDir).list(),s=[];for(const i of t)try{const t=o(i),r=c(this.config,i.skill.name);await this.ensureAgentReady(i,r);const n=await this.clientManager.connect(i.skill.name,t,i.installPath,{samplingModel:e,...r?{env:r}:{}}),a=(await n.listTools()).tools,d=l(a).map((e,t)=>({tool:e,annotations:m(a[t])}));s.push({agentName:i.skill.name,client:n,tools:d})}catch(e){this.onAgentBootstrapIssue?.({agentName:i.skill.name,message:e instanceof Error?e.message:String(e)})}return{model:e,sources:s}}resolveModel(){const e=this.resolveProviderName(),t=this.resolveModelName(),i=this.config.llm.providers[e];if(!i)throw new Error(`LLM provider "${e}" 未配置`);return s(e,t,i.apiKey,i.baseUrl)}resolveProviderName(){return this.config.runtime.provider??this.config.llm.defaultProvider}resolveModelName(){return this.config.runtime.model??this.config.llm.defaultModel}async dispose(){await this.clientManager.disconnectAll()}}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
export { ConversationEngine } from "./engine/conversation-engine.ts";
|
|
2
2
|
export type { AgentBootstrapIssue, ConversationEngineOptions, CreateSessionInput, } from "./engine/conversation-engine.ts";
|
|
3
3
|
export { AgentSession } from "./engine/agent-session.ts";
|
|
4
|
-
export type { AgentSessionOptions } from "./engine/agent-session.ts";
|
|
4
|
+
export type { AgentSessionOptions, SessionCompactionSettings } from "./engine/agent-session.ts";
|
|
5
|
+
export { resolveContextWindow, lookupContextWindow } from "./engine/context-window.ts";
|
|
6
|
+
export { compactMessages, findTurnBoundaries, SUMMARY_PREFIX, SUMMARY_ACK, } from "./engine/compactor.ts";
|
|
7
|
+
export type { CompactionInput, CompactionResult } from "./engine/compactor.ts";
|
|
5
8
|
export { buildAgentToolset } from "./tool-bridge/build-tools.ts";
|
|
6
9
|
export type { AgentToolSource, SourceTool, ApprovalRequest, ToolBridgeContext, BuiltToolset, } from "./tool-bridge/build-tools.ts";
|
|
7
10
|
export { ToolRegistry } from "./tool-bridge/naming.ts";
|
|
@@ -20,4 +23,4 @@ export { RuntimeServer } from "./server/runtime-server.ts";
|
|
|
20
23
|
export { createStdioConnection } from "./server/transport/stdio.ts";
|
|
21
24
|
export { RpcMethod, EVENT_NOTIFICATION, isRequest } from "./server/protocol.ts";
|
|
22
25
|
export type { JsonRpcConnection, JsonRpcMessage, JsonRpcRequest, JsonRpcNotification, JsonRpcId, } from "./server/protocol.ts";
|
|
23
|
-
export type { SessionEvent, SessionEventStage, SessionTokenUsage } from "./types/events.ts";
|
|
26
|
+
export type { SessionEvent, SessionEventStage, SessionTokenUsage, ContextCompactionReason, ContextCompactionStrategy, } from "./types/events.ts";
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{ConversationEngine}from"./engine/conversation-engine.js";export{AgentSession}from"./engine/agent-session.js";export{buildAgentToolset}from"./tool-bridge/build-tools.js";export{ToolRegistry}from"./tool-bridge/naming.js";export{normalizeToolResult}from"./tool-bridge/normalize-result.js";export{ThreadStore,defaultThreadsDir,expandTilde}from"./store/thread-store.js";export{DefaultToolPolicy}from"./policy/default-policy.js";export{ConfigurableToolPolicy}from"./policy/configurable-policy.js";export{ApprovalGate}from"./approval/approval-gate.js";export{RuntimeServer}from"./server/runtime-server.js";export{createStdioConnection}from"./server/transport/stdio.js";export{RpcMethod,EVENT_NOTIFICATION,isRequest}from"./server/protocol.js";
|
|
1
|
+
export{ConversationEngine}from"./engine/conversation-engine.js";export{AgentSession}from"./engine/agent-session.js";export{resolveContextWindow,lookupContextWindow}from"./engine/context-window.js";export{compactMessages,findTurnBoundaries,SUMMARY_PREFIX,SUMMARY_ACK}from"./engine/compactor.js";export{buildAgentToolset}from"./tool-bridge/build-tools.js";export{ToolRegistry}from"./tool-bridge/naming.js";export{normalizeToolResult}from"./tool-bridge/normalize-result.js";export{ThreadStore,defaultThreadsDir,expandTilde}from"./store/thread-store.js";export{DefaultToolPolicy}from"./policy/default-policy.js";export{ConfigurableToolPolicy}from"./policy/configurable-policy.js";export{ApprovalGate}from"./approval/approval-gate.js";export{RuntimeServer}from"./server/runtime-server.js";export{createStdioConnection}from"./server/transport/stdio.js";export{RpcMethod,EVENT_NOTIFICATION,isRequest}from"./server/protocol.js";
|
|
@@ -39,6 +39,7 @@ export declare const RpcMethod: {
|
|
|
39
39
|
readonly Reject: "session.reject";
|
|
40
40
|
readonly Abort: "session.abort";
|
|
41
41
|
readonly Messages: "session.messages";
|
|
42
|
+
readonly Compact: "session.compact";
|
|
42
43
|
};
|
|
43
44
|
export declare const EVENT_NOTIFICATION = "session.event";
|
|
44
45
|
export declare const createParamsSchema: z.ZodObject<{
|
|
@@ -102,4 +103,11 @@ export declare const messagesParamsSchema: z.ZodObject<{
|
|
|
102
103
|
}, {
|
|
103
104
|
sessionId: string;
|
|
104
105
|
}>;
|
|
106
|
+
export declare const compactParamsSchema: z.ZodObject<{
|
|
107
|
+
sessionId: z.ZodString;
|
|
108
|
+
}, "strip", z.ZodTypeAny, {
|
|
109
|
+
sessionId: string;
|
|
110
|
+
}, {
|
|
111
|
+
sessionId: string;
|
|
112
|
+
}>;
|
|
105
113
|
export declare function isRequest(message: JsonRpcMessage): message is JsonRpcRequest;
|
package/dist/server/protocol.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{z as s}from"zod";export const RpcMethod={Create:"session.create",Resume:"session.resume",Send:"session.send",Approve:"session.approve",Reject:"session.reject",Abort:"session.abort",Messages:"session.messages"};export const EVENT_NOTIFICATION="session.event";export const createParamsSchema=s.object({title:s.string().optional()});export const resumeParamsSchema=s.object({threadId:s.string()});export const sendParamsSchema=s.object({sessionId:s.string(),input:s.string()});export const approveParamsSchema=s.object({sessionId:s.string(),approvalId:s.string()});export const rejectParamsSchema=s.object({sessionId:s.string(),approvalId:s.string(),reason:s.string().optional()});export const abortParamsSchema=s.object({sessionId:s.string()});export const messagesParamsSchema=s.object({sessionId:s.string()});export function isRequest(s){return"method"in s&&"id"in s}
|
|
1
|
+
import{z as s}from"zod";export const RpcMethod={Create:"session.create",Resume:"session.resume",Send:"session.send",Approve:"session.approve",Reject:"session.reject",Abort:"session.abort",Messages:"session.messages",Compact:"session.compact"};export const EVENT_NOTIFICATION="session.event";export const createParamsSchema=s.object({title:s.string().optional()});export const resumeParamsSchema=s.object({threadId:s.string()});export const sendParamsSchema=s.object({sessionId:s.string(),input:s.string()});export const approveParamsSchema=s.object({sessionId:s.string(),approvalId:s.string()});export const rejectParamsSchema=s.object({sessionId:s.string(),approvalId:s.string(),reason:s.string().optional()});export const abortParamsSchema=s.object({sessionId:s.string()});export const messagesParamsSchema=s.object({sessionId:s.string()});export const compactParamsSchema=s.object({sessionId:s.string()});export function isRequest(s){return"method"in s&&"id"in s}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{EVENT_NOTIFICATION as s,RpcMethod as e,abortParamsSchema as n,approveParamsSchema as t,createParamsSchema as r,isRequest as
|
|
1
|
+
import{EVENT_NOTIFICATION as s,RpcMethod as e,abortParamsSchema as n,approveParamsSchema as t,compactParamsSchema as o,createParamsSchema as r,isRequest as i,messagesParamsSchema as a,rejectParamsSchema as c,resumeParamsSchema as d,sendParamsSchema as p}from"./protocol.js";function h(s){return s instanceof Error?s.message:String(s)}export class RuntimeServer{engine;connection;sessions=new Map;constructor(s,e){this.engine=s,this.connection=e,this.connection.onMessage(s=>this.handleMessage(s))}abortAll(){for(const s of this.sessions.values())s.abort();this.sessions.clear()}handleMessage(s){i(s)&&this.dispatch(s).then(e=>{this.connection.send({jsonrpc:"2.0",id:s.id,result:e})}).catch(e=>{this.connection.send({jsonrpc:"2.0",id:s.id,error:{code:-32e3,message:h(e)}})})}requireSession(s){const e=this.sessions.get(s);if(!e)throw new Error(`Session "${s}" 不存在`);return e}async dispatch(i){switch(i.method){case e.Create:{const s=r.parse(i.params),e=await this.engine.createSession(s.title?{title:s.title}:{});return this.sessions.set(e.id,e),{sessionId:e.id}}case e.Resume:{const s=d.parse(i.params),e=await this.engine.resumeSession(s.threadId);return this.sessions.set(e.id,e),{sessionId:e.id}}case e.Send:{const e=p.parse(i.params),n=this.requireSession(e.sessionId);for await(const t of n.send(e.input))this.connection.send({jsonrpc:"2.0",method:s,params:{sessionId:e.sessionId,event:t}});return{status:"completed"}}case e.Approve:{const s=t.parse(i.params);return{resolved:this.requireSession(s.sessionId).approve(s.approvalId)}}case e.Reject:{const s=c.parse(i.params);return{resolved:this.requireSession(s.sessionId).reject(s.approvalId,s.reason)}}case e.Abort:{const s=n.parse(i.params);return this.requireSession(s.sessionId).abort(),{ok:!0}}case e.Messages:{const s=a.parse(i.params);return{messages:this.requireSession(s.sessionId).getMessages()}}case e.Compact:{const e=o.parse(i.params),n=this.requireSession(e.sessionId);for await(const t of n.compact("manual"))this.connection.send({jsonrpc:"2.0",method:s,params:{sessionId:e.sessionId,event:t}});return{status:"completed"}}default:throw new Error(`Unknown method: ${i.method}`)}}}
|
|
@@ -25,6 +25,7 @@ export declare class ThreadStore {
|
|
|
25
25
|
updateTitle(threadId: string, title: string): void;
|
|
26
26
|
deleteThread(threadId: string): void;
|
|
27
27
|
appendMessages(threadId: string, messages: readonly ModelMessage[]): void;
|
|
28
|
+
replaceMessages(threadId: string, messages: readonly ModelMessage[]): void;
|
|
28
29
|
getMessages(threadId: string): ModelMessage[];
|
|
29
30
|
close(): void;
|
|
30
31
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as e,mkdirSync as t}from"node:fs";import{homedir as r}from"node:os";import{resolve as d}from"node:path";import{randomUUID as a}from"node:crypto";import{DatabaseSync as s}from"node:sqlite";import{modelMessageSchema as i}from"ai";const n=1;export function expandTilde(e){return e.startsWith("~/")?d(r(),e.slice(2)):e}export function defaultThreadsDir(){return d(r(),".roll-agent","threads")}function E(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at}}export class ThreadStore{db;constructor(r=defaultThreadsDir()){const a=expandTilde(r);e(a)||t(a,{recursive:!0}),this.db=new s(d(a,"threads.db")),this.init()}init(){this.db.exec("PRAGMA foreign_keys = ON;"),this.db.exec(`CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n PRAGMA user_version = ${String(1)};`)}createThread(e={}){const t=e.id??a(),r=(new Date).toISOString();return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,r,r),t}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?E(t):void 0}listThreads(){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(E)}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendMessages(e,t){if(0===t.length)return;if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);let r=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e).maxIdx+1;const d=(new Date).toISOString(),a=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.exec("BEGIN");try{for(const s of t)a.run(e,r,s.role,JSON.stringify(s),d),r+=1;this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(d,e),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>i.parse(JSON.parse(e.content_json)))}close(){this.db.close()}}
|
|
1
|
+
import{existsSync as e,mkdirSync as t}from"node:fs";import{homedir as r}from"node:os";import{resolve as d}from"node:path";import{randomUUID as a}from"node:crypto";import{DatabaseSync as s}from"node:sqlite";import{modelMessageSchema as i}from"ai";const n=1;export function expandTilde(e){return e.startsWith("~/")?d(r(),e.slice(2)):e}export function defaultThreadsDir(){return d(r(),".roll-agent","threads")}function E(e){return{id:e.id,title:e.title??void 0,model:e.model??void 0,createdAt:e.created_at,updatedAt:e.updated_at}}export class ThreadStore{db;constructor(r=defaultThreadsDir()){const a=expandTilde(r);e(a)||t(a,{recursive:!0}),this.db=new s(d(a,"threads.db")),this.init()}init(){this.db.exec("PRAGMA foreign_keys = ON;"),this.db.exec(`CREATE TABLE IF NOT EXISTS threads (\n id TEXT PRIMARY KEY,\n title TEXT,\n model TEXT,\n created_at TEXT NOT NULL,\n updated_at TEXT NOT NULL\n );\n CREATE TABLE IF NOT EXISTS messages (\n thread_id TEXT NOT NULL,\n idx INTEGER NOT NULL,\n role TEXT NOT NULL,\n content_json TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (thread_id, idx),\n FOREIGN KEY (thread_id) REFERENCES threads(id) ON DELETE CASCADE\n );\n PRAGMA user_version = ${String(1)};`)}createThread(e={}){const t=e.id??a(),r=(new Date).toISOString();return this.db.prepare("INSERT INTO threads (id, title, model, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(t,e.title??null,e.model??null,r,r),t}hasThread(e){return void 0!==this.db.prepare("SELECT 1 FROM threads WHERE id = ?").get(e)}getThread(e){const t=this.db.prepare("SELECT * FROM threads WHERE id = ?").get(e);return t?E(t):void 0}listThreads(){return this.db.prepare("SELECT * FROM threads ORDER BY updated_at DESC, rowid DESC").all().map(E)}countMessages(e){return this.db.prepare("SELECT COUNT(*) AS n FROM messages WHERE thread_id = ?").get(e).n}updateTitle(e,t){this.db.prepare("UPDATE threads SET title = ? WHERE id = ?").run(t,e)}deleteThread(e){this.db.prepare("DELETE FROM threads WHERE id = ?").run(e)}appendMessages(e,t){if(0===t.length)return;if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);let r=this.db.prepare("SELECT COALESCE(MAX(idx), -1) AS maxIdx FROM messages WHERE thread_id = ?").get(e).maxIdx+1;const d=(new Date).toISOString(),a=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.exec("BEGIN");try{for(const s of t)a.run(e,r,s.role,JSON.stringify(s),d),r+=1;this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(d,e),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}replaceMessages(e,t){if(!this.hasThread(e))throw new Error(`Thread "${e}" 不存在`);const r=(new Date).toISOString(),d=this.db.prepare("INSERT INTO messages (thread_id, idx, role, content_json, created_at) VALUES (?, ?, ?, ?, ?)");this.db.exec("BEGIN");try{this.db.prepare("DELETE FROM messages WHERE thread_id = ?").run(e),t.forEach((t,a)=>{d.run(e,a,t.role,JSON.stringify(t),r)}),this.db.prepare("UPDATE threads SET updated_at = ? WHERE id = ?").run(r,e),this.db.exec("COMMIT")}catch(e){throw this.db.exec("ROLLBACK"),e}}getMessages(e){return this.db.prepare("SELECT content_json FROM messages WHERE thread_id = ? ORDER BY idx ASC").all(e).map(e=>i.parse(JSON.parse(e.content_json)))}close(){this.db.close()}}
|
package/dist/types/events.d.ts
CHANGED
|
@@ -2,9 +2,19 @@ export interface SessionTokenUsage {
|
|
|
2
2
|
readonly inputTokens?: number;
|
|
3
3
|
readonly outputTokens?: number;
|
|
4
4
|
readonly totalTokens?: number;
|
|
5
|
+
readonly cachedInputTokens?: number;
|
|
6
|
+
readonly reasoningTokens?: number;
|
|
5
7
|
}
|
|
6
8
|
export type SessionEventStage = "bootstrap" | "plan" | "execute";
|
|
9
|
+
export type SessionDebugStage = "turn" | "compaction" | "model" | "persist";
|
|
10
|
+
export type SessionDebugData = Readonly<Record<string, string | number | boolean>>;
|
|
7
11
|
export type SessionEvent = {
|
|
12
|
+
readonly type: "debug";
|
|
13
|
+
readonly stage: SessionDebugStage;
|
|
14
|
+
readonly message: string;
|
|
15
|
+
readonly elapsedMs?: number;
|
|
16
|
+
readonly data?: SessionDebugData;
|
|
17
|
+
} | {
|
|
8
18
|
readonly type: "message-start";
|
|
9
19
|
readonly messageId: string;
|
|
10
20
|
} | {
|
|
@@ -38,8 +48,24 @@ export type SessionEvent = {
|
|
|
38
48
|
readonly type: "message-finish";
|
|
39
49
|
readonly text: string;
|
|
40
50
|
readonly totalUsage?: SessionTokenUsage;
|
|
51
|
+
readonly sessionUsage?: SessionTokenUsage;
|
|
52
|
+
readonly contextInputTokens?: number;
|
|
53
|
+
readonly stoppedAtStepLimit?: boolean;
|
|
54
|
+
} | {
|
|
55
|
+
readonly type: "compaction-start";
|
|
56
|
+
readonly reason: ContextCompactionReason;
|
|
57
|
+
} | {
|
|
58
|
+
readonly type: "context-compacted";
|
|
59
|
+
readonly reason: ContextCompactionReason;
|
|
60
|
+
readonly strategy: ContextCompactionStrategy;
|
|
61
|
+
readonly removed: number;
|
|
62
|
+
readonly kept: number;
|
|
63
|
+
readonly truncatedTools?: number;
|
|
64
|
+
readonly beforeInputTokens?: number;
|
|
41
65
|
} | {
|
|
42
66
|
readonly type: "error";
|
|
43
67
|
readonly stage: SessionEventStage;
|
|
44
68
|
readonly message: string;
|
|
45
69
|
};
|
|
70
|
+
export type ContextCompactionReason = "auto" | "manual";
|
|
71
|
+
export type ContextCompactionStrategy = "summarize" | "truncate";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@roll-agent/runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/node": "^22.0.0",
|
|
36
|
-
"@roll-agent/core": "0.
|
|
36
|
+
"@roll-agent/core": "0.12.0"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && node ../../scripts/obfuscate.mjs",
|