@vinhnt-sdk/core 0.6.1 → 0.8.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/README.md +149 -87
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel/handoff.d.ts +136 -0
- package/dist/kernel/handoff.d.ts.map +1 -0
- package/dist/kernel/handoff.js +144 -0
- package/dist/kernel/handoff.js.map +1 -0
- package/dist/kernel/kernel-types.d.ts +112 -13
- package/dist/kernel/kernel-types.d.ts.map +1 -1
- package/dist/kernel/kernel-types.js.map +1 -1
- package/dist/kernel/kernel.d.ts +19 -0
- package/dist/kernel/kernel.d.ts.map +1 -1
- package/dist/kernel/kernel.js +94 -11
- package/dist/kernel/kernel.js.map +1 -1
- package/dist/kernel/run-context.d.ts +136 -22
- package/dist/kernel/run-context.d.ts.map +1 -1
- package/dist/kernel/run-context.js +182 -11
- package/dist/kernel/run-context.js.map +1 -1
- package/dist/kernel/run-loop.d.ts +41 -1
- package/dist/kernel/run-loop.d.ts.map +1 -1
- package/dist/kernel/run-loop.js +151 -6
- package/dist/kernel/run-loop.js.map +1 -1
- package/dist/tool/bridge.js +1 -1
- package/dist/tool/bridge.js.map +1 -1
- package/dist/tool/runtime.d.ts +18 -1
- package/dist/tool/runtime.d.ts.map +1 -1
- package/dist/tool/runtime.js +63 -4
- package/dist/tool/runtime.js.map +1 -1
- package/package.json +8 -8
|
@@ -1,13 +1,184 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
/**
|
|
2
|
+
* AgentRunContext — dependency injection container for a single agent run.
|
|
3
|
+
*
|
|
4
|
+
* Follows the OpenAI Agents SDK pattern: a typed context object that is
|
|
5
|
+
* created once per run and shared across all tools, hooks, and guardrails.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* // User creates a plain context object
|
|
10
|
+
* interface AppContext {
|
|
11
|
+
* userId: string;
|
|
12
|
+
* apiKey: string;
|
|
13
|
+
* db: Database;
|
|
14
|
+
* }
|
|
15
|
+
*
|
|
16
|
+
* // Tools receive AgentRunContext<AppContext> as 2nd parameter
|
|
17
|
+
* const fetchUser = tool({
|
|
18
|
+
* name: 'fetch_user',
|
|
19
|
+
* description: 'Fetch user by ID',
|
|
20
|
+
* parameters: z.object({ userId: z.string() }),
|
|
21
|
+
* execute: async (args, runContext) => {
|
|
22
|
+
* const user = await runContext.context.db.getUser(args.userId);
|
|
23
|
+
* return user;
|
|
24
|
+
* },
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Kernel creates AgentRunContext internally
|
|
28
|
+
* const kernel = new AgentKernel({ ... });
|
|
29
|
+
* const result = await kernel.run('Get user 123', {
|
|
30
|
+
* context: { userId: 'u-1', apiKey: 'sk-...', db: myDb },
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
/**
|
|
35
|
+
* AgentRunContext carries all state for a single agent run.
|
|
36
|
+
*
|
|
37
|
+
* - `context`: user's application state (generic, typed)
|
|
38
|
+
* - `usage`: cumulative token usage
|
|
39
|
+
* - `signal`: abort signal for cancellation
|
|
40
|
+
* - `approvals`: tool approval decisions
|
|
41
|
+
*
|
|
42
|
+
* The context is **mutable** — tools can read/write `context` and all
|
|
43
|
+
* tools in the same run see the mutations (shared reference).
|
|
44
|
+
*/
|
|
45
|
+
export class AgentRunContext {
|
|
46
|
+
/** User's application state. */
|
|
47
|
+
context;
|
|
48
|
+
/** Cumulative usage for this run. */
|
|
49
|
+
usage;
|
|
50
|
+
/** Abort signal — tools should check `signal.aborted` before long operations. */
|
|
51
|
+
signal;
|
|
52
|
+
/** Session ID for this run. */
|
|
53
|
+
sessionId;
|
|
54
|
+
/** Run ID. */
|
|
55
|
+
runId;
|
|
56
|
+
/** Agent ID. */
|
|
57
|
+
agentId;
|
|
58
|
+
/** Agent name. */
|
|
59
|
+
agentName;
|
|
60
|
+
/** Environment variables (for subprocess execution). */
|
|
61
|
+
env;
|
|
62
|
+
/**
|
|
63
|
+
* The active workspace root for this run.
|
|
64
|
+
* Tools should resolve file paths relative to this directory.
|
|
65
|
+
*/
|
|
66
|
+
workspaceRoot;
|
|
67
|
+
/** Tool approval decisions (toolName+callId → decision). */
|
|
68
|
+
approvals;
|
|
69
|
+
/** Compensation actions for saga rollback. */
|
|
70
|
+
compensations;
|
|
71
|
+
/** Extension data for plugins. */
|
|
72
|
+
extensionData;
|
|
73
|
+
/** Current tool input (set before tool execute, cleared after). */
|
|
74
|
+
toolInput;
|
|
75
|
+
constructor(context, options) {
|
|
76
|
+
this.context = context;
|
|
77
|
+
this.signal = options.signal;
|
|
78
|
+
this.sessionId = options.sessionId;
|
|
79
|
+
this.runId = options.runId;
|
|
80
|
+
this.agentId = options.agentId;
|
|
81
|
+
this.agentName = options.agentName;
|
|
82
|
+
this.env = options.env ?? {};
|
|
83
|
+
if (options.workspaceRoot !== undefined) {
|
|
84
|
+
this.workspaceRoot = options.workspaceRoot;
|
|
85
|
+
}
|
|
86
|
+
this.approvals = new Map();
|
|
87
|
+
this.compensations = [];
|
|
88
|
+
this.extensionData = {};
|
|
89
|
+
this.usage = {
|
|
90
|
+
promptTokens: 0,
|
|
91
|
+
completionTokens: 0,
|
|
92
|
+
totalTokens: 0,
|
|
93
|
+
modelCalls: 0,
|
|
94
|
+
toolCalls: 0,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Check if a tool call has been approved.
|
|
99
|
+
* Returns the approval record if found, undefined otherwise.
|
|
100
|
+
*/
|
|
101
|
+
getApproval(toolName, callId) {
|
|
102
|
+
return this.approvals.get(`${toolName}:${callId}`);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Record an approval decision for a tool call.
|
|
106
|
+
*/
|
|
107
|
+
setApproval(toolName, callId, decision) {
|
|
108
|
+
this.approvals.set(`${toolName}:${callId}`, {
|
|
109
|
+
toolName,
|
|
110
|
+
callId,
|
|
111
|
+
decision,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Register a compensation action for saga rollback.
|
|
117
|
+
* If the run fails, compensations are executed in reverse order.
|
|
118
|
+
*/
|
|
119
|
+
setCompensation(action) {
|
|
120
|
+
this.compensations.push(action);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Execute all compensation actions in reverse order.
|
|
124
|
+
* Called automatically on run failure if `saga: true` is set.
|
|
125
|
+
*/
|
|
126
|
+
async runCompensations() {
|
|
127
|
+
const errors = [];
|
|
128
|
+
for (const comp of this.compensations.reverse()) {
|
|
129
|
+
try {
|
|
130
|
+
await comp();
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
errors.push(err instanceof Error ? err : new Error(String(err)));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (errors.length > 0) {
|
|
137
|
+
throw new Error(`Compensation errors:\n${errors.map((e) => e.message).join("\n")}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Fork context for a sub-agent run (scoped toolInput, shared approvals).
|
|
142
|
+
*/
|
|
143
|
+
forkForSubagent(options) {
|
|
144
|
+
const childOptions = {
|
|
145
|
+
signal: this.signal,
|
|
146
|
+
sessionId: this.sessionId,
|
|
147
|
+
runId: this.runId,
|
|
148
|
+
agentId: options.agentId,
|
|
149
|
+
agentName: options.agentName,
|
|
150
|
+
env: this.env,
|
|
151
|
+
};
|
|
152
|
+
if (this.workspaceRoot !== undefined) {
|
|
153
|
+
childOptions.workspaceRoot = this.workspaceRoot;
|
|
154
|
+
}
|
|
155
|
+
const child = new AgentRunContext(this.context, childOptions);
|
|
156
|
+
// Share approvals and extension data by reference
|
|
157
|
+
child.approvals.clear();
|
|
158
|
+
this.approvals.forEach((v, k) => child.approvals.set(k, v));
|
|
159
|
+
Object.assign(child.extensionData, this.extensionData);
|
|
160
|
+
return child;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Create a ToolContext from this RunContext (adapter for existing tool code).
|
|
164
|
+
*/
|
|
165
|
+
toToolContext() {
|
|
166
|
+
return {
|
|
167
|
+
sessionId: this.sessionId,
|
|
168
|
+
runId: this.runId,
|
|
169
|
+
agentId: this.agentId,
|
|
170
|
+
agentName: this.agentName,
|
|
171
|
+
signal: this.signal,
|
|
172
|
+
env: this.env,
|
|
173
|
+
...(this.workspaceRoot !== undefined ? { workspaceRoot: this.workspaceRoot } : {}),
|
|
174
|
+
extensionData: this.extensionData,
|
|
175
|
+
ask: async (input) => {
|
|
176
|
+
// Delegate to approval handler (will be set by kernel)
|
|
177
|
+
return "reject";
|
|
178
|
+
},
|
|
179
|
+
metadata: () => { },
|
|
180
|
+
setCompensation: (action) => this.setCompensation(action),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
12
183
|
}
|
|
13
184
|
//# sourceMappingURL=run-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-context.js","sourceRoot":"","sources":["../../src/kernel/run-context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run-context.js","sourceRoot":"","sources":["../../src/kernel/run-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AA8BH;;;;;;;;;;GAUG;AACH,MAAM,OAAO,eAAe;IAC1B,gCAAgC;IAChC,OAAO,CAAW;IAElB,qCAAqC;IACrC,KAAK,CAAW;IAEhB,iFAAiF;IACxE,MAAM,CAAc;IAE7B,+BAA+B;IACtB,SAAS,CAAS;IAE3B,cAAc;IACL,KAAK,CAAS;IAEvB,gBAAgB;IACP,OAAO,CAAS;IAEzB,kBAAkB;IACT,SAAS,CAAS;IAE3B,wDAAwD;IAC/C,GAAG,CAAyB;IAErC;;;OAGG;IACM,aAAa,CAAU;IAEhC,4DAA4D;IACnD,SAAS,CAA8B;IAEhD,8CAA8C;IACrC,aAAa,CAA6B;IAEnD,kCAAkC;IACzB,aAAa,CAA0B;IAEhD,mEAAmE;IACnE,SAAS,CAAW;IAEpB,YACE,OAAiB,EACjB,OAQC;QAED,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;QAC/B,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;QACnC,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC;QAC7B,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACxC,IAAI,CAAC,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;QAC7C,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,IAAI,GAAG,EAAE,CAAC;QAC3B,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,aAAa,GAAG,EAAE,CAAC;QACxB,IAAI,CAAC,KAAK,GAAG;YACX,YAAY,EAAE,CAAC;YACf,gBAAgB,EAAE,CAAC;YACnB,WAAW,EAAE,CAAC;YACd,UAAU,EAAE,CAAC;YACb,SAAS,EAAE,CAAC;SACb,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,WAAW,CAAC,QAAgB,EAAE,MAAc;QAC1C,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,MAAM,EAAE,CAAC,CAAC;IACrD,CAAC;IAED;;OAEG;IACH,WAAW,CAAC,QAAgB,EAAE,MAAc,EAAE,QAAsC;QAClF,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,QAAQ,IAAI,MAAM,EAAE,EAAE;YAC1C,QAAQ;YACR,MAAM;YACN,QAAQ;YACR,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,MAA2B;QACzC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,gBAAgB;QACpB,MAAM,MAAM,GAAY,EAAE,CAAC;QAC3B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,EAAE,EAAE,CAAC;YAChD,IAAI,CAAC;gBACH,MAAM,IAAI,EAAE,CAAC;YACf,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CACb,yBAAyB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACnE,CAAC;QACJ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,eAAe,CAAC,OAA+C;QAC7D,MAAM,YAAY,GAQd;YACF,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC;QACF,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACrC,YAAY,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;QAClD,CAAC;QACD,MAAM,KAAK,GAAG,IAAI,eAAe,CAAW,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QACxE,kDAAkD;QAClD,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO;YACL,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAClF,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;gBACnB,uDAAuD;gBACvD,OAAO,QAA2B,CAAC;YACrC,CAAC;YACD,QAAQ,EAAE,GAAG,EAAE,GAAE,CAAC;YAClB,eAAe,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;SAC1D,CAAC;IACJ,CAAC;CACF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RequestContext, RunId, AgentConfig } from "@vinhnt-sdk/schema";
|
|
2
|
-
import type { ModelProvider } from "../model.js";
|
|
2
|
+
import type { ChatMessage, ModelProvider } from "../model.js";
|
|
3
3
|
import type { ContextRegistry } from "../system-context/types.js";
|
|
4
4
|
import type { ConversationCompactor } from "@vinhnt-sdk/session";
|
|
5
5
|
import type { RunEventStore, SessionStore } from "@vinhnt-sdk/session";
|
|
@@ -12,6 +12,8 @@ import type { ToolSaga } from "@vinhnt-sdk/tools";
|
|
|
12
12
|
import type { CircuitBreaker } from "@vinhnt-sdk/step-executor";
|
|
13
13
|
import type { RunStateMachine } from "@vinhnt-sdk/step-executor";
|
|
14
14
|
import type { TerminationPolicy } from "@vinhnt-sdk/step-executor";
|
|
15
|
+
import type { Guardrail } from "@vinhnt-sdk/guardrails";
|
|
16
|
+
import type { z } from "zod";
|
|
15
17
|
export interface RunLoopDeps {
|
|
16
18
|
readonly modelCaller: ModelCaller;
|
|
17
19
|
readonly permissionGate: PermissionGate;
|
|
@@ -33,6 +35,14 @@ export interface RunLoopDeps {
|
|
|
33
35
|
readonly compactionThreshold?: number;
|
|
34
36
|
readonly currentAgent?: AgentConfig;
|
|
35
37
|
readonly termination?: TerminationPolicy;
|
|
38
|
+
/** Input guardrails — run before model calls. */
|
|
39
|
+
readonly inputGuardrails?: readonly Guardrail[];
|
|
40
|
+
/** Output guardrails — run after model responses. */
|
|
41
|
+
readonly outputGuardrails?: readonly Guardrail[];
|
|
42
|
+
/** Structured output type — 'text' or Zod schema. */
|
|
43
|
+
readonly outputType?: 'text' | z.ZodTypeAny;
|
|
44
|
+
/** Per-run workspace root override. Tools operate within this directory. */
|
|
45
|
+
readonly workspaceRoot?: string;
|
|
36
46
|
/** Optional judge model for `llm-judge` stop conditions (defaults to the active run model). */
|
|
37
47
|
readonly judgeModel?: ModelProvider;
|
|
38
48
|
/** Await once before the loop starts, e.g. re-queuing persisted pending inputs (RV-21). */
|
|
@@ -80,6 +90,27 @@ export interface RunLoopInput {
|
|
|
80
90
|
emitFail: (runId: RunId, ctx: RequestContext, reason: string, steps: number, sessionId?: string, totalInputTokens?: number, totalOutputTokens?: number, durationMs?: number, cancelled?: boolean) => Promise<void>;
|
|
81
91
|
/** If true, resuming from durable storage — skip run.started event and user prompt injection. */
|
|
82
92
|
resume?: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Optional callback invoked before each step. Allows dynamic model/tool selection.
|
|
95
|
+
*
|
|
96
|
+
* Inspired by Vercel AI SDK's `prepareStep` pattern.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* prepareStep: async ({ step, model, messages }) => {
|
|
101
|
+
* // Use a cheaper model for early steps
|
|
102
|
+
* if (step < 3) return { model: fastModel };
|
|
103
|
+
* return {}; // use defaults
|
|
104
|
+
* }
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
prepareStep?: (params: {
|
|
108
|
+
step: number;
|
|
109
|
+
model: ModelProvider;
|
|
110
|
+
messages: readonly ChatMessage[];
|
|
111
|
+
}) => Promise<{
|
|
112
|
+
model?: ModelProvider;
|
|
113
|
+
} | void>;
|
|
83
114
|
}
|
|
84
115
|
export type RunLoopStatus = "succeeded" | "failed" | "cancelled";
|
|
85
116
|
export interface RunLoopResult {
|
|
@@ -88,6 +119,15 @@ export interface RunLoopResult {
|
|
|
88
119
|
readonly totalInputTokens?: number;
|
|
89
120
|
readonly totalOutputTokens?: number;
|
|
90
121
|
readonly durationMs?: number;
|
|
122
|
+
/** Validated structured output (when outputType is Zod schema). */
|
|
123
|
+
readonly structuredOutput?: unknown;
|
|
124
|
+
/** If set, a handoff was detected — kernel should transfer control. */
|
|
125
|
+
readonly handoff?: {
|
|
126
|
+
readonly targetAgentId: string;
|
|
127
|
+
readonly reason: string;
|
|
128
|
+
readonly summary?: string | undefined;
|
|
129
|
+
readonly context?: Record<string, unknown> | undefined;
|
|
130
|
+
};
|
|
91
131
|
}
|
|
92
132
|
export declare function runLoop(deps: RunLoopDeps, input: RunLoopInput): Promise<RunLoopResult>;
|
|
93
133
|
//# sourceMappingURL=run-loop.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-loop.d.ts","sourceRoot":"","sources":["../../src/kernel/run-loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,WAAW,EAAe,MAAM,oBAAoB,CAAC;AAE1F,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"run-loop.d.ts","sourceRoot":"","sources":["../../src/kernel/run-loop.ts"],"names":[],"mappings":"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"}
|
package/dist/kernel/run-loop.js
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import { getTextContent, COMPACTION_SUMMARY_PREFIX } from "@vinhnt-sdk/schema";
|
|
2
2
|
import { KernelError } from "@vinhnt-sdk/step-executor";
|
|
3
3
|
import { evaluateStopConditions, buildJudgeMessages, parseJudgeVerdict } from "@vinhnt-sdk/step-executor";
|
|
4
|
+
import { runGuardrails } from "@vinhnt-sdk/guardrails";
|
|
5
|
+
import { zodSchemaToNestedJsonSchema } from "@vinhnt-sdk/tools";
|
|
6
|
+
/**
|
|
7
|
+
* Convert a Zod schema to ResponseFormat for structured output.
|
|
8
|
+
* Uses the existing zodSchemaToNestedJsonSchema utility from @vinhnt-sdk/tools.
|
|
9
|
+
*/
|
|
10
|
+
function zodToResponseFormat(schema, name) {
|
|
11
|
+
const jsonSchema = zodSchemaToNestedJsonSchema(schema);
|
|
12
|
+
if (jsonSchema) {
|
|
13
|
+
return {
|
|
14
|
+
type: "json_schema",
|
|
15
|
+
jsonSchema: { name, schema: jsonSchema, strict: true },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
// Fallback: basic json_object format
|
|
19
|
+
return { type: "json_object" };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validate structured output against a Zod schema.
|
|
23
|
+
* Returns parsed output or throws on validation failure.
|
|
24
|
+
*/
|
|
25
|
+
function validateStructuredOutput(schema, output) {
|
|
26
|
+
const parsed = JSON.parse(output);
|
|
27
|
+
return schema.parse(parsed);
|
|
28
|
+
}
|
|
4
29
|
/** Index of the head `system` message — the first one. All system instructions
|
|
5
30
|
* (identity, agent systemPrompt, context baseline/reconciled updates) must live
|
|
6
31
|
* in that single message so providers never see mid-conversation `system`
|
|
@@ -140,6 +165,27 @@ async function processStep(deps, input) {
|
|
|
140
165
|
throw new KernelError("max_tokens_exceeded", `Agent max tokens exceeded (${input.totalInputTokens + input.totalOutputTokens})`);
|
|
141
166
|
}
|
|
142
167
|
let messages = input.messages;
|
|
168
|
+
// Run input guardrails before model call
|
|
169
|
+
if (deps.inputGuardrails && deps.inputGuardrails.length > 0) {
|
|
170
|
+
const lastUserMsg = [...messages].reverse().find((m) => m.role === "user");
|
|
171
|
+
const inputContent = lastUserMsg?.content ?? "";
|
|
172
|
+
const guardrailResult = await runGuardrails([...deps.inputGuardrails], {
|
|
173
|
+
direction: "input",
|
|
174
|
+
content: inputContent,
|
|
175
|
+
metadata: { runId, step: input.step, agentId: deps.currentAgent?.id },
|
|
176
|
+
});
|
|
177
|
+
if (!guardrailResult.passed) {
|
|
178
|
+
// Input guardrail denied — halt the run
|
|
179
|
+
throw new KernelError("guardrail_denied", `Input guardrail denied: ${guardrailResult.reason ?? "unknown reason"}`);
|
|
180
|
+
}
|
|
181
|
+
// If guardrail modified content, update the message
|
|
182
|
+
if (guardrailResult.modifiedContent !== undefined && lastUserMsg) {
|
|
183
|
+
const modifiedText = typeof guardrailResult.modifiedContent === "string"
|
|
184
|
+
? guardrailResult.modifiedContent
|
|
185
|
+
: JSON.stringify(guardrailResult.modifiedContent);
|
|
186
|
+
messages = messages.map((m) => m === lastUserMsg ? { ...m, content: modifiedText } : m);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
143
189
|
if (deps.thinkingBudget > 0) {
|
|
144
190
|
await deps.modelCaller.doThinkingStep(messages, input.step, runId, ctx, stepTimeoutController.signal);
|
|
145
191
|
}
|
|
@@ -194,10 +240,42 @@ async function processStep(deps, input) {
|
|
|
194
240
|
else if (runModel.countTokens) {
|
|
195
241
|
input.totalOutputTokens += runModel.countTokens(response.content);
|
|
196
242
|
}
|
|
243
|
+
// Run output guardrails after model response
|
|
244
|
+
let finalContent = response.content;
|
|
245
|
+
if (deps.outputGuardrails && deps.outputGuardrails.length > 0) {
|
|
246
|
+
const guardrailResult = await runGuardrails([...deps.outputGuardrails], {
|
|
247
|
+
direction: "output",
|
|
248
|
+
content: response.content,
|
|
249
|
+
metadata: { runId, step: input.step, agentId: deps.currentAgent?.id, toolCalls: response.toolCalls?.map((tc) => tc.name) },
|
|
250
|
+
});
|
|
251
|
+
if (!guardrailResult.passed) {
|
|
252
|
+
// Output guardrail denied — halt the run
|
|
253
|
+
throw new KernelError("guardrail_denied", `Output guardrail denied: ${guardrailResult.reason ?? "unknown reason"}`);
|
|
254
|
+
}
|
|
255
|
+
// If guardrail modified content, use the modified version
|
|
256
|
+
if (guardrailResult.modifiedContent !== undefined) {
|
|
257
|
+
finalContent = typeof guardrailResult.modifiedContent === "string"
|
|
258
|
+
? guardrailResult.modifiedContent
|
|
259
|
+
: JSON.stringify(guardrailResult.modifiedContent);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Validate structured output if outputType is Zod schema
|
|
263
|
+
let validatedOutput = undefined;
|
|
264
|
+
if (deps.outputType && deps.outputType !== 'text') {
|
|
265
|
+
try {
|
|
266
|
+
validatedOutput = validateStructuredOutput(deps.outputType, finalContent);
|
|
267
|
+
// Use the validated (parsed) output as the final content
|
|
268
|
+
finalContent = JSON.stringify(validatedOutput);
|
|
269
|
+
}
|
|
270
|
+
catch (err) {
|
|
271
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
272
|
+
throw new KernelError("validation_error", `Structured output validation failed: ${errMsg}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
197
275
|
const toolCalls = response.toolCalls ?? [];
|
|
198
276
|
messages.push({
|
|
199
277
|
role: "assistant",
|
|
200
|
-
content:
|
|
278
|
+
content: finalContent,
|
|
201
279
|
...(toolCalls.length > 0 ? {
|
|
202
280
|
toolCalls: toolCalls.map((tc) => ({
|
|
203
281
|
id: tc.id,
|
|
@@ -209,7 +287,7 @@ async function processStep(deps, input) {
|
|
|
209
287
|
const asstTokens = { input: input.totalInputTokens, output: input.totalOutputTokens };
|
|
210
288
|
const asstModel = runModel.model;
|
|
211
289
|
const msgCost = deps.modelCaller.calculateCost(asstTokens.input, asstTokens.output, runModel);
|
|
212
|
-
await deps.addSessionMessage(sessionId, "assistant",
|
|
290
|
+
await deps.addSessionMessage(sessionId, "assistant", finalContent, {
|
|
213
291
|
tokens: asstTokens,
|
|
214
292
|
...(asstModel ? { model: asstModel } : {}),
|
|
215
293
|
...(msgCost !== undefined ? { cost: msgCost } : {}),
|
|
@@ -225,9 +303,33 @@ async function processStep(deps, input) {
|
|
|
225
303
|
completed: true,
|
|
226
304
|
toolCallCount: 0,
|
|
227
305
|
lastStepToolOutcomes: [],
|
|
306
|
+
...(validatedOutput !== undefined ? { structuredOutput: validatedOutput } : {}),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
const { toolCallCount, selfCorrectTokens, toolResults, handoff } = await deps.stepExecutor.executeToolCalls(toolCalls.map((tc) => ({ toolId: tc.id, toolName: tc.name, args: tc.args })), messages, input.step, runId, ctx, stepTimeoutController, sessionId, runModel);
|
|
310
|
+
// Handle handoff — transfer control to target agent
|
|
311
|
+
if (handoff) {
|
|
312
|
+
const currentAgentId = deps.currentAgent?.id ?? "unknown";
|
|
313
|
+
// Note: handoff event is emitted in the main runLoop after processStep returns
|
|
314
|
+
// Signal to kernel to swap active agent (kernel handles the swap)
|
|
315
|
+
return {
|
|
316
|
+
messages,
|
|
317
|
+
step: input.step,
|
|
318
|
+
runId,
|
|
319
|
+
totalInputTokens: input.totalInputTokens,
|
|
320
|
+
totalOutputTokens: input.totalOutputTokens,
|
|
321
|
+
finalOutput: input.finalOutput,
|
|
322
|
+
completed: false,
|
|
323
|
+
toolCallCount,
|
|
324
|
+
lastStepToolOutcomes: toolResults,
|
|
325
|
+
handoff: {
|
|
326
|
+
targetAgentId: handoff.targetAgentId,
|
|
327
|
+
reason: handoff.reason,
|
|
328
|
+
summary: handoff.summary,
|
|
329
|
+
context: handoff.context,
|
|
330
|
+
},
|
|
228
331
|
};
|
|
229
332
|
}
|
|
230
|
-
const { toolCallCount, selfCorrectTokens, toolResults } = await deps.stepExecutor.executeToolCalls(toolCalls.map((tc) => ({ toolId: tc.id, toolName: tc.name, args: tc.args })), messages, input.step, runId, ctx, stepTimeoutController, sessionId, runModel);
|
|
231
333
|
if (stepTimeoutController.signal.aborted && !runAbort.signal.aborted) {
|
|
232
334
|
// Step timed out during tool execution — surface an error for any tool call
|
|
233
335
|
// that never got a response so the conversation stays coherent for the model.
|
|
@@ -283,6 +385,7 @@ async function processStep(deps, input) {
|
|
|
283
385
|
completed: false,
|
|
284
386
|
toolCallCount,
|
|
285
387
|
lastStepToolOutcomes: toolResults,
|
|
388
|
+
...(validatedOutput !== undefined ? { structuredOutput: validatedOutput } : {}),
|
|
286
389
|
};
|
|
287
390
|
}
|
|
288
391
|
finally {
|
|
@@ -301,6 +404,8 @@ export async function runLoop(deps, input) {
|
|
|
301
404
|
let messages = [];
|
|
302
405
|
let step = 0;
|
|
303
406
|
let finalOutput = "";
|
|
407
|
+
let structuredOutput = undefined;
|
|
408
|
+
let handoffResult;
|
|
304
409
|
let contextEpochActive = false;
|
|
305
410
|
// Real system head (identity + agent systemPrompt) sent as a proper `system`
|
|
306
411
|
// message at the head of the conversation instead of being flattened into the
|
|
@@ -434,6 +539,21 @@ export async function runLoop(deps, input) {
|
|
|
434
539
|
}
|
|
435
540
|
await emitEvt("step.started", { step });
|
|
436
541
|
await deps.pluginManager?.fireHook("onStepStarted", { step });
|
|
542
|
+
// Invoke prepareStep callback if provided (Vercel AI SDK pattern)
|
|
543
|
+
let stepModel = runModel;
|
|
544
|
+
if (input.prepareStep) {
|
|
545
|
+
try {
|
|
546
|
+
const prepareResult = await input.prepareStep({ step, model: runModel, messages });
|
|
547
|
+
if (prepareResult?.model) {
|
|
548
|
+
stepModel = prepareResult.model;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
catch (err) {
|
|
552
|
+
if (typeof console !== "undefined") {
|
|
553
|
+
console.warn("[run-loop] prepareStep failed, using default model:", err instanceof Error ? err.message : String(err));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
437
557
|
const compactResult = await maybeCompact(messages, {
|
|
438
558
|
...(runModel.countTokens ? { countTokens: runModel.countTokens } : {}),
|
|
439
559
|
...(runModel.contextLimit !== undefined ? { contextLimit: runModel.contextLimit } : {}),
|
|
@@ -455,7 +575,7 @@ export async function runLoop(deps, input) {
|
|
|
455
575
|
const stepResult = await processStep(deps, {
|
|
456
576
|
messages, step, runId, ctx, runAbort,
|
|
457
577
|
...(sessionId !== undefined ? { sessionId } : {}),
|
|
458
|
-
runModel,
|
|
578
|
+
runModel: stepModel,
|
|
459
579
|
...(runSessionState !== undefined ? { runSessionState } : {}),
|
|
460
580
|
totalInputTokens, totalOutputTokens, finalOutput,
|
|
461
581
|
...(onLastStep ? { disableTools: true } : {}),
|
|
@@ -464,6 +584,9 @@ export async function runLoop(deps, input) {
|
|
|
464
584
|
totalInputTokens = stepResult.totalInputTokens;
|
|
465
585
|
totalOutputTokens = stepResult.totalOutputTokens;
|
|
466
586
|
finalOutput = stepResult.finalOutput;
|
|
587
|
+
if (stepResult.structuredOutput !== undefined) {
|
|
588
|
+
structuredOutput = stepResult.structuredOutput;
|
|
589
|
+
}
|
|
467
590
|
if (stepResult.stepFailed) {
|
|
468
591
|
await emitEvt("step.failed", { step, reason: stepResult.stepFailed.reason, ...(stepResult.stepFailed.error ? { error: stepResult.stepFailed.error } : {}) });
|
|
469
592
|
await deps.pluginManager?.fireHook("onStepFailed", { step, reason: stepResult.stepFailed.reason, ...(stepResult.stepFailed.error ? { error: stepResult.stepFailed.error } : {}) });
|
|
@@ -472,6 +595,21 @@ export async function runLoop(deps, input) {
|
|
|
472
595
|
await emitEvt("step.completed", { step, toolCallCount: stepResult.toolCallCount });
|
|
473
596
|
await deps.pluginManager?.fireHook("onStepCompleted", { step, toolCallCount: stepResult.toolCallCount });
|
|
474
597
|
}
|
|
598
|
+
// Handle handoff — transfer control to target agent
|
|
599
|
+
if (stepResult.handoff) {
|
|
600
|
+
// Emit handoff event
|
|
601
|
+
const currentAgentId = deps.currentAgent?.id ?? "unknown";
|
|
602
|
+
await emitEvt("agent.handoff", {
|
|
603
|
+
fromAgentId: currentAgentId,
|
|
604
|
+
toAgentId: stepResult.handoff.targetAgentId,
|
|
605
|
+
reason: stepResult.handoff.reason,
|
|
606
|
+
summary: stepResult.handoff.summary,
|
|
607
|
+
});
|
|
608
|
+
// Break out of the loop — kernel will handle agent swap
|
|
609
|
+
finalOutput = `Handoff to agent "${stepResult.handoff.targetAgentId}": ${stepResult.handoff.reason}`;
|
|
610
|
+
handoffResult = stepResult.handoff;
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
475
613
|
if (stepResult.completed) {
|
|
476
614
|
finalOutput = stepResult.finalOutput;
|
|
477
615
|
await emitEvt("turn.end", { turn: step, reason: "completed" });
|
|
@@ -556,7 +694,14 @@ export async function runLoop(deps, input) {
|
|
|
556
694
|
runSessionState.isRunning = false;
|
|
557
695
|
}
|
|
558
696
|
await saveFinalSnapshot("succeeded");
|
|
559
|
-
return {
|
|
697
|
+
return {
|
|
698
|
+
totalSteps: step + 1,
|
|
699
|
+
status: "succeeded",
|
|
700
|
+
totalInputTokens,
|
|
701
|
+
totalOutputTokens,
|
|
702
|
+
durationMs: Date.now() - startTime,
|
|
703
|
+
...(structuredOutput !== undefined ? { structuredOutput } : {}),
|
|
704
|
+
};
|
|
560
705
|
}
|
|
561
706
|
}
|
|
562
707
|
}
|
|
@@ -592,7 +737,7 @@ export async function runLoop(deps, input) {
|
|
|
592
737
|
}
|
|
593
738
|
setState(runId, "completed");
|
|
594
739
|
await saveFinalSnapshot("succeeded");
|
|
595
|
-
return { totalSteps: step + 1, status: "succeeded", totalInputTokens, totalOutputTokens, durationMs };
|
|
740
|
+
return { totalSteps: step + 1, status: "succeeded", totalInputTokens, totalOutputTokens, durationMs, ...(handoffResult ? { handoff: handoffResult } : {}) };
|
|
596
741
|
}
|
|
597
742
|
if (runAbort.signal.aborted)
|
|
598
743
|
return cancelRun(step + 1);
|