@a5c-ai/agent-core 5.0.1-staging.ee42097a6d34 → 5.0.1-staging.ee474eb45

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 CHANGED
@@ -118,6 +118,7 @@ If you still need the PI-era controls above, use the PI wrapper exposed from `@a
118
118
  - delegation tools: `AskUserQuestion`, `task`, `skill`
119
119
  - code tools: `calc`, `ast_grep`, `ast_edit`, `render_mermaid`, `notebook`
120
120
  - background/discovery/web tools: `background_status`, `background_list`, `tool_search`, `tool_fetch`, `web_search`, `fetch_process`
121
+ - optional programmatic tool calling: `code_executor` when `programmaticToolCalling` is enabled
121
122
 
122
123
  `AgentCoreToolOptions` controls how those definitions are wired into a host runtime:
123
124
 
@@ -127,6 +128,18 @@ If you still need the PI-era controls above, use the PI wrapper exposed from `@a
127
128
  - `onToolUse`: observer callback fired after tool wrapping.
128
129
  - `onBackgroundComplete`, `maxBackgroundProcesses`, `backgroundRegistry`: background-process lifecycle hooks and limits.
129
130
  - `deferredToolRegistry`: enables `tool_search` and `tool_fetch`.
131
+ - `programmaticToolCalling`: opt-in Code Mode / Programmatic Tool Calling surface. When enabled, `code_executor` runs a bounded JavaScript async body with `tools.<name>(params)` and `callTool(name, params)` helpers for batching existing agent-core tools behind one model-level tool call.
132
+
133
+ Example:
134
+
135
+ ```ts
136
+ const tools = createAgentCoreToolDefinitions({
137
+ workspace: process.cwd(),
138
+ interactive: false,
139
+ deferredToolRegistry,
140
+ programmaticToolCalling: { maxToolCalls: 10, timeout: 60_000 },
141
+ });
142
+ ```
130
143
 
131
144
  ### Interactive and cancellation contract
132
145
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agenticTools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAexE,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,kBAAkB,GAAG,oBAAoB,EAAE,CAelG;AAED,wBAAgB,+BAA+B,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAOzF;AAED,eAAO,MAAM,4BAA4B,uCAAiC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agenticTools/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAC;AAmBxE,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,kBAAkB,GAAG,oBAAoB,EAAE,CAsBlG;AAED,wBAAgB,+BAA+B,CAAC,WAAW,EAAE,oBAAoB,EAAE,GAAG,IAAI,CAOzF;AAED,eAAO,MAAM,4BAA4B,uCAAiC,CAAC"}
@@ -14,9 +14,10 @@ const code_1 = require("./tools/code");
14
14
  const delegation_1 = require("./tools/delegation");
15
15
  const execution_1 = require("./tools/execution");
16
16
  const fileSystem_1 = require("./tools/fileSystem");
17
+ const programmaticToolCalling_1 = require("./tools/programmaticToolCalling");
17
18
  const toolDefinitionScopes = new WeakMap();
