@a5c-ai/agent-core 5.0.1-staging.f17326334 → 5.0.1-staging.f672fe79b

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a5c-ai/agent-core",
3
- "version": "5.0.1-staging.f17326334",
3
+ "version": "5.0.1-staging.f672fe79b",
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.f17326334",
31
- "@a5c-ai/babysitter-sdk": "5.0.1-staging.f17326334",
30
+ "@a5c-ai/agent-mux": "5.0.1-staging.f672fe79b",
31
+ "@a5c-ai/babysitter-sdk": "5.0.1-staging.f672fe79b",
32
32
  "@sinclair/typebox": "^0.34.48"
33
33
  },
34
34
  "devDependencies": {