@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 +2 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +105 -125
- package/dist/session.test.js +92 -234
- package/package.json +3 -3
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;
|
package/dist/session.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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.
|
|
135
|
+
if (this.isActive) {
|
|
140
136
|
throw new Error("Agent core session is already processing a prompt");
|
|
141
137
|
}
|
|
142
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
this.
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return;
|
|
181
|
+
emit(event) {
|
|
182
|
+
for (const listener of this.listeners) {
|
|
183
|
+
listener(event);
|
|
196
184
|
}
|
|
197
|
-
|
|
185
|
+
}
|
|
186
|
+
async steer(text) {
|
|
187
|
+
this.queuedFollowUps.push(text);
|
|
198
188
|
}
|
|
199
189
|
async followUp(text) {
|
|
200
|
-
|
|
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
|
-
|
|
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.
|
|
246
|
+
return this.isActive;
|
|
267
247
|
}
|
|
268
248
|
}
|
|
269
249
|
exports.AgentCoreSessionHandle = AgentCoreSessionHandle;
|
package/dist/session.test.js
CHANGED
|
@@ -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
|
-
|
|
38
|
-
|
|
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.
|
|
93
|
-
vitest_1.vi.clearAllMocks();
|
|
12
|
+
vitest_1.vi.unstubAllGlobals();
|
|
94
13
|
vitest_1.vi.unstubAllEnvs();
|
|
14
|
+
vitest_1.vi.clearAllMocks();
|
|
95
15
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
(0,
|
|
140
|
-
|
|
141
|
-
(0, vitest_1.expect)(
|
|
142
|
-
(0, vitest_1.expect)(
|
|
143
|
-
(0, vitest_1.expect)(
|
|
144
|
-
|
|
145
|
-
(0, vitest_1.expect)(
|
|
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)("
|
|
148
|
-
|
|
149
|
-
const session =
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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)("
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const session =
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
(0, vitest_1.expect)(
|
|
176
|
-
(0, vitest_1.expect)(
|
|
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)("
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
210
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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)("
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
|
254
|
-
|
|
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.
|
|
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.
|
|
31
|
-
"@a5c-ai/babysitter-sdk": "5.0.1-staging.
|
|
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": {
|