@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.
@@ -1,13 +1,184 @@
1
- /** Create a fresh per-run context. */
2
- export function createRunContext(runId, agent, saga) {
3
- return {
4
- runId,
5
- agent,
6
- depth: 0,
7
- agentChain: new Set(),
8
- saga,
9
- cachedTools: null,
10
- cachedToolsAgentId: undefined,
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":"AAyBA,sCAAsC;AACtC,MAAM,UAAU,gBAAgB,CAC9B,KAAY,EACZ,KAA8B,EAC9B,IAAc;IAEd,OAAO;QACL,KAAK;QACL,KAAK;QACL,KAAK,EAAE,CAAC;QACR,UAAU,EAAE,IAAI,GAAG,EAAE;QACrB,IAAI;QACJ,WAAW,EAAE,IAAI;QACjB,kBAAkB,EAAE,SAAS;KAC9B,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"run-context.js","sourceRoot":"","sources":["../../src/kernel/run-context.ts"],"names":[],"mappings":"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"}
@@ -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":"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"}
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,EAAE,WAAW,EAAsB,aAAa,EAAiB,MAAM,aAAa,CAAC;AACjG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAClE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAA2B,MAAM,2BAA2B,CAAC;AACzF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAEjE,OAAO,KAAK,EAA0C,iBAAiB,EAAmB,MAAM,2BAA2B,CAAC;AAC5H,OAAO,KAAK,EAAE,SAAS,EAAmB,MAAM,wBAAwB,CAAC;AAEzE,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA6B7B,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,QAAQ,CAAC,YAAY,EAAE,eAAe,CAAC;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,CAAC;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,eAAe,CAAC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrE,QAAQ,CAAC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnJ,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,iBAAiB,CAAC;IACzC,iDAAiD;IACjD,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAChD,qDAAqD;IACrD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IACjD,qDAAqD;IACrD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,UAAU,CAAC;IAC5C,4EAA4E;IAC5E,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,+FAA+F;IAC/F,QAAQ,CAAC,UAAU,CAAC,EAAE,aAAa,CAAC;IACpC,2FAA2F;IAC3F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC5D,yFAAyF;IACzF,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACpF;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf;;6EAEyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC;IACb,GAAG,EAAE,cAAc,CAAC;IACpB,QAAQ,EAAE,eAAe,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjG,QAAQ,EAAE,aAAa,CAAC;IACxB,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9H,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,OAAO,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvK,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,oFAAoF;IACpF,aAAa,EAAE,CAAC,KAAK,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1Q,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,gBAAgB,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnN,iGAAiG;IACjG,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;;OAaG;IACH,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE;QACrB,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,aAAa,CAAC;QACrB,QAAQ,EAAE,SAAS,WAAW,EAAE,CAAC;KAClC,KAAK,OAAO,CAAC;QAAE,KAAK,CAAC,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACjD;AAED,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IACpC,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE;QACjB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;QAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;QACtC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;KACxD,CAAC;CACH;AAkfD,wBAAsB,OAAO,CAC3B,IAAI,EAAE,WAAW,EACjB,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,aAAa,CAAC,CA+bxB"}
@@ -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: response.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", response.content, {
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 { totalSteps: step + 1, status: "succeeded", totalInputTokens, totalOutputTokens, durationMs: Date.now() - startTime };
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);