18
19
  function createAgentCoreToolDefinitions(options) {
19
- const tools = [
20
+ const baseTools = [
20
21
  ...(0, fileSystem_1.createFileSystemTools)(options),
21
22
  ...(0, execution_1.createExecutionTools)(options),
22
23
  (0, tool_1.createBrowserTool)(),
@@ -27,6 +28,12 @@ function createAgentCoreToolDefinitions(options) {
27
28
  ...(0, tools_2.createDiscoveryTools)(options),
28
29
  ...(0, tools_3.createWebTools)(),
29
30
  ].map((tool) => (0, results_1.wrapToolDefinition)(tool, options.onToolUse));
31
+ const tools = (0, programmaticToolCalling_1.shouldEnableProgrammaticToolCalling)(options)
32
+ ? [
33
+ ...baseTools,
34
+ (0, results_1.wrapToolDefinition)((0, programmaticToolCalling_1.createProgrammaticToolCallingTool)(options, baseTools), options.onToolUse),
35
+ ]
36
+ : baseTools;
30
37
  toolDefinitionScopes.set(tools, options);
31
38
  return tools;
32
39
  }
@@ -0,0 +1,4 @@
1
+ import type { AgenticToolOptions, CustomToolDefinition } from "../types";
2
+ export declare function shouldEnableProgrammaticToolCalling(options: AgenticToolOptions): boolean;
3
+ export declare function createProgrammaticToolCallingTool(options: AgenticToolOptions, availableTools: CustomToolDefinition[]): CustomToolDefinition;
4
+ //# sourceMappingURL=programmaticToolCalling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"programmaticToolCalling.d.ts","sourceRoot":"","sources":["../../../src/agenticTools/tools/programmaticToolCalling.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,oBAAoB,EAAc,MAAM,UAAU,CAAC;AAgBrF,wBAAgB,mCAAmC,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAExF;AAED,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,kBAAkB,EAC3B,cAAc,EAAE,oBAAoB,EAAE,GACrC,oBAAoB,CAwEtB"}
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.shouldEnableProgrammaticToolCalling = shouldEnableProgrammaticToolCalling;
7
+ exports.createProgrammaticToolCallingTool = createProgrammaticToolCallingTool;
8
+ const node_vm_1 = __importDefault(require("node:vm"));
9
+ const typebox_1 = require("@sinclair/typebox");
10
+ const results_1 = require("../shared/results");
11
+ const DEFAULT_TIMEOUT_MS = 120_000;
12
+ const DEFAULT_MAX_TOOL_CALLS = 25;
13
+ function shouldEnableProgrammaticToolCalling(options) {
14
+ return Boolean(options.programmaticToolCalling);
15
+ }
16
+ function createProgrammaticToolCallingTool(options, availableTools) {
17
+ const config = resolveExecutorConfig(options);
18
+ const callableTools = availableTools.filter((tool) => tool.name !== "code_executor");
19
+ return {
20
+ name: "code_executor",
21
+ label: "Programmatic Tool Calling",
22
+ description: [
23
+ "Execute a JavaScript tool chain against the agent-core tool surface.",
24
+ "Use tools.<name>(params) or callTool(name, params) to batch discovery,",
25
+ "fetch, filesystem, shell, web, and other enabled tools in one request.",
26
+ ].join(" "),
27
+ promptSnippet: [
28
+ "Programmatic Tool Calling is available through code_executor.",
29
+ "Write JavaScript inside an async function body and return the final value.",
30
+ "Call agent-core tools with await tools.read({ path: 'README.md' }) or await callTool('tool_search', { query: 'git' }).",
31
+ ].join("\n"),
32
+ parameters: typebox_1.Type.Object({
33
+ code: typebox_1.Type.String({
34
+ description: "JavaScript async function body. Use return <value> for the final result.",
35
+ }),
36
+ timeout: typebox_1.Type.Optional(typebox_1.Type.Number({ description: `Timeout in ms (default: ${config.timeout})` })),
37
+ max_tool_calls: typebox_1.Type.Optional(typebox_1.Type.Number({
38
+ description: `Maximum nested tool calls (default: ${config.maxToolCalls})`,
39
+ })),
40
+ }),
41
+ execute: async (_toolCallId, params) => {
42
+ const calls = [];
43
+ const timeout = resolveInvocationLimit(params.timeout, config.timeout);
44
+ const maxToolCalls = resolveInvocationLimit(params.max_tool_calls, config.maxToolCalls);
45
+ const logs = [];
46
+ const toolMap = new Map(callableTools.map((tool) => [tool.name, tool]));
47
+ const callTool = async (name, toolParams = {}) => {
48
+ if (calls.length >= maxToolCalls) {
49
+ throw new Error(`code_executor exceeded max_tool_calls (${maxToolCalls})`);
50
+ }
51
+ const tool = toolMap.get(name);
52
+ if (!tool) {
53
+ throw new Error(`Tool "${name}" is not available to code_executor.`);
54
+ }
55
+ calls.push({ tool: name, params: toolParams });
56
+ return unwrapToolResult(await tool.execute(`code-executor:${calls.length}:${name}`, toolParams));
57
+ };
58
+ const tools = Object.create(null);
59
+ for (const tool of callableTools) {
60
+ tools[tool.name] = (toolParams = {}) => callTool(tool.name, toolParams);
61
+ }
62
+ const context = node_vm_1.default.createContext({
63
+ callTool,
64
+ tools,
65
+ console: {
66
+ log: (...items) => logs.push(items.map(stringifyLogItem).join(" ")),
67
+ error: (...items) => logs.push(items.map(stringifyLogItem).join(" ")),
68
+ warn: (...items) => logs.push(items.map(stringifyLogItem).join(" ")),
69
+ },
70
+ });
71
+ const script = new node_vm_1.default.Script(`(async () => {\n${String(params.code ?? "")}\n})()`);
72
+ const startedAt = Date.now();
73
+ const result = await withTimeout(Promise.resolve(script.runInContext(context, { timeout })), timeout);
74
+ return (0, results_1.jsonResult)({
75
+ result,
76
+ logs,
77
+ toolCalls: calls,
78
+ duration: Date.now() - startedAt,
79
+ });
80
+ },
81
+ };
82
+ }
83
+ function resolveInvocationLimit(value, configuredLimit) {
84
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
85
+ return configuredLimit;
86
+ }
87
+ return Math.min(Math.floor(value), configuredLimit);
88
+ }
89
+ function resolveExecutorConfig(options) {
90
+ const configured = options.programmaticToolCalling;
91
+ if (configured && typeof configured === "object") {
92
+ return {
93
+ timeout: configured.timeout ?? DEFAULT_TIMEOUT_MS,
94
+ maxToolCalls: configured.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS,
95
+ };
96
+ }
97
+ return {
98
+ timeout: DEFAULT_TIMEOUT_MS,
99
+ maxToolCalls: DEFAULT_MAX_TOOL_CALLS,
100
+ };
101
+ }
102
+ async function withTimeout(promise, timeout) {
103
+ let timer;
104
+ try {
105
+ return await Promise.race([
106
+ promise,
107
+ new Promise((_resolve, reject) => {
108
+ timer = setTimeout(() => reject(new Error(`code_executor timed out after ${timeout}ms`)), timeout);
109
+ }),
110
+ ]);
111
+ }
112
+ finally {
113
+ if (timer) {
114
+ clearTimeout(timer);
115
+ }
116
+ }
117
+ }
118
+ function unwrapToolResult(result) {
119
+ const text = result.content.map((item) => item.text).join("\n");
120
+ try {
121
+ return JSON.parse(text);
122
+ }
123
+ catch {
124
+ return text;
125
+ }
126
+ }
127
+ function stringifyLogItem(item) {
128
+ if (typeof item === "string") {
129
+ return item;
130
+ }
131
+ try {
132
+ return JSON.stringify(item);
133
+ }
134
+ catch {
135
+ return String(item);
136
+ }
137
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type { AgentCorePromptResult, AgentCoreSessionEvent, AgentCoreSessionOptions, AgentCoreToolOptions, AgentCoreToolOptions as AgenticToolOptions, CustomToolDefinition, ToolResult, } from "./types";
1
+ export type { AgentCorePromptResult, AgentCoreSessionEvent, AgentCoreSessionOptions, AgentCoreToolOptions, AgentCoreToolOptions as AgenticToolOptions, CustomToolDefinition, ProgrammaticToolCallingOptions, ToolResult, } from "./types";
2
2
  export { AGENT_CORE_TOOL_NAMES } from "./types";
3
3
  export { AGENT_CORE_TOOL_NAMES as AGENTIC_TOOL_NAMES } from "./types";
4
4
  export { AgentCoreSessionHandle, createAgentCoreSession, type AgentCoreEventListener, } from "./session";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,IAAI,kBAAkB,EAC1C,oBAAoB,EACpB,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,qBAAqB,IAAI,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,8BAA8B,EAC9B,+BAA+B,EAC/B,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,IAAI,kBAAkB,EAC1C,oBAAoB,EACpB,8BAA8B,EAC9B,UAAU,GACX,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAChD,OAAO,EAAE,qBAAqB,IAAI,kBAAkB,EAAE,MAAM,SAAS,CAAC;AACtE,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,8BAA8B,EAC9B,+BAA+B,EAC/B,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/session.d.ts CHANGED
@@ -3,12 +3,13 @@ export type AgentCoreEventListener = (event: AgentCoreSessionEvent) => void;
3
3
  export declare class AgentCoreSessionHandle {
4
4
  private readonly options;
5
5
  private readonly listeners;
6
- private activeHandle;
7
6
  private queuedFollowUps;
8
7
  private currentSessionId;
8
+ private isActive;
9
9
  constructor(options?: AgentCoreSessionOptions);
10
10
  initialize(): Promise<void>;
11
11
  prompt(text: string, timeout?: number): Promise<AgentCorePromptResult>;
12
+ private emit;
12
13
  steer(text: string): Promise<void>;
13
14
  followUp(text: string): Promise<void>;
14
15
  subscribe(listener: AgentCoreEventListener): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AASjB,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAsG5E,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAC/D,OAAO,CAAC,YAAY,CAA0B;IAC9C,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,gBAAgB,CAAqB;gBAEjC,OAAO,GAAE,uBAA4B;IAI3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IA8DtE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ3C,SAAS,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAOjD,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAsC1E,WAAW,CACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAI1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAM5B,OAAO,IAAI,IAAI;IASf,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,sBAAsB,CAEhG"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,qBAAqB,EACrB,qBAAqB,EACrB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAIjB,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAiH5E,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAC/D,OAAO,CAAC,eAAe,CAAgB;IACvC,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,GAAE,uBAA4B;IAI3C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;IAuD5E,OAAO,CAAC,IAAI;IAMN,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI3C,SAAS,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,IAAI;IAOjD,cAAc,CAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAsC1E,WAAW,CACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,GAChC,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,SAAS,EAAE,OAAO,CAAA;KAAE,CAAC;IAI1E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,IAAI,IAAI;IAKf,IAAI,SAAS,IAAI,MAAM,GAAG,SAAS,CAElC;IAED,IAAI,WAAW,IAAI,OAAO,CAEzB;CACF;AAED,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,sBAAsB,CAEhG"}
package/dist/session.js CHANGED
@@ -37,30 +37,6 @@ exports.AgentCoreSessionHandle = void 0;
37
37
  exports.createAgentCoreSession = createAgentCoreSession;
38
38
  const childProcess = __importStar(require("node:child_process"));
39
39
  const DEFAULT_TIMEOUT_MS = 900_000;
40
- const DEFAULT_BACKEND = "codex-sdk";
41
- const SDK_BACKEND_SUFFIX = "-sdk";
42
- let agentMuxPromise = null;
43
- let agentMuxClientPromise = null;
44
- async function loadAgentMux() {
45
- if (!agentMuxPromise) {
46
- agentMuxPromise = Promise.resolve().then(() => __importStar(require("@a5c-ai/agent-mux")));
47
- }
48
- return agentMuxPromise;
49
- }
50
- async function getAgentMuxClient() {
51
- if (!agentMuxClientPromise) {
52
- agentMuxClientPromise = (async () => {
53
- const agentMux = await loadAgentMux();
54
- const client = agentMux.createClient({
55
- approvalMode: "prompt",
56
- stream: true,
57
- });
58
- agentMux.registerBuiltInAdapters(client);
59
- return client;
60
- })();
61
- }
62
- return agentMuxClientPromise;
63
- }
64
40
  function buildSystemPrompt(options) {
65
41
  const segments = [];
66
42
  if (options.systemPrompt?.trim()) {
@@ -78,57 +54,77 @@ function buildSystemPrompt(options) {
78
54
  }
79
55
  return segments.join("\n\n");
80
56
  }
81
- function mapThinkingLevel(thinkingLevel) {
82
- switch (thinkingLevel) {
83
- case "minimal":
84
- case "low":
85
- return "low";
86
- case "medium":
87
- return "medium";
88
- case "high":
89
- return "high";
90
- case "xhigh":
91
- return "max";
92
- default:
93
- return undefined;
94
- }
57
+ function resolveEndpoint(options) {
58
+ const model = options.model ?? "gpt-4o";
59
+ const amuxProvider = process.env["AMUX_PROVIDER"];
60
+ const amuxApiBase = process.env["AMUX_API_BASE"];
61
+ const amuxApiKey = process.env["AMUX_API_KEY"];
62
+ const azureApiKey = process.env["AZURE_API_KEY"];
63
+ const openaiApiKey = process.env["OPENAI_API_KEY"];
64
+ const anthropicApiKey = process.env["ANTHROPIC_API_KEY"];
65
+ if (amuxProvider === "foundry" || amuxProvider === "azure") {
66
+ const apiBase = amuxApiBase ?? "";
67
+ const apiKey = amuxApiKey ?? azureApiKey ?? "";
68
+ return { apiBase: `${apiBase}/openai`, apiKey, model, isAzure: true };
69
+ }
70
+ if (amuxApiBase) {
71
+ const apiKey = amuxApiKey ?? openaiApiKey ?? "";
72
+ return { apiBase: amuxApiBase, apiKey, model, isAzure: false };
73
+ }
74
+ if (openaiApiKey) {
75
+ return { apiBase: "https://api.openai.com/v1", apiKey: openaiApiKey, model, isAzure: false };
76
+ }
77
+ if (anthropicApiKey) {
78
+ return { apiBase: "https://api.anthropic.com", apiKey: anthropicApiKey, model, isAzure: false };
79
+ }
80
+ return { apiBase: "https://api.openai.com/v1", apiKey: amuxApiKey ?? "", model, isAzure: false };
95
81
  }
96
- function mapEventPayload(event) {
97
- if (!event || typeof event !== "object") {
98
- return { type: "unknown", value: event };
99
- }
100
- const typed = event;
101
- return {
102
- type: typeof typed.type === "string" ? typed.type : "unknown",
103
- ...typed,
104
- };
105
- }
106
- function resolveRunBackend(client, options) {
107
- const configuredBackend = options.backend ?? process.env.AGENT_CORE_BACKEND;
108
- if (configuredBackend) {
109
- return configuredBackend;
110
- }
111
- if (!options.model) {
112
- return DEFAULT_BACKEND;
113
- }
114
- const defaultBackendSupportsModel = client.models.model(DEFAULT_BACKEND, options.model);
115
- if (defaultBackendSupportsModel) {
116
- return DEFAULT_BACKEND;
117
- }
118
- if (DEFAULT_BACKEND.endsWith(SDK_BACKEND_SUFFIX)) {
119
- const fallbackBackend = DEFAULT_BACKEND.slice(0, -SDK_BACKEND_SUFFIX.length);
120
- if (client.adapters.get(fallbackBackend)) {
121
- return fallbackBackend;
82
+ async function callCompletionApi(endpoint, messages, timeout) {
83
+ const controller = new AbortController();
84
+ const timer = setTimeout(() => controller.abort(), timeout);
85
+ try {
86
+ let url;
87
+ const headers = { "Content-Type": "application/json" };
88
+ if (endpoint.isAzure) {
89
+ url = `${endpoint.apiBase}/deployments/${endpoint.model}/chat/completions?api-version=2025-04-01-preview`;
90
+ headers["api-key"] = endpoint.apiKey;
122
91
  }
92
+ else {
93
+ url = `${endpoint.apiBase}/chat/completions`;
94
+ headers["Authorization"] = `Bearer ${endpoint.apiKey}`;
95
+ }
96
+ const body = JSON.stringify({
97
+ model: endpoint.model,
98
+ messages,
99
+ max_completion_tokens: 16384,
100
+ });
101
+ const response = await fetch(url, {
102
+ method: "POST",
103
+ headers,
104
+ body,
105
+ signal: controller.signal,
106
+ });
107
+ if (!response.ok) {
108
+ const errorText = await response.text();
109
+ throw new Error(`API request failed (${response.status}): ${errorText}`);
110
+ }
111
+ const data = await response.json();
112
+ const text = data.choices?.[0]?.message?.content ?? "";
113
+ const usage = data.usage
114
+ ? { promptTokens: data.usage.prompt_tokens ?? 0, completionTokens: data.usage.completion_tokens ?? 0 }
115
+ : undefined;
116
+ return { text, usage };
117
+ }
118
+ finally {
119
+ clearTimeout(timer);
123
120
  }
124
- return DEFAULT_BACKEND;
125
121
  }
126
122
  class AgentCoreSessionHandle {
127
123
  options;
128
124
  listeners = new Set();
129
- activeHandle = null;
130
125
  queuedFollowUps = [];
131
126
  currentSessionId;
127
+ isActive = false;
132
128
  constructor(options = {}) {
133
129
  this.options = options;
134
130
  }
@@ -136,72 +132,62 @@ class AgentCoreSessionHandle {
136
132
  return;
137
133
  }
138
134
  async prompt(text, timeout) {
139
- if (this.activeHandle) {
135
+ if (this.isActive) {
140
136
  throw new Error("Agent core session is already processing a prompt");
141
137
  }
142
- const agentMux = await loadAgentMux();
143
- const client = await getAgentMuxClient();
138
+ this.isActive = true;
144
139
  const effectiveTimeout = timeout ?? this.options.timeout ?? DEFAULT_TIMEOUT_MS;
145
- const backend = resolveRunBackend(client, this.options);
146
- const thinkingEffort = mapThinkingLevel(this.options.thinkingLevel);
147
140
  const start = Date.now();
148
141
  const followUps = this.queuedFollowUps;
149
142
  this.queuedFollowUps = [];
150
143
  const promptText = followUps.length > 0
151
144
  ? [text, ...followUps.map((item) => `Follow-up instruction:\n${item}`)].join("\n\n")
152
145
  : text;
153
- const handle = client.run({
154
- agent: backend,
155
- prompt: promptText,
156
- cwd: this.options.workspace,
157
- model: this.options.model,
158
- timeout: effectiveTimeout,
159
- sessionId: this.currentSessionId,
160
- systemPrompt: buildSystemPrompt(this.options),
161
- systemPromptMode: this.options.systemPrompt ? "replace" : "append",
162
- approvalMode: this.options.uiContext ? "prompt" : "yolo",
163
- ...(thinkingEffort ? { thinkingEffort } : {}),
164
- collectEvents: true,
165
- });
166
- this.activeHandle = handle;
167
- const pump = (async () => {
168
- for await (const event of handle) {
169
- const mapped = mapEventPayload(event);
170
- if (mapped.type === "session_start" && typeof mapped.sessionId === "string") {
171
- this.currentSessionId = mapped.sessionId;
172
- }
173
- for (const listener of this.listeners) {
174
- listener(mapped);
175
- }
146
+ try {
147
+ const endpoint = resolveEndpoint(this.options);
148
+ const messages = [];
149
+ const systemPrompt = buildSystemPrompt(this.options);
150
+ if (systemPrompt) {
151
+ messages.push({ role: "system", content: systemPrompt });
176
152
  }
177
- })();
178
- const result = await handle;
179
- await pump;
180
- this.activeHandle = null;
181
- if (result.sessionId) {
182
- this.currentSessionId = result.sessionId;
153
+ messages.push({ role: "user", content: promptText });
154
+ const sessionId = this.currentSessionId ?? `agent-core-${Date.now()}`;
155
+ this.currentSessionId = sessionId;
156
+ this.emit({ type: "session_start", sessionId });
157
+ const result = await callCompletionApi(endpoint, messages, effectiveTimeout);
158
+ this.emit({ type: "text_delta", delta: result.text });
159
+ this.emit({ type: "session_end", sessionId });
160
+ return {
161
+ output: result.text,
162
+ duration: Date.now() - start,
163
+ success: true,
164
+ exitCode: 0,
165
+ };
166
+ }
167
+ catch (err) {
168
+ const message = err instanceof Error ? err.message : String(err);
169
+ this.emit({ type: "error", message });
170
+ return {
171
+ output: message,
172
+ duration: Date.now() - start,
173
+ success: false,
174
+ exitCode: 1,
175
+ };
176
+ }
177
+ finally {
178
+ this.isActive = false;
183
179
  }
184
- const output = result.text || result.error?.message || "";
185
- return {
186
- output,
187
- duration: Date.now() - start,
188
- success: result.exitReason === "completed" && !result.error,
189
- exitCode: result.exitCode ?? (result.error ? 1 : 0),
190
- };
191
180
  }
192
- async steer(text) {
193
- if (!this.activeHandle) {
194
- this.queuedFollowUps.push(text);
195
- return;
181
+ emit(event) {
182
+ for (const listener of this.listeners) {
183
+ listener(event);
196
184
  }
197
- await this.activeHandle.send(text);
185
+ }
186
+ async steer(text) {
187
+ this.queuedFollowUps.push(text);
198
188
  }
199
189
  async followUp(text) {
200
- if (!this.activeHandle) {
201
- this.queuedFollowUps.push(text);
202
- return;
203
- }
204
- await this.activeHandle.queue(text, { when: "after-response" });
190
+ this.queuedFollowUps.push(text);
205
191
  }
206
192
  subscribe(listener) {
207
193
  this.listeners.add(listener);
@@ -247,15 +233,9 @@ class AgentCoreSessionHandle {
247
233
  return this.executeCommand(command, onChunk);
248
234
  }
249
235
  async abort() {
250
- if (this.activeHandle) {
251
- await this.activeHandle.abort();
252
- }
236
+ // Direct API calls don't support mid-request abort easily
253
237
  }
254
238
  dispose() {
255
- if (this.activeHandle) {
256
- void this.activeHandle.abort().catch(() => undefined);
257
- this.activeHandle = null;
258
- }
259
239
  this.listeners.clear();
260
240
  this.queuedFollowUps = [];
261
241
  }
@@ -263,7 +243,7 @@ class AgentCoreSessionHandle {
263
243
  return this.currentSessionId;
264
244
  }
265
245
  get isStreaming() {
266
- return this.activeHandle !== null;
246
+ return this.isActive;
267
247
  }
268
248
  }
269
249
  exports.AgentCoreSessionHandle = AgentCoreSessionHandle;
@@ -1,258 +1,116 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  const vitest_1 = require("vitest");
37
- function createHandle(result, events = []) {
38
- const handle = Object.assign(Promise.resolve(result), {
39
- send: vitest_1.vi.fn(async () => undefined),
40
- queue: vitest_1.vi.fn(async () => undefined),
41
- abort: vitest_1.vi.fn(async () => undefined),
42
- async *[Symbol.asyncIterator]() {
43
- for (const event of events) {
44
- yield event;
45
- }
46
- },
47
- });
48
- return handle;
49
- }
50
- function createPendingHandle(events = []) {
51
- let resolveResult;
52
- const promise = new Promise((resolve) => {
53
- resolveResult = resolve;
54
- });
55
- const handle = Object.assign(promise, {
56
- send: vitest_1.vi.fn(async () => undefined),
57
- queue: vitest_1.vi.fn(async () => undefined),
58
- abort: vitest_1.vi.fn(async () => undefined),
59
- async *[Symbol.asyncIterator]() {
60
- for (const event of events) {
61
- yield event;
62
- }
63
- },
64
- });
65
- return {
66
- handle,
67
- resolve(result) {
68
- resolveResult?.(result);
69
- },
70
- };
71
- }
72
- async function loadSessionModule(args) {
73
- vitest_1.vi.resetModules();
74
- const run = vitest_1.vi.fn((options) => args.runImplementation?.(options) ?? createHandle(args.handleResult ?? { text: "ok", exitReason: "completed", exitCode: 0, sessionId: "session-1" }, args.events));
75
- const models = {
76
- model: vitest_1.vi.fn((agent, modelId) => args.modelImplementation?.(agent, modelId) ?? null),
77
- };
78
- const adapters = {
79
- get: vitest_1.vi.fn((agent) => args.adapterImplementation?.(agent)),
80
- };
81
- const createClient = vitest_1.vi.fn(() => ({ run, models, adapters }));
82
- const registerBuiltInAdapters = vitest_1.vi.fn();
83
- vitest_1.vi.doMock("@a5c-ai/agent-mux", () => ({
84
- createClient,
85
- registerBuiltInAdapters,
86
- }));
87
- const sessionModule = await Promise.resolve().then(() => __importStar(require("./session")));
88
- return { ...sessionModule, createClient, registerBuiltInAdapters, run, models, adapters };
89
- }
4
+ const session_1 = require("./session");
5
+ const mockFetch = vitest_1.vi.fn();
90
6
  (0, vitest_1.describe)("AgentCoreSessionHandle", () => {
7
+ (0, vitest_1.beforeEach)(() => {
8
+ vitest_1.vi.stubGlobal("fetch", mockFetch);
9
+ vitest_1.vi.stubEnv("OPENAI_API_KEY", "test-key");
10
+ });
91
11
  (0, vitest_1.afterEach)(() => {
92
- vitest_1.vi.resetModules();
93
- vitest_1.vi.clearAllMocks();
12
+ vitest_1.vi.unstubAllGlobals();
94
13
  vitest_1.vi.unstubAllEnvs();
14
+ vitest_1.vi.clearAllMocks();
95
15
  });
96
- (0, vitest_1.it)("forwards the supported run options and translates thinkingLevel", async () => {
97
- const sessionModule = await loadSessionModule({
98
- events: [{ type: "session_start", sessionId: "started-session" }],
99
- });
100
- const session = sessionModule.createAgentCoreSession({
101
- workspace: "/tmp/workspace",
102
- model: "gpt-5.4",
103
- timeout: 12_345,
104
- thinkingLevel: "xhigh",
105
- uiContext: { interactive: true },
106
- systemPrompt: "Base prompt",
107
- appendSystemPrompt: ["More context"],
108
- backend: "codex",
109
- toolsMode: "coding",
110
- customTools: [{ name: "ignored-tool" }],
111
- isolated: true,
112
- ephemeral: true,
113
- bashSandbox: "secure",
114
- enableCompaction: true,
115
- agentDir: "/tmp/agents",
116
- });
117
- await session.prompt("Implement the change");
118
- (0, vitest_1.expect)(sessionModule.createClient).toHaveBeenCalledWith({
119
- approvalMode: "prompt",
120
- stream: true,
121
- });
122
- (0, vitest_1.expect)(sessionModule.registerBuiltInAdapters).toHaveBeenCalledTimes(1);
123
- (0, vitest_1.expect)(sessionModule.run).toHaveBeenCalledWith({
124
- agent: "codex",
125
- prompt: "Implement the change",
126
- cwd: "/tmp/workspace",
127
- model: "gpt-5.4",
128
- timeout: 12_345,
129
- sessionId: undefined,
130
- systemPrompt: "Base prompt\n\nMore context",
131
- systemPromptMode: "replace",
132
- approvalMode: "prompt",
133
- thinkingEffort: "max",
134
- collectEvents: true,
16
+ function mockApiResponse(text) {
17
+ mockFetch.mockResolvedValueOnce({
18
+ ok: true,
19
+ json: async () => ({
20
+ choices: [{ message: { content: text } }],
21
+ usage: { prompt_tokens: 10, completion_tokens: 5 },
22
+ }),
135
23
  });
136
- const firstCall = sessionModule.run.mock.calls[0];
137
- (0, vitest_1.expect)(firstCall).toBeDefined();
138
- const forwarded = firstCall?.[0];
139
- (0, vitest_1.expect)(forwarded).not.toHaveProperty("toolsMode");
140
- (0, vitest_1.expect)(forwarded).not.toHaveProperty("customTools");
141
- (0, vitest_1.expect)(forwarded).not.toHaveProperty("isolated");
142
- (0, vitest_1.expect)(forwarded).not.toHaveProperty("ephemeral");
143
- (0, vitest_1.expect)(forwarded).not.toHaveProperty("bashSandbox");
144
- (0, vitest_1.expect)(forwarded).not.toHaveProperty("enableCompaction");
145
- (0, vitest_1.expect)(forwarded).not.toHaveProperty("agentDir");
24
+ }
25
+ (0, vitest_1.it)("makes a direct API call with the prompt", async () => {
26
+ mockApiResponse("hello world");
27
+ const session = (0, session_1.createAgentCoreSession)({ model: "gpt-5.5" });
28
+ const result = await session.prompt("Say hello");
29
+ (0, vitest_1.expect)(result.success).toBe(true);
30
+ (0, vitest_1.expect)(result.output).toBe("hello world");
31
+ (0, vitest_1.expect)(mockFetch).toHaveBeenCalledTimes(1);
32
+ const [url, options] = mockFetch.mock.calls[0];
33
+ (0, vitest_1.expect)(url).toBe("https://api.openai.com/v1/chat/completions");
34
+ const body = JSON.parse(options.body);
35
+ (0, vitest_1.expect)(body.model).toBe("gpt-5.5");
36
+ (0, vitest_1.expect)(body.messages).toEqual([{ role: "user", content: "Say hello" }]);
146
37
  });
147
- (0, vitest_1.it)("uses append mode and yolo approval when no interactive UI context is provided", async () => {
148
- const sessionModule = await loadSessionModule({});
149
- const session = sessionModule.createAgentCoreSession({
150
- appendSystemPrompt: ["Line one", "Line two"],
151
- });
152
- await session.prompt("Review this");
153
- (0, vitest_1.expect)(sessionModule.run).toHaveBeenCalledWith({
154
- agent: "codex-sdk",
155
- prompt: "Review this",
156
- cwd: undefined,
157
- model: undefined,
158
- timeout: 900_000,
159
- sessionId: undefined,
160
- systemPrompt: "Line one\n\nLine two",
161
- systemPromptMode: "append",
162
- approvalMode: "yolo",
163
- collectEvents: true,
164
- });
38
+ (0, vitest_1.it)("includes system prompt when provided", async () => {
39
+ mockApiResponse("ok");
40
+ const session = (0, session_1.createAgentCoreSession)({
41
+ systemPrompt: "You are helpful",
42
+ appendSystemPrompt: ["Be concise"],
43
+ });
44
+ await session.prompt("Do something");
45
+ const body = JSON.parse(mockFetch.mock.calls[0][1].body);
46
+ (0, vitest_1.expect)(body.messages[0]).toEqual({ role: "system", content: "You are helpful\n\nBe concise" });
47
+ (0, vitest_1.expect)(body.messages[1]).toEqual({ role: "user", content: "Do something" });
165
48
  });
166
- (0, vitest_1.it)("falls back from the implicit sdk backend to the paired subprocess backend for unsupported models", async () => {
167
- const sessionModule = await loadSessionModule({
168
- modelImplementation: () => null,
169
- adapterImplementation: (agent) => (agent === "codex" ? { agent } : undefined),
170
- });
171
- const session = sessionModule.createAgentCoreSession({
172
- model: "gpt-5.4",
173
- });
174
- await session.prompt("Plan the run");
175
- (0, vitest_1.expect)(sessionModule.models.model).toHaveBeenCalledWith("codex-sdk", "gpt-5.4");
176
- (0, vitest_1.expect)(sessionModule.adapters.get).toHaveBeenCalledWith("codex");
177
- (0, vitest_1.expect)(sessionModule.run).toHaveBeenCalledWith({
178
- agent: "codex",
179
- prompt: "Plan the run",
180
- cwd: undefined,
181
- model: "gpt-5.4",
182
- timeout: 900_000,
183
- sessionId: undefined,
184
- systemPrompt: undefined,
185
- systemPromptMode: "append",
186
- approvalMode: "yolo",
187
- collectEvents: true,
188
- });
49
+ (0, vitest_1.it)("routes to Azure foundry when AMUX_PROVIDER=foundry", async () => {
50
+ vitest_1.vi.stubEnv("AMUX_PROVIDER", "foundry");
51
+ vitest_1.vi.stubEnv("AMUX_API_BASE", "https://myresource.services.ai.azure.com");
52
+ vitest_1.vi.stubEnv("AZURE_API_KEY", "az-key-123");
53
+ mockApiResponse("azure response");
54
+ const session = (0, session_1.createAgentCoreSession)({ model: "gpt-5.5" });
55
+ await session.prompt("Hello");
56
+ const [url, options] = mockFetch.mock.calls[0];
57
+ (0, vitest_1.expect)(url).toBe("https://myresource.services.ai.azure.com/openai/deployments/gpt-5.5/chat/completions?api-version=2025-04-01-preview");
58
+ (0, vitest_1.expect)(options.headers["api-key"]).toBe("az-key-123");
59
+ (0, vitest_1.expect)(options.headers["Authorization"]).toBeUndefined();
189
60
  });
190
- (0, vitest_1.it)("reuses the session id learned from the prior run", async () => {
191
- const sessionModule = await loadSessionModule({
192
- handleResult: { text: "ok", exitReason: "completed", exitCode: 0, sessionId: "persisted-session" },
193
- });
194
- const session = sessionModule.createAgentCoreSession();
195
- await session.prompt("First prompt");
196
- await session.prompt("Second prompt");
197
- const firstCall = sessionModule.run.mock.calls[0];
198
- const secondCall = sessionModule.run.mock.calls[1];
199
- (0, vitest_1.expect)(firstCall).toBeDefined();
200
- (0, vitest_1.expect)(secondCall).toBeDefined();
201
- (0, vitest_1.expect)(firstCall?.[0]).toMatchObject({
202
- sessionId: undefined,
203
- });
204
- (0, vitest_1.expect)(secondCall?.[0]).toMatchObject({
205
- sessionId: "persisted-session",
206
- });
61
+ (0, vitest_1.it)("uses OPENAI_API_KEY with Bearer auth for OpenAI", async () => {
62
+ mockApiResponse("openai response");
63
+ const session = (0, session_1.createAgentCoreSession)({});
64
+ await session.prompt("Test");
65
+ const [, options] = mockFetch.mock.calls[0];
66
+ (0, vitest_1.expect)(options.headers["Authorization"]).toBe("Bearer test-key");
67
+ (0, vitest_1.expect)(options.headers["api-key"]).toBeUndefined();
207
68
  });
208
69
  (0, vitest_1.it)("appends queued follow-up instructions to the next prompt only once", async () => {
209
- const sessionModule = await loadSessionModule({});
210
- const session = sessionModule.createAgentCoreSession();
70
+ mockApiResponse("first");
71
+ mockApiResponse("second");
72
+ const session = (0, session_1.createAgentCoreSession)({});
211
73
  await session.steer("Use the session export path");
212
74
  await session.followUp("Add the registry regression");
213
75
  await session.prompt("Implement tests");
214
76
  await session.prompt("Verify again");
215
- (0, vitest_1.expect)(sessionModule.run.mock.calls[0]?.[0]).toMatchObject({
216
- prompt: [
217
- "Implement tests",
218
- "Follow-up instruction:\nUse the session export path",
219
- "Follow-up instruction:\nAdd the registry regression",
220
- ].join("\n\n"),
221
- });
222
- (0, vitest_1.expect)(sessionModule.run.mock.calls[1]?.[0]).toMatchObject({
223
- prompt: "Verify again",
224
- });
77
+ const firstBody = JSON.parse(mockFetch.mock.calls[0][1].body);
78
+ const secondBody = JSON.parse(mockFetch.mock.calls[1][1].body);
79
+ (0, vitest_1.expect)(firstBody.messages[0].content).toContain("Implement tests");
80
+ (0, vitest_1.expect)(firstBody.messages[0].content).toContain("Follow-up instruction:\nUse the session export path");
81
+ (0, vitest_1.expect)(secondBody.messages[0].content).toBe("Verify again");
225
82
  });
226
- (0, vitest_1.it)("normalizes unknown event payloads for subscribers", async () => {
227
- const sessionModule = await loadSessionModule({
228
- events: [null, { foo: "bar" }, { type: "session_start", sessionId: "event-session" }],
229
- handleResult: { text: "ok", exitReason: "completed", exitCode: 0, sessionId: "event-session" },
230
- });
231
- const session = sessionModule.createAgentCoreSession();
232
- const received = [];
233
- session.subscribe((event) => {
234
- received.push(event);
235
- });
236
- await session.prompt("Inspect event flow");
237
- (0, vitest_1.expect)(received).toEqual([
238
- { type: "unknown", value: null },
239
- { type: "unknown", foo: "bar" },
240
- { type: "session_start", sessionId: "event-session" },
241
- ]);
242
- (0, vitest_1.expect)(session.sessionId).toBe("event-session");
243
- (0, vitest_1.expect)(session.isStreaming).toBe(false);
83
+ (0, vitest_1.it)("emits events to subscribers", async () => {
84
+ mockApiResponse("streamed text");
85
+ const session = (0, session_1.createAgentCoreSession)({});
86
+ const events = [];
87
+ session.subscribe((event) => events.push(event));
88
+ await session.prompt("Test events");
89
+ (0, vitest_1.expect)(events.some((e) => e.type === "session_start")).toBe(true);
90
+ (0, vitest_1.expect)(events.some((e) => e.type === "text_delta" && e.delta === "streamed text")).toBe(true);
91
+ (0, vitest_1.expect)(events.some((e) => e.type === "session_end")).toBe(true);
244
92
  });
245
- (0, vitest_1.it)("rejects concurrent prompt attempts while a prompt is active", async () => {
246
- const pending = createPendingHandle();
247
- const sessionModule = await loadSessionModule({
248
- runImplementation: () => pending.handle,
249
- });
250
- const session = sessionModule.createAgentCoreSession();
251
- const firstPrompt = session.prompt("First prompt");
93
+ (0, vitest_1.it)("rejects concurrent prompt attempts", async () => {
94
+ let resolveResponse;
95
+ mockFetch.mockReturnValueOnce(new Promise((resolve) => { resolveResponse = resolve; }));
96
+ const session = (0, session_1.createAgentCoreSession)({});
97
+ const firstPrompt = session.prompt("First");
252
98
  await new Promise((resolve) => setTimeout(resolve, 0));
253
- await (0, vitest_1.expect)(session.prompt("Second prompt")).rejects.toThrow("Agent core session is already processing a prompt");
254
- pending.resolve({ text: "done", exitReason: "completed", exitCode: 0, sessionId: "session-1" });
99
+ await (0, vitest_1.expect)(session.prompt("Second")).rejects.toThrow("Agent core session is already processing a prompt");
100
+ resolveResponse({ ok: true, json: async () => ({ choices: [{ message: { content: "done" } }] }) });
255
101
  await firstPrompt;
256
102
  (0, vitest_1.expect)(session.isStreaming).toBe(false);
257
103
  });
104
+ (0, vitest_1.it)("handles API errors gracefully", async () => {
105
+ mockFetch.mockResolvedValueOnce({
106
+ ok: false,
107
+ status: 401,
108
+ text: async () => "Unauthorized",
109
+ });
110
+ const session = (0, session_1.createAgentCoreSession)({});
111
+ const result = await session.prompt("Will fail");
112
+ (0, vitest_1.expect)(result.success).toBe(false);
113
+ (0, vitest_1.expect)(result.exitCode).toBe(1);
114
+ (0, vitest_1.expect)(result.output).toContain("401");
115
+ });
258
116
  });
@@ -185,6 +185,83 @@ function mockSpawnExit(exitCode = 0, stdoutText = "") {
185
185
  exitCode: 7,
186
186
  });
187
187
  });
188
+ (0, vitest_1.it)("exposes code_executor only when programmatic tool calling is enabled", () => {
189
+ const workspace = (0, node_fs_1.mkdtempSync)(path.join(os.tmpdir(), "agent-core-code-mode-toggle-"));
190
+ (0, vitest_1.expect)(getToolDefinitions(workspace).some((tool) => tool.name === "code_executor")).toBe(false);
191
+ (0, vitest_1.expect)(getToolDefinitions(workspace, { programmaticToolCalling: true }).some((tool) => tool.name === "code_executor"))
192
+ .toBe(true);
193
+ });
194
+ (0, vitest_1.it)("executes a programmatic tool chain against existing tools", async () => {
195
+ const workspace = (0, node_fs_1.mkdtempSync)(path.join(os.tmpdir(), "agent-core-code-mode-"));
196
+ (0, node_fs_1.writeFileSync)(path.join(workspace, "note.txt"), "alpha\nbeta\n", "utf8");
197
+ const onToolUse = vitest_1.vi.fn();
198
+ const definitions = getToolDefinitions(workspace, {
199
+ onToolUse,
200
+ programmaticToolCalling: true,
201
+ });
202
+ const codeExecutor = definitions.find((tool) => tool.name === "code_executor");
203
+ if (!codeExecutor) {
204
+ throw new Error("Expected code_executor to be registered");
205
+ }
206
+ const result = await codeExecutor.execute("code-mode", {
207
+ code: [
208
+ "const readResult = await tools.read({ path: 'note.txt' });",
209
+ "await callTool('write', { path: 'copy.txt', content: readResult });",
210
+ "console.log('read bytes', String(readResult).length);",
211
+ "return { readResult, copied: await tools.read({ path: 'copy.txt' }) };",
212
+ ].join("\n"),
213
+ });
214
+ const resultText = getText(result);
215
+ if (resultText.startsWith("Error:")) {
216
+ throw new Error(resultText);
217
+ }
218
+ const payload = JSON.parse(resultText);
219
+ (0, vitest_1.expect)(payload.result.readResult).toContain("alpha");
220
+ (0, vitest_1.expect)(payload.result.copied).toContain("alpha");
221
+ (0, vitest_1.expect)(payload.logs).toEqual(["read bytes 17"]);
222
+ (0, vitest_1.expect)(payload.toolCalls.map((call) => call.tool)).toEqual(["read", "write", "read"]);
223
+ (0, vitest_1.expect)(onToolUse).toHaveBeenCalledWith("code_executor", vitest_1.expect.objectContaining({ code: vitest_1.expect.any(String) }));
224
+ (0, vitest_1.expect)(onToolUse).toHaveBeenCalledWith("read", { path: "note.txt" });
225
+ (0, vitest_1.expect)(onToolUse).toHaveBeenCalledWith("write", {
226
+ path: "copy.txt",
227
+ content: vitest_1.expect.stringContaining("alpha"),
228
+ });
229
+ });
230
+ (0, vitest_1.it)("enforces code_executor nested tool call limits", async () => {
231
+ const workspace = (0, node_fs_1.mkdtempSync)(path.join(os.tmpdir(), "agent-core-code-mode-limit-"));
232
+ const codeExecutor = getToolDefinitions(workspace, {
233
+ programmaticToolCalling: { maxToolCalls: 1 },
234
+ }).find((tool) => tool.name === "code_executor");
235
+ if (!codeExecutor) {
236
+ throw new Error("Expected code_executor to be registered");
237
+ }
238
+ const result = await codeExecutor.execute("code-mode-limit", {
239
+ code: [
240
+ "await tools.tool_search({ query: 'read' });",
241
+ "await tools.tool_search({ query: 'write' });",
242
+ "return 'unreachable';",
243
+ ].join("\n"),
244
+ });
245
+ (0, vitest_1.expect)(getText(result)).toBe("Error: code_executor exceeded max_tool_calls (1)");
246
+ });
247
+ (0, vitest_1.it)("does not allow code_executor invocations to raise configured limits", async () => {
248
+ const workspace = (0, node_fs_1.mkdtempSync)(path.join(os.tmpdir(), "agent-core-code-mode-limit-cap-"));
249
+ const codeExecutor = getToolDefinitions(workspace, {
250
+ programmaticToolCalling: { maxToolCalls: 1 },
251
+ }).find((tool) => tool.name === "code_executor");
252
+ if (!codeExecutor) {
253
+ throw new Error("Expected code_executor to be registered");
254
+ }
255
+ const result = await codeExecutor.execute("code-mode-limit-cap", {
256
+ max_tool_calls: 10,
257
+ code: [
258
+ "await tools.tool_search({ query: 'read' });",
259
+ "await tools.tool_search({ query: 'write' });",
260
+ "return 'unreachable';",
261
+ ].join("\n"),
262
+ });
263
+ (0, vitest_1.expect)(getText(result)).toBe("Error: code_executor exceeded max_tool_calls (1)");
264
+ });
188
265
  (0, vitest_1.it)("processes fetched html content and exposes helper exports directly", async () => {
189
266
  const workspace = (0, node_fs_1.mkdtempSync)(path.join(os.tmpdir(), "agent-core-web-"));
190
267
  const fetchMock = vitest_1.vi.fn(async () => ({
package/dist/types.d.ts CHANGED
@@ -105,6 +105,18 @@ export interface AgentCoreToolOptions {
105
105
  /** Optional externally managed registry. When provided, the caller owns disposal. */
106
106
  backgroundRegistry?: BackgroundProcessRegistry;
107
107
  deferredToolRegistry?: DeferredToolRegistry;
108
+ /**
109
+ * Opt-in Programmatic Tool Calling / Code Mode surface. When enabled,
110
+ * agent-core exposes a single `code_executor` tool that can execute a
111
+ * JavaScript tool chain against the already configured agent-core tools.
112
+ */
113
+ programmaticToolCalling?: boolean | ProgrammaticToolCallingOptions;
114
+ }
115
+ export interface ProgrammaticToolCallingOptions {
116
+ /** Maximum wall-clock time for one code_executor invocation. Default: 120000. */
117
+ timeout?: number;
118
+ /** Maximum nested tool calls allowed from one code_executor invocation. Default: 25. */
119
+ maxToolCalls?: number;
108
120
  }
109
121
  export declare const AGENT_CORE_TOOL_NAMES: string[];
110
122
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AAC7E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,uBAAuB;IACtC,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,aAAa,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;IAChE;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,CAAC;IAC9C;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACxB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kEAAkE;IAClE,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAC;IAC1C;;;OAGG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,OAAO,EAAE,CACP,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,QAAQ,CAAC,EAAE,OAAO,EAClB,WAAW,CAAC,EAAE,OAAO,KAClB,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAAC;CACvC;AAED,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,WAAW,EAAE,OAAO,CAAC;IACrB,mDAAmD;IACnD,sBAAsB,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAClE,WAAW,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACvD,YAAY,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxD,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACxD,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qFAAqF;IACrF,kBAAkB,CAAC,EAAE,yBAAyB,CAAC;IAC/C,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;CAC7C;AAED,eAAO,MAAM,qBAAqB,EAAE,MAAM,EA0BzC,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js CHANGED
@@ -25,6 +25,7 @@ exports.AGENT_CORE_TOOL_NAMES = [
25
25
  "background_list",
26
26
  "tool_search",
27
27
  "tool_fetch",
28
+ "code_executor",
28
29
  "web_search",
29
30
  "fetch_process",
30
31
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a5c-ai/agent-core",
3
- "version": "5.0.1-staging.ee42097a6d34",
3
+ "version": "5.0.1-staging.ee474eb45",
4
4
  "description": "Built-in agent-core runtime and tool surface for Babysitter.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -27,8 +27,8 @@
27
27
  "test": "vitest run --config vitest.config.ts"
28
28
  },
29
29
  "dependencies": {
30
- "@a5c-ai/agent-mux": "5.0.1-staging.ee42097a6d34",
31
- "@a5c-ai/babysitter-sdk": "5.0.1-staging.ee42097a6d34",
30
+ "@a5c-ai/agent-mux": "5.0.1-staging.ee474eb45",
31
+ "@a5c-ai/babysitter-sdk": "5.0.1-staging.ee474eb45",
32
32
  "@sinclair/typebox": "^0.34.48"
33
33
  },
34
34
  "devDependencies": {