@contractspec/lib.ai-agent 7.0.10 → 8.0.2
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 +45 -4
- package/dist/agent/agent-factory.d.ts +14 -3
- package/dist/agent/agent-factory.js +653 -113
- package/dist/agent/contract-spec-agent.d.ts +16 -2
- package/dist/agent/contract-spec-agent.js +649 -112
- package/dist/agent/index.js +660 -118
- package/dist/agent/json-runner.d.ts +1 -1
- package/dist/agent/json-runner.js +649 -112
- package/dist/agent/unified-agent.d.ts +2 -2
- package/dist/agent/unified-agent.js +660 -118
- package/dist/approval/index.js +6 -1
- package/dist/approval/workflow.js +5 -1
- package/dist/exporters/claude-agent-exporter.d.ts +1 -1
- package/dist/exporters/claude-agent-exporter.js +3 -51
- package/dist/exporters/index.js +8 -54
- package/dist/exporters/opencode-exporter.d.ts +1 -1
- package/dist/exporters/opencode-exporter.js +3 -51
- package/dist/exporters/types.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3805 -71
- package/dist/interop/index.js +3 -51
- package/dist/interop/spec-consumer.d.ts +1 -1
- package/dist/interop/spec-consumer.js +3 -51
- package/dist/interop/tool-consumer.d.ts +1 -1
- package/dist/interop/types.d.ts +1 -1
- package/dist/knowledge/injector.d.ts +1 -1
- package/dist/node/agent/agent-factory.js +653 -113
- package/dist/node/agent/contract-spec-agent.js +649 -112
- package/dist/node/agent/index.js +660 -118
- package/dist/node/agent/json-runner.js +649 -112
- package/dist/node/agent/unified-agent.js +660 -118
- package/dist/node/approval/index.js +6 -1
- package/dist/node/approval/workflow.js +5 -1
- package/dist/node/exporters/claude-agent-exporter.js +3 -51
- package/dist/node/exporters/index.js +8 -54
- package/dist/node/exporters/opencode-exporter.js +3 -51
- package/dist/node/index.js +3805 -71
- package/dist/node/interop/index.js +3 -51
- package/dist/node/interop/spec-consumer.js +3 -51
- package/dist/node/providers/claude-agent-sdk/adapter.js +3 -51
- package/dist/node/providers/claude-agent-sdk/index.js +3 -51
- package/dist/node/providers/index.js +8 -53
- package/dist/node/providers/opencode-sdk/adapter.js +4 -51
- package/dist/node/providers/opencode-sdk/index.js +4 -51
- package/dist/node/telemetry/adapter.js +2 -0
- package/dist/node/telemetry/index.js +2 -0
- package/dist/providers/claude-agent-sdk/adapter.d.ts +1 -1
- package/dist/providers/claude-agent-sdk/adapter.js +3 -51
- package/dist/providers/claude-agent-sdk/index.js +3 -51
- package/dist/providers/claude-agent-sdk/tool-bridge.d.ts +1 -8
- package/dist/providers/index.js +8 -53
- package/dist/providers/opencode-sdk/adapter.d.ts +1 -13
- package/dist/providers/opencode-sdk/adapter.js +4 -51
- package/dist/providers/opencode-sdk/agent-bridge.d.ts +1 -10
- package/dist/providers/opencode-sdk/index.js +4 -51
- package/dist/providers/opencode-sdk/tool-bridge.d.ts +1 -4
- package/dist/providers/types.d.ts +1 -8
- package/dist/session/store.d.ts +2 -2
- package/dist/telemetry/adapter.d.ts +1 -0
- package/dist/telemetry/adapter.js +2 -0
- package/dist/telemetry/index.js +2 -0
- package/dist/tools/knowledge-tool.d.ts +1 -1
- package/dist/tools/mcp-server.d.ts +1 -1
- package/dist/tools/operation-tool-handler.d.ts +1 -1
- package/dist/tools/tool-adapter.d.ts +1 -1
- package/dist/types.d.ts +13 -0
- package/package.json +7 -43
- package/dist/node/spec/index.js +0 -2233
- package/dist/node/spec/registry.js +0 -2178
- package/dist/node/spec/spec.js +0 -2188
- package/dist/spec/index.d.ts +0 -2
- package/dist/spec/index.js +0 -2233
- package/dist/spec/registry.d.ts +0 -41
- package/dist/spec/registry.js +0 -2178
- package/dist/spec/spec.d.ts +0 -218
- package/dist/spec/spec.js +0 -2188
- package/dist/spec/spec.test.d.ts +0 -1
package/dist/node/index.js
CHANGED
|
@@ -2131,103 +2131,3837 @@ var init_i18n = __esm(() => {
|
|
|
2131
2131
|
init_messages();
|
|
2132
2132
|
});
|
|
2133
2133
|
|
|
2134
|
-
// src/
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2134
|
+
// src/approval/workflow.ts
|
|
2135
|
+
import { randomUUID } from "node:crypto";
|
|
2136
|
+
|
|
2137
|
+
class InMemoryApprovalStore {
|
|
2138
|
+
items = new Map;
|
|
2139
|
+
maxItems;
|
|
2140
|
+
constructor(options = {}) {
|
|
2141
|
+
this.maxItems = options.maxItems ?? 1000;
|
|
2142
|
+
}
|
|
2143
|
+
async create(request) {
|
|
2144
|
+
this.evictIfNeeded();
|
|
2145
|
+
this.items.set(request.id, request);
|
|
2146
|
+
}
|
|
2147
|
+
async get(id) {
|
|
2148
|
+
return this.items.get(id) ?? null;
|
|
2149
|
+
}
|
|
2150
|
+
async getByToolCallId(toolCallId) {
|
|
2151
|
+
for (const request of this.items.values()) {
|
|
2152
|
+
if (request.toolCallId === toolCallId) {
|
|
2153
|
+
return request;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
return null;
|
|
2157
|
+
}
|
|
2158
|
+
async update(id, updates) {
|
|
2159
|
+
const existing = this.items.get(id);
|
|
2160
|
+
if (existing) {
|
|
2161
|
+
this.items.set(id, { ...existing, ...updates });
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
async list(options) {
|
|
2165
|
+
let results = [...this.items.values()];
|
|
2166
|
+
if (options?.status) {
|
|
2167
|
+
results = results.filter((r) => r.status === options.status);
|
|
2168
|
+
}
|
|
2169
|
+
if (options?.agentId) {
|
|
2170
|
+
results = results.filter((r) => r.agentId === options.agentId);
|
|
2171
|
+
}
|
|
2172
|
+
if (options?.tenantId) {
|
|
2173
|
+
results = results.filter((r) => r.tenantId === options.tenantId);
|
|
2174
|
+
}
|
|
2175
|
+
return results.sort((a, b) => b.requestedAt.getTime() - a.requestedAt.getTime());
|
|
2176
|
+
}
|
|
2177
|
+
clear() {
|
|
2178
|
+
this.items.clear();
|
|
2179
|
+
}
|
|
2180
|
+
evictIfNeeded() {
|
|
2181
|
+
if (this.items.size < this.maxItems) {
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
let oldestId = null;
|
|
2185
|
+
let oldestTimestamp = Number.POSITIVE_INFINITY;
|
|
2186
|
+
for (const [id, request] of this.items.entries()) {
|
|
2187
|
+
const ts = request.requestedAt.getTime();
|
|
2188
|
+
if (ts < oldestTimestamp) {
|
|
2189
|
+
oldestTimestamp = ts;
|
|
2190
|
+
oldestId = id;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
if (oldestId) {
|
|
2194
|
+
this.items.delete(oldestId);
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
class ApprovalWorkflow {
|
|
2200
|
+
store;
|
|
2201
|
+
constructor(store = new InMemoryApprovalStore) {
|
|
2202
|
+
this.store = store;
|
|
2203
|
+
}
|
|
2204
|
+
async requestApproval(params) {
|
|
2205
|
+
const request = {
|
|
2206
|
+
id: randomUUID(),
|
|
2207
|
+
sessionId: params.sessionId,
|
|
2208
|
+
agentId: params.agentId,
|
|
2209
|
+
tenantId: params.tenantId,
|
|
2210
|
+
toolName: params.toolName,
|
|
2211
|
+
toolCallId: params.toolCallId,
|
|
2212
|
+
toolArgs: params.toolArgs,
|
|
2213
|
+
reason: params.reason,
|
|
2214
|
+
requestedAt: new Date,
|
|
2215
|
+
status: "pending",
|
|
2216
|
+
payload: params.payload
|
|
2217
|
+
};
|
|
2218
|
+
await this.store.create(request);
|
|
2219
|
+
return request;
|
|
2220
|
+
}
|
|
2221
|
+
async requestApprovalFromToolCall(toolCall, context) {
|
|
2222
|
+
return this.requestApproval({
|
|
2223
|
+
sessionId: context.sessionId,
|
|
2224
|
+
agentId: context.agentId,
|
|
2225
|
+
tenantId: context.tenantId,
|
|
2226
|
+
toolName: toolCall.toolName,
|
|
2227
|
+
toolCallId: toolCall.toolCallId,
|
|
2228
|
+
toolArgs: toolCall.args,
|
|
2229
|
+
reason: context.reason ?? createAgentI18n(context.locale).t("approval.toolRequiresApproval", {
|
|
2230
|
+
name: toolCall.toolName
|
|
2231
|
+
})
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
async approve(id, reviewer, notes) {
|
|
2235
|
+
await this.store.update(id, {
|
|
2236
|
+
status: "approved",
|
|
2237
|
+
reviewer,
|
|
2238
|
+
resolvedAt: new Date,
|
|
2239
|
+
notes
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
async reject(id, reviewer, notes) {
|
|
2243
|
+
await this.store.update(id, {
|
|
2244
|
+
status: "rejected",
|
|
2245
|
+
reviewer,
|
|
2246
|
+
resolvedAt: new Date,
|
|
2247
|
+
notes
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
async getStatus(toolCallId) {
|
|
2251
|
+
const request = await this.store.getByToolCallId(toolCallId);
|
|
2252
|
+
return request?.status ?? null;
|
|
2253
|
+
}
|
|
2254
|
+
async isApproved(toolCallId) {
|
|
2255
|
+
const status = await this.getStatus(toolCallId);
|
|
2256
|
+
return status === "approved";
|
|
2257
|
+
}
|
|
2258
|
+
async listPending(options) {
|
|
2259
|
+
return this.store.list({ ...options, status: "pending" });
|
|
2260
|
+
}
|
|
2261
|
+
async get(id) {
|
|
2262
|
+
return this.store.get(id);
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
function createApprovalWorkflow(store) {
|
|
2266
|
+
if (store && typeof store === "object" && "create" in store && typeof store.create === "function") {
|
|
2267
|
+
return new ApprovalWorkflow(store);
|
|
2268
|
+
}
|
|
2269
|
+
const options = store;
|
|
2270
|
+
return new ApprovalWorkflow(new InMemoryApprovalStore(options));
|
|
2271
|
+
}
|
|
2272
|
+
var init_workflow = __esm(() => {
|
|
2273
|
+
init_i18n();
|
|
2274
|
+
});
|
|
2275
|
+
|
|
2276
|
+
// src/knowledge/injector.ts
|
|
2277
|
+
async function injectStaticKnowledge(instructions, knowledgeRefs, retriever, locale) {
|
|
2278
|
+
if (!retriever)
|
|
2279
|
+
return instructions;
|
|
2280
|
+
const requiredRefs = knowledgeRefs.filter((ref) => ref.required);
|
|
2281
|
+
if (requiredRefs.length === 0)
|
|
2282
|
+
return instructions;
|
|
2283
|
+
const i18n = createAgentI18n(locale);
|
|
2284
|
+
const blocks = [];
|
|
2285
|
+
for (const ref of requiredRefs) {
|
|
2286
|
+
if (!retriever.supportsSpace(ref.key)) {
|
|
2287
|
+
console.warn(i18n.t("log.knowledge.spaceNotAvailable", { key: ref.key }));
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
try {
|
|
2291
|
+
const content = await retriever.getStatic(ref.key);
|
|
2292
|
+
if (content) {
|
|
2293
|
+
const header = ref.instructions ? `## ${ref.key}
|
|
2294
|
+
${ref.instructions}` : `## ${ref.key}`;
|
|
2295
|
+
blocks.push(`${header}
|
|
2296
|
+
|
|
2297
|
+
${content}`);
|
|
2298
|
+
}
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
console.warn(i18n.t("log.knowledge.loadFailed", { key: ref.key }), error);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
if (blocks.length === 0)
|
|
2304
|
+
return instructions;
|
|
2305
|
+
return `${instructions}
|
|
2306
|
+
|
|
2307
|
+
---
|
|
2308
|
+
|
|
2309
|
+
# ${i18n.t("knowledge.header")}
|
|
2310
|
+
|
|
2311
|
+
${i18n.t("knowledge.description")}
|
|
2312
|
+
|
|
2313
|
+
${blocks.join(`
|
|
2314
|
+
|
|
2315
|
+
---
|
|
2316
|
+
|
|
2317
|
+
`)}`;
|
|
2318
|
+
}
|
|
2319
|
+
function createKnowledgeInjector(retriever, locale) {
|
|
2320
|
+
return {
|
|
2321
|
+
inject: (instructions, knowledgeRefs) => injectStaticKnowledge(instructions, knowledgeRefs, retriever, locale),
|
|
2322
|
+
hasSpace: (spaceKey) => retriever?.supportsSpace(spaceKey) ?? false,
|
|
2323
|
+
listSpaces: () => retriever?.listSpaces() ?? []
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
var init_injector = __esm(() => {
|
|
2327
|
+
init_i18n();
|
|
2328
|
+
});
|
|
2329
|
+
|
|
2330
|
+
// src/session/store.ts
|
|
2331
|
+
class InMemorySessionStore {
|
|
2332
|
+
sessions = new Map;
|
|
2333
|
+
maxSessions;
|
|
2334
|
+
maxMessagesPerSession;
|
|
2335
|
+
maxStepsPerSession;
|
|
2336
|
+
constructor(options = {}) {
|
|
2337
|
+
this.maxSessions = options.maxSessions ?? 500;
|
|
2338
|
+
this.maxMessagesPerSession = options.maxMessagesPerSession ?? 200;
|
|
2339
|
+
this.maxStepsPerSession = options.maxStepsPerSession ?? 200;
|
|
2340
|
+
}
|
|
2341
|
+
async get(sessionId) {
|
|
2342
|
+
return this.sessions.get(sessionId) ?? null;
|
|
2343
|
+
}
|
|
2344
|
+
async create(session) {
|
|
2345
|
+
this.evictIfNeeded();
|
|
2346
|
+
const now = new Date;
|
|
2347
|
+
const fullSession = {
|
|
2348
|
+
...session,
|
|
2349
|
+
createdAt: now,
|
|
2350
|
+
updatedAt: now
|
|
2351
|
+
};
|
|
2352
|
+
this.sessions.set(session.sessionId, fullSession);
|
|
2353
|
+
return fullSession;
|
|
2354
|
+
}
|
|
2355
|
+
async appendStep(sessionId, step) {
|
|
2356
|
+
const session = this.sessions.get(sessionId);
|
|
2357
|
+
if (session) {
|
|
2358
|
+
session.steps.push(step);
|
|
2359
|
+
this.trimArray(session.steps, this.maxStepsPerSession);
|
|
2360
|
+
session.updatedAt = new Date;
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
async appendMessage(sessionId, message) {
|
|
2364
|
+
const session = this.sessions.get(sessionId);
|
|
2365
|
+
if (session) {
|
|
2366
|
+
session.messages.push(message);
|
|
2367
|
+
this.trimArray(session.messages, this.maxMessagesPerSession);
|
|
2368
|
+
session.updatedAt = new Date;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
async update(sessionId, updates) {
|
|
2372
|
+
const session = this.sessions.get(sessionId);
|
|
2373
|
+
if (session) {
|
|
2374
|
+
Object.assign(session, updates, { updatedAt: new Date });
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
async delete(sessionId) {
|
|
2378
|
+
return this.sessions.delete(sessionId);
|
|
2379
|
+
}
|
|
2380
|
+
async listByAgent(agentId, limit = 100) {
|
|
2381
|
+
const results = [];
|
|
2382
|
+
for (const session of this.sessions.values()) {
|
|
2383
|
+
if (session.agentId === agentId) {
|
|
2384
|
+
results.push(session);
|
|
2385
|
+
if (results.length >= limit)
|
|
2386
|
+
break;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
return results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
2390
|
+
}
|
|
2391
|
+
async listByTenant(tenantId, limit = 100) {
|
|
2392
|
+
const results = [];
|
|
2393
|
+
for (const session of this.sessions.values()) {
|
|
2394
|
+
if (session.tenantId === tenantId) {
|
|
2395
|
+
results.push(session);
|
|
2396
|
+
if (results.length >= limit)
|
|
2397
|
+
break;
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
2400
|
+
return results.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
|
|
2401
|
+
}
|
|
2402
|
+
clear() {
|
|
2403
|
+
this.sessions.clear();
|
|
2404
|
+
}
|
|
2405
|
+
evictIfNeeded() {
|
|
2406
|
+
if (this.sessions.size < this.maxSessions) {
|
|
2407
|
+
return;
|
|
2408
|
+
}
|
|
2409
|
+
let oldestSessionId = null;
|
|
2410
|
+
let oldestUpdatedAt = Number.POSITIVE_INFINITY;
|
|
2411
|
+
for (const [sessionId, session] of this.sessions.entries()) {
|
|
2412
|
+
const updatedAt = session.updatedAt.getTime();
|
|
2413
|
+
if (updatedAt < oldestUpdatedAt) {
|
|
2414
|
+
oldestUpdatedAt = updatedAt;
|
|
2415
|
+
oldestSessionId = sessionId;
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
if (oldestSessionId) {
|
|
2419
|
+
this.sessions.delete(oldestSessionId);
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
trimArray(items, maxItems) {
|
|
2423
|
+
if (items.length <= maxItems) {
|
|
2424
|
+
return;
|
|
2425
|
+
}
|
|
2426
|
+
const overflow = items.length - maxItems;
|
|
2427
|
+
items.splice(0, overflow);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
function createInMemorySessionStore(options) {
|
|
2431
|
+
return new InMemorySessionStore(options);
|
|
2432
|
+
}
|
|
2433
|
+
function createSecureSessionToken() {
|
|
2434
|
+
const cryptoApi = globalThis.crypto;
|
|
2435
|
+
if (typeof cryptoApi?.randomUUID === "function") {
|
|
2436
|
+
return cryptoApi.randomUUID();
|
|
2437
|
+
}
|
|
2438
|
+
if (typeof cryptoApi?.getRandomValues === "function") {
|
|
2439
|
+
const bytes = cryptoApi.getRandomValues(new Uint8Array(16));
|
|
2440
|
+
const versionByte = bytes.at(6);
|
|
2441
|
+
const variantByte = bytes.at(8);
|
|
2442
|
+
if (versionByte === undefined || variantByte === undefined) {
|
|
2443
|
+
throw new Error("Secure session token generation requires 16 random bytes.");
|
|
2444
|
+
}
|
|
2445
|
+
bytes[6] = versionByte & 15 | 64;
|
|
2446
|
+
bytes[8] = variantByte & 63 | 128;
|
|
2447
|
+
const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, "0"));
|
|
2448
|
+
return [
|
|
2449
|
+
hex.slice(0, 4).join(""),
|
|
2450
|
+
hex.slice(4, 6).join(""),
|
|
2451
|
+
hex.slice(6, 8).join(""),
|
|
2452
|
+
hex.slice(8, 10).join(""),
|
|
2453
|
+
hex.slice(10, 16).join("")
|
|
2454
|
+
].join("-");
|
|
2455
|
+
}
|
|
2456
|
+
throw new Error("Secure session IDs require Web Crypto support.");
|
|
2457
|
+
}
|
|
2458
|
+
function generateSessionId() {
|
|
2459
|
+
return `sess_${createSecureSessionToken()}`;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
// src/telemetry/adapter.ts
|
|
2463
|
+
function parseAgentId(agentId) {
|
|
2464
|
+
const match = agentId.match(/^(.+)\.v(\d+)$/);
|
|
2465
|
+
if (match) {
|
|
2466
|
+
return { name: match[1], version: match[2] };
|
|
2467
|
+
}
|
|
2468
|
+
return { name: agentId, version: "1.0.0" };
|
|
2469
|
+
}
|
|
2470
|
+
function getRecord(value) {
|
|
2471
|
+
return value && typeof value === "object" ? value : undefined;
|
|
2472
|
+
}
|
|
2473
|
+
function getStepResponse(step) {
|
|
2474
|
+
const response = getRecord(step.response);
|
|
2475
|
+
if (!response)
|
|
2476
|
+
return;
|
|
2477
|
+
return {
|
|
2478
|
+
id: response["id"],
|
|
2479
|
+
modelId: response["modelId"],
|
|
2480
|
+
timestamp: response["timestamp"],
|
|
2481
|
+
headers: response["headers"],
|
|
2482
|
+
body: response["body"],
|
|
2483
|
+
messages: response["messages"]
|
|
2484
|
+
};
|
|
2485
|
+
}
|
|
2486
|
+
function getRequestBodyValue(request, key) {
|
|
2487
|
+
const body = request?.["body"];
|
|
2488
|
+
if (!body)
|
|
2489
|
+
return;
|
|
2490
|
+
if (typeof body === "string") {
|
|
2491
|
+
try {
|
|
2492
|
+
const parsed = JSON.parse(body);
|
|
2493
|
+
return typeof parsed[key] === "string" ? parsed[key] : undefined;
|
|
2494
|
+
} catch {
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
const record = getRecord(body);
|
|
2499
|
+
return typeof record?.[key] === "string" ? record[key] : undefined;
|
|
2500
|
+
}
|
|
2501
|
+
async function trackAgentStep(collector, agentId, step, durationMs, context) {
|
|
2502
|
+
const { name, version } = parseAgentId(agentId);
|
|
2503
|
+
const response = getStepResponse(step);
|
|
2504
|
+
const providerMetadata = step.providerMetadata;
|
|
2505
|
+
const warnings = step.warnings;
|
|
2506
|
+
const rawFinishReason = step.rawFinishReason;
|
|
2507
|
+
const request = getRecord(step.request);
|
|
2508
|
+
const traceId = context?.traceId ?? getRequestBodyValue(request, "$ai_trace_id");
|
|
2509
|
+
const sessionId = context?.sessionId ?? getRequestBodyValue(request, "$ai_session_id");
|
|
2510
|
+
const parentSpanId = response?.id;
|
|
2511
|
+
for (const toolCall of step.toolCalls ?? []) {
|
|
2512
|
+
const toolResult = step.toolResults?.find((r) => r.toolCallId === toolCall.toolCallId);
|
|
2513
|
+
const toolError = getRecord(toolResult?.output)?.["error"];
|
|
2514
|
+
const toolSample = {
|
|
2515
|
+
operation: { name: `${name}.${toolCall.toolName}`, version },
|
|
2516
|
+
durationMs: durationMs ?? 0,
|
|
2517
|
+
success: step.toolResults?.some((r) => r.toolCallId === toolCall.toolCallId && r.output !== undefined) ?? false,
|
|
2518
|
+
timestamp: new Date,
|
|
2519
|
+
metadata: {
|
|
2520
|
+
telemetryEvent: "span",
|
|
2521
|
+
traceId,
|
|
2522
|
+
sessionId,
|
|
2523
|
+
spanId: toolCall.toolCallId,
|
|
2524
|
+
parentSpanId,
|
|
2525
|
+
spanName: `tool.${toolCall.toolName}`,
|
|
2526
|
+
agentId,
|
|
2527
|
+
actorId: context?.actorId,
|
|
2528
|
+
tenantId: context?.tenantId,
|
|
2529
|
+
workflowId: context?.workflowId,
|
|
2530
|
+
stepIndex: context?.stepIndex,
|
|
2531
|
+
toolName: toolCall.toolName,
|
|
2532
|
+
toolCallArgs: toolCall.input,
|
|
2533
|
+
toolResultOutput: toolResult?.output,
|
|
2534
|
+
errorMessage: typeof toolError === "string" ? toolError : typeof toolResult?.output === "string" ? toolResult.output : undefined,
|
|
2535
|
+
finishReason: step.finishReason,
|
|
2536
|
+
rawFinishReason
|
|
2537
|
+
}
|
|
2538
|
+
};
|
|
2539
|
+
await collector.collect(toolSample);
|
|
2540
|
+
}
|
|
2541
|
+
const stepSample = {
|
|
2542
|
+
operation: { name, version },
|
|
2543
|
+
durationMs: durationMs ?? 0,
|
|
2544
|
+
success: step.finishReason !== "error",
|
|
2545
|
+
timestamp: new Date,
|
|
2546
|
+
metadata: {
|
|
2547
|
+
telemetryEvent: "generation",
|
|
2548
|
+
traceId,
|
|
2549
|
+
sessionId,
|
|
2550
|
+
spanId: response?.id,
|
|
2551
|
+
spanName: `agent.${name}.step`,
|
|
2552
|
+
agentId,
|
|
2553
|
+
actorId: context?.actorId,
|
|
2554
|
+
tenantId: context?.tenantId,
|
|
2555
|
+
workflowId: context?.workflowId,
|
|
2556
|
+
stepIndex: context?.stepIndex,
|
|
2557
|
+
stepStartedAt: context?.stepStartedAt,
|
|
2558
|
+
finishReason: step.finishReason,
|
|
2559
|
+
rawFinishReason,
|
|
2560
|
+
tokenUsage: step.usage,
|
|
2561
|
+
totalUsage: step.totalUsage,
|
|
2562
|
+
providerMetadata,
|
|
2563
|
+
warnings,
|
|
2564
|
+
stepText: step.text,
|
|
2565
|
+
stepReasoningText: step.reasoningText,
|
|
2566
|
+
responseId: response?.id,
|
|
2567
|
+
responseModelId: response?.modelId,
|
|
2568
|
+
responseTimestamp: response?.timestamp,
|
|
2569
|
+
responseHeaders: response?.headers,
|
|
2570
|
+
responseBody: response?.body,
|
|
2571
|
+
responseMessages: response?.messages,
|
|
2572
|
+
requestBody: request?.["body"],
|
|
2573
|
+
requestHeaders: request?.["headers"],
|
|
2574
|
+
toolCallCount: step.toolCalls?.length ?? 0,
|
|
2575
|
+
toolCalls: step.toolCalls,
|
|
2576
|
+
toolResults: step.toolResults,
|
|
2577
|
+
errorMessage: step.finishReason === "error" ? step.text : undefined
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
await collector.collect(stepSample);
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
class InMemoryTelemetryCollector {
|
|
2584
|
+
samples = [];
|
|
2585
|
+
async collect(sample) {
|
|
2586
|
+
this.samples.push(sample);
|
|
2587
|
+
}
|
|
2588
|
+
getSamples() {
|
|
2589
|
+
return [...this.samples];
|
|
2590
|
+
}
|
|
2591
|
+
getSamplesForOperation(operationName) {
|
|
2592
|
+
return this.samples.filter((s) => s.operation.name === operationName);
|
|
2593
|
+
}
|
|
2594
|
+
clear() {
|
|
2595
|
+
this.samples.length = 0;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
function createInMemoryTelemetryCollector() {
|
|
2599
|
+
return new InMemoryTelemetryCollector;
|
|
2600
|
+
}
|
|
2601
|
+
var noopTelemetryCollector;
|
|
2602
|
+
var init_adapter = __esm(() => {
|
|
2603
|
+
noopTelemetryCollector = {
|
|
2604
|
+
collect: async () => {}
|
|
2605
|
+
};
|
|
2606
|
+
});
|
|
2607
|
+
|
|
2608
|
+
// src/tools/knowledge-tool.ts
|
|
2609
|
+
import { tool } from "ai";
|
|
2610
|
+
import * as z from "zod";
|
|
2611
|
+
function createKnowledgeQueryTool(retriever, knowledgeRefs, locale) {
|
|
2612
|
+
const i18n = createAgentI18n(locale);
|
|
2613
|
+
const optionalSpaces = knowledgeRefs.filter((k) => !k.required).map((k) => k.key).filter((key) => retriever.supportsSpace(key));
|
|
2614
|
+
if (optionalSpaces.length === 0) {
|
|
2615
|
+
return null;
|
|
2616
|
+
}
|
|
2617
|
+
const spaceDescriptions = knowledgeRefs.filter((k) => !k.required && retriever.supportsSpace(k.key)).map((k) => `- ${k.key}: ${k.instructions ?? i18n.t("tool.knowledge.spaceDefault")}`).join(`
|
|
2618
|
+
`);
|
|
2619
|
+
return tool({
|
|
2620
|
+
description: `${i18n.t("tool.knowledge.description")}
|
|
2621
|
+
|
|
2622
|
+
${i18n.t("tool.knowledge.availableSpaces")}
|
|
2623
|
+
${spaceDescriptions}`,
|
|
2624
|
+
inputSchema: z.object({
|
|
2625
|
+
query: z.string().describe(i18n.t("tool.knowledge.param.query")),
|
|
2626
|
+
spaceKey: z.enum(optionalSpaces).optional().describe(i18n.t("tool.knowledge.param.spaceKey")),
|
|
2627
|
+
topK: z.number().optional().default(5).describe(i18n.t("tool.knowledge.param.topK"))
|
|
2628
|
+
}),
|
|
2629
|
+
execute: async ({ query, spaceKey, topK }) => {
|
|
2630
|
+
const spacesToSearch = spaceKey ? [spaceKey] : optionalSpaces;
|
|
2631
|
+
const allResults = [];
|
|
2632
|
+
for (const space of spacesToSearch) {
|
|
2633
|
+
try {
|
|
2634
|
+
const results = await retriever.retrieve(query, {
|
|
2635
|
+
spaceKey: space,
|
|
2636
|
+
topK: topK ?? 5
|
|
2637
|
+
});
|
|
2638
|
+
for (const result of results) {
|
|
2639
|
+
allResults.push({
|
|
2640
|
+
space,
|
|
2641
|
+
content: result.content,
|
|
2642
|
+
score: result.score
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
} catch (error) {
|
|
2646
|
+
console.warn(i18n.t("log.knowledge.queryFailed", { space }), error);
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
if (allResults.length === 0) {
|
|
2650
|
+
return i18n.t("tool.knowledge.noResults");
|
|
2651
|
+
}
|
|
2652
|
+
allResults.sort((a, b) => b.score - a.score);
|
|
2653
|
+
const topResults = allResults.slice(0, topK ?? 5);
|
|
2654
|
+
return topResults.map((r, i) => `${i18n.t("tool.knowledge.sourceLabel", { index: i + 1, space: r.space, score: (r.score * 100).toFixed(0) })}
|
|
2655
|
+
${r.content}`).join(`
|
|
2656
|
+
|
|
2657
|
+
---
|
|
2658
|
+
|
|
2659
|
+
`);
|
|
2660
|
+
}
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
var init_knowledge_tool = __esm(() => {
|
|
2664
|
+
init_i18n();
|
|
2665
|
+
});
|
|
2666
|
+
|
|
2667
|
+
// src/tools/mcp-client-helpers.ts
|
|
2668
|
+
import {
|
|
2669
|
+
Experimental_StdioMCPTransport as StdioClientTransport
|
|
2670
|
+
} from "@ai-sdk/mcp/mcp-stdio";
|
|
2671
|
+
function buildMcpTransport(config) {
|
|
2672
|
+
const transport = resolveTransportType(config);
|
|
2673
|
+
if (transport === "stdio") {
|
|
2674
|
+
const stdioConfig = resolveStdioConfig(config);
|
|
2675
|
+
return new StdioClientTransport(stdioConfig);
|
|
2676
|
+
}
|
|
2677
|
+
const remoteConfig = config;
|
|
2678
|
+
const headers = resolveRemoteHeaders(remoteConfig);
|
|
2679
|
+
const remoteTransport = {
|
|
2680
|
+
type: transport,
|
|
2681
|
+
url: requireNonEmptyString(remoteConfig.url, "url", config.name)
|
|
2682
|
+
};
|
|
2683
|
+
if (headers) {
|
|
2684
|
+
remoteTransport.headers = headers;
|
|
2685
|
+
}
|
|
2686
|
+
if (remoteConfig.authProvider) {
|
|
2687
|
+
remoteTransport.authProvider = remoteConfig.authProvider;
|
|
2688
|
+
}
|
|
2689
|
+
return remoteTransport;
|
|
2690
|
+
}
|
|
2691
|
+
function prefixToolNames(config, tools) {
|
|
2692
|
+
const prefix = config.toolPrefix?.trim();
|
|
2693
|
+
if (!prefix) {
|
|
2694
|
+
return tools;
|
|
2695
|
+
}
|
|
2696
|
+
const prefixedTools = {};
|
|
2697
|
+
for (const [toolName, tool2] of Object.entries(tools)) {
|
|
2698
|
+
prefixedTools[`${prefix}_${toolName}`] = tool2;
|
|
2699
|
+
}
|
|
2700
|
+
return prefixedTools;
|
|
2701
|
+
}
|
|
2702
|
+
function getErrorMessage(error) {
|
|
2703
|
+
if (error instanceof Error) {
|
|
2704
|
+
return error.message;
|
|
2705
|
+
}
|
|
2706
|
+
return String(error);
|
|
2707
|
+
}
|
|
2708
|
+
function resolveTransportType(config) {
|
|
2709
|
+
return config.transport ?? "stdio";
|
|
2710
|
+
}
|
|
2711
|
+
function resolveStdioConfig(config) {
|
|
2712
|
+
const stdioConfig = config;
|
|
2713
|
+
return {
|
|
2714
|
+
command: requireNonEmptyString(stdioConfig.command, "command", config.name),
|
|
2715
|
+
args: stdioConfig.args,
|
|
2716
|
+
env: stdioConfig.env,
|
|
2717
|
+
cwd: stdioConfig.cwd
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
function resolveRemoteHeaders(config) {
|
|
2721
|
+
const headers = {
|
|
2722
|
+
...config.headers ?? {}
|
|
2723
|
+
};
|
|
2724
|
+
const accessToken = config.accessToken ?? resolveEnvToken(config.accessTokenEnvVar);
|
|
2725
|
+
if (accessToken && headers.Authorization === undefined) {
|
|
2726
|
+
headers.Authorization = `Bearer ${accessToken}`;
|
|
2727
|
+
}
|
|
2728
|
+
return Object.keys(headers).length > 0 ? headers : undefined;
|
|
2729
|
+
}
|
|
2730
|
+
function resolveEnvToken(envVarName) {
|
|
2731
|
+
if (!envVarName) {
|
|
2732
|
+
return;
|
|
2733
|
+
}
|
|
2734
|
+
const value = process.env[envVarName];
|
|
2735
|
+
if (!value) {
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
const trimmed = value.trim();
|
|
2739
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
2740
|
+
}
|
|
2741
|
+
function requireNonEmptyString(value, field, serverName) {
|
|
2742
|
+
if (!value) {
|
|
2743
|
+
throw new Error(`MCP server "${serverName}" is missing required "${field}".`);
|
|
2744
|
+
}
|
|
2745
|
+
const trimmed = value.trim();
|
|
2746
|
+
if (trimmed.length === 0) {
|
|
2747
|
+
throw new Error(`MCP server "${serverName}" has an empty "${field}".`);
|
|
2748
|
+
}
|
|
2749
|
+
return trimmed;
|
|
2750
|
+
}
|
|
2751
|
+
var init_mcp_client_helpers = () => {};
|
|
2752
|
+
|
|
2753
|
+
// src/tools/mcp-client.ts
|
|
2754
|
+
import {
|
|
2755
|
+
experimental_createMCPClient
|
|
2756
|
+
} from "@ai-sdk/mcp";
|
|
2757
|
+
async function mcpServerToTools(config) {
|
|
2758
|
+
let client = null;
|
|
2759
|
+
try {
|
|
2760
|
+
const transport = buildMcpTransport(config);
|
|
2761
|
+
client = await experimental_createMCPClient({
|
|
2762
|
+
transport,
|
|
2763
|
+
name: config.clientName,
|
|
2764
|
+
version: config.clientVersion
|
|
2765
|
+
});
|
|
2766
|
+
const tools = await client.tools();
|
|
2767
|
+
const prefixedTools = prefixToolNames(config, tools);
|
|
2768
|
+
const connectedClient = client;
|
|
2769
|
+
return {
|
|
2770
|
+
tools: prefixedTools,
|
|
2771
|
+
cleanup: () => connectedClient.close(),
|
|
2772
|
+
serverToolNames: {
|
|
2773
|
+
[config.name]: Object.keys(prefixedTools)
|
|
2774
|
+
}
|
|
2775
|
+
};
|
|
2776
|
+
} catch (error) {
|
|
2777
|
+
if (client) {
|
|
2778
|
+
await client.close().catch(() => {
|
|
2779
|
+
return;
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
throw new Error(`[MCP:${config.name}] Failed to connect tools: ${getErrorMessage(error)}`);
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
async function createMcpToolsets(configs, options = {}) {
|
|
2786
|
+
const connected = [];
|
|
2787
|
+
try {
|
|
2788
|
+
for (const config of configs) {
|
|
2789
|
+
const result = await mcpServerToTools(config);
|
|
2790
|
+
connected.push(result);
|
|
2791
|
+
}
|
|
2792
|
+
} catch (error) {
|
|
2793
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2794
|
+
throw error;
|
|
2795
|
+
}
|
|
2796
|
+
const combinedTools = {};
|
|
2797
|
+
const serverToolNames = {};
|
|
2798
|
+
const collisionStrategy = options.onNameCollision ?? "overwrite";
|
|
2799
|
+
try {
|
|
2800
|
+
for (const result of connected) {
|
|
2801
|
+
for (const [serverName, toolNames] of Object.entries(result.serverToolNames)) {
|
|
2802
|
+
serverToolNames[serverName] = toolNames;
|
|
2803
|
+
}
|
|
2804
|
+
for (const [toolName, tool2] of Object.entries(result.tools)) {
|
|
2805
|
+
const hasCollision = combinedTools[toolName] !== undefined;
|
|
2806
|
+
if (hasCollision && collisionStrategy === "error") {
|
|
2807
|
+
throw new Error(`Duplicate MCP tool name "${toolName}" detected. Use "toolPrefix" or set onNameCollision to "overwrite".`);
|
|
2808
|
+
}
|
|
2809
|
+
combinedTools[toolName] = tool2;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
} catch (error) {
|
|
2813
|
+
await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2814
|
+
throw error;
|
|
2815
|
+
}
|
|
2816
|
+
return {
|
|
2817
|
+
tools: combinedTools,
|
|
2818
|
+
serverToolNames,
|
|
2819
|
+
cleanup: async () => {
|
|
2820
|
+
const cleanupResults = await Promise.allSettled(connected.map((result) => result.cleanup()));
|
|
2821
|
+
const failures = cleanupResults.filter((result) => result.status === "rejected");
|
|
2822
|
+
if (failures.length > 0) {
|
|
2823
|
+
throw new Error(`Failed to cleanup ${failures.length} MCP client connection(s).`);
|
|
2824
|
+
}
|
|
2825
|
+
}
|
|
2826
|
+
};
|
|
2827
|
+
}
|
|
2828
|
+
var init_mcp_client = __esm(() => {
|
|
2829
|
+
init_mcp_client_helpers();
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2832
|
+
// src/tools/memory-tools.ts
|
|
2833
|
+
import { anthropic } from "@ai-sdk/anthropic";
|
|
2834
|
+
function createAnthropicMemoryTool(store) {
|
|
2835
|
+
const memory = anthropic.tools.memory_20250818({
|
|
2836
|
+
execute: async (action) => {
|
|
2837
|
+
switch (action.command) {
|
|
2838
|
+
case "view":
|
|
2839
|
+
return store.view(action.path ?? "/memories", action.view_range);
|
|
2840
|
+
case "create":
|
|
2841
|
+
return store.create(action.path ?? "/memories/untitled", action.file_text ?? "");
|
|
2842
|
+
case "str_replace":
|
|
2843
|
+
return store.strReplace(action.path ?? "/memories", action.old_str ?? "", action.new_str ?? "");
|
|
2844
|
+
case "insert":
|
|
2845
|
+
return store.insert(action.path ?? "/memories", action.insert_line ?? 0, action.insert_text ?? "");
|
|
2846
|
+
case "delete":
|
|
2847
|
+
return store.delete(action.path ?? "/memories");
|
|
2848
|
+
case "rename":
|
|
2849
|
+
return store.rename(action.old_path ?? "/memories", action.new_path ?? "/memories");
|
|
2850
|
+
default:
|
|
2851
|
+
return `Unknown command: ${action.command}`;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2855
|
+
return memory;
|
|
2856
|
+
}
|
|
2857
|
+
var init_memory_tools = () => {};
|
|
2858
|
+
|
|
2859
|
+
// src/schema/json-schema-to-zod.ts
|
|
2860
|
+
import { z as z2 } from "zod";
|
|
2861
|
+
function jsonSchemaToZod(schema) {
|
|
2862
|
+
const s = schema;
|
|
2863
|
+
const makeNullable = (zodSchema) => {
|
|
2864
|
+
return s.nullable ? z2.union([zodSchema, z2.null()]) : zodSchema;
|
|
2865
|
+
};
|
|
2866
|
+
if (s.const !== undefined) {
|
|
2867
|
+
return z2.literal(s.const);
|
|
2868
|
+
}
|
|
2869
|
+
if (s.enum) {
|
|
2870
|
+
const values = s.enum;
|
|
2871
|
+
return makeNullable(z2.enum(values.map(String)));
|
|
2872
|
+
}
|
|
2873
|
+
if (s.anyOf && s.anyOf.length > 0) {
|
|
2874
|
+
const schemas = s.anyOf.map((sub) => jsonSchemaToZod(sub));
|
|
2875
|
+
if (schemas.length === 1)
|
|
2876
|
+
return schemas[0] ?? z2.unknown();
|
|
2877
|
+
return z2.union([
|
|
2878
|
+
schemas[0] ?? z2.unknown(),
|
|
2879
|
+
schemas[1] ?? z2.unknown(),
|
|
2880
|
+
...schemas.slice(2)
|
|
2881
|
+
]);
|
|
2882
|
+
}
|
|
2883
|
+
if (s.oneOf && s.oneOf.length > 0) {
|
|
2884
|
+
const schemas = s.oneOf.map((sub) => jsonSchemaToZod(sub));
|
|
2885
|
+
if (schemas.length === 1)
|
|
2886
|
+
return schemas[0] ?? z2.unknown();
|
|
2887
|
+
return z2.union([
|
|
2888
|
+
schemas[0] ?? z2.unknown(),
|
|
2889
|
+
schemas[1] ?? z2.unknown(),
|
|
2890
|
+
...schemas.slice(2)
|
|
2891
|
+
]);
|
|
2892
|
+
}
|
|
2893
|
+
if (s.allOf && s.allOf.length > 0) {
|
|
2894
|
+
const schemas = s.allOf.map((sub) => jsonSchemaToZod(sub));
|
|
2895
|
+
return schemas.reduce((acc, curr) => z2.intersection(acc, curr));
|
|
2896
|
+
}
|
|
2897
|
+
switch (s.type) {
|
|
2898
|
+
case "string":
|
|
2899
|
+
return makeNullable(buildStringSchema(s));
|
|
2900
|
+
case "number":
|
|
2901
|
+
case "integer":
|
|
2902
|
+
return makeNullable(buildNumberSchema(s));
|
|
2903
|
+
case "boolean":
|
|
2904
|
+
return makeNullable(z2.boolean());
|
|
2905
|
+
case "null":
|
|
2906
|
+
return z2.null();
|
|
2907
|
+
case "array":
|
|
2908
|
+
return makeNullable(buildArraySchema(s));
|
|
2909
|
+
case "object":
|
|
2910
|
+
return makeNullable(buildObjectSchema(s));
|
|
2911
|
+
default:
|
|
2912
|
+
return z2.unknown();
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
function buildStringSchema(schema) {
|
|
2916
|
+
let zodSchema = z2.string();
|
|
2917
|
+
if (schema.description) {
|
|
2918
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
2919
|
+
}
|
|
2920
|
+
switch (schema.format) {
|
|
2921
|
+
case "email":
|
|
2922
|
+
zodSchema = zodSchema.email();
|
|
2923
|
+
break;
|
|
2924
|
+
case "uri":
|
|
2925
|
+
case "url":
|
|
2926
|
+
zodSchema = zodSchema.url();
|
|
2927
|
+
break;
|
|
2928
|
+
case "uuid":
|
|
2929
|
+
zodSchema = zodSchema.uuid();
|
|
2930
|
+
break;
|
|
2931
|
+
case "date-time":
|
|
2932
|
+
zodSchema = zodSchema.datetime();
|
|
2933
|
+
break;
|
|
2934
|
+
case "date":
|
|
2935
|
+
zodSchema = zodSchema.date();
|
|
2936
|
+
break;
|
|
2937
|
+
}
|
|
2938
|
+
if (schema.minLength !== undefined) {
|
|
2939
|
+
zodSchema = zodSchema.min(schema.minLength);
|
|
2940
|
+
}
|
|
2941
|
+
if (schema.maxLength !== undefined) {
|
|
2942
|
+
zodSchema = zodSchema.max(schema.maxLength);
|
|
2943
|
+
}
|
|
2944
|
+
if (schema.pattern) {
|
|
2945
|
+
zodSchema = zodSchema.regex(new RegExp(schema.pattern));
|
|
2946
|
+
}
|
|
2947
|
+
return zodSchema;
|
|
2948
|
+
}
|
|
2949
|
+
function buildNumberSchema(schema) {
|
|
2950
|
+
let zodSchema = schema.type === "integer" ? z2.number().int() : z2.number();
|
|
2951
|
+
if (schema.description) {
|
|
2952
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
2953
|
+
}
|
|
2954
|
+
if (schema.minimum !== undefined) {
|
|
2955
|
+
zodSchema = zodSchema.min(schema.minimum);
|
|
2956
|
+
}
|
|
2957
|
+
if (schema.maximum !== undefined) {
|
|
2958
|
+
zodSchema = zodSchema.max(schema.maximum);
|
|
2959
|
+
}
|
|
2960
|
+
return zodSchema;
|
|
2961
|
+
}
|
|
2962
|
+
function buildArraySchema(schema) {
|
|
2963
|
+
const itemsSchema = schema.items ? jsonSchemaToZod(schema.items) : z2.unknown();
|
|
2964
|
+
let zodSchema = z2.array(itemsSchema);
|
|
2965
|
+
if (schema.description) {
|
|
2966
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
2967
|
+
}
|
|
2968
|
+
return zodSchema;
|
|
2969
|
+
}
|
|
2970
|
+
function buildObjectSchema(schema) {
|
|
2971
|
+
const properties = schema.properties ?? {};
|
|
2972
|
+
const required = new Set(schema.required ?? []);
|
|
2973
|
+
const shape = {};
|
|
2974
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
2975
|
+
const zodProp = jsonSchemaToZod(propSchema);
|
|
2976
|
+
shape[key] = required.has(key) ? zodProp : zodProp.optional();
|
|
2977
|
+
}
|
|
2978
|
+
let zodSchema = z2.object(shape);
|
|
2979
|
+
if (schema.description) {
|
|
2980
|
+
zodSchema = zodSchema.describe(schema.description);
|
|
2981
|
+
}
|
|
2982
|
+
return zodSchema;
|
|
2983
|
+
}
|
|
2984
|
+
function jsonSchemaToZodSafe(schema) {
|
|
2985
|
+
if (!schema || Object.keys(schema).length === 0) {
|
|
2986
|
+
return z2.object({});
|
|
2987
|
+
}
|
|
2988
|
+
return jsonSchemaToZod(schema);
|
|
2989
|
+
}
|
|
2990
|
+
var init_json_schema_to_zod = () => {};
|
|
2991
|
+
|
|
2992
|
+
// src/tools/operation-tool-handler.ts
|
|
2993
|
+
function toolCtxToHandlerCtx(ctx) {
|
|
2994
|
+
return {
|
|
2995
|
+
traceId: ctx.metadata?.traceId,
|
|
2996
|
+
organizationId: ctx.tenantId ?? null,
|
|
2997
|
+
userId: ctx.actorId ?? null,
|
|
2998
|
+
actor: ctx.actorId ? "user" : "anonymous",
|
|
2999
|
+
channel: "agent",
|
|
3000
|
+
roles: []
|
|
3001
|
+
};
|
|
3002
|
+
}
|
|
3003
|
+
function createOperationToolHandler(registry, operationRef) {
|
|
3004
|
+
return async (input, context) => {
|
|
3005
|
+
const handlerCtx = toolCtxToHandlerCtx(context);
|
|
3006
|
+
const result = await registry.execute(operationRef.key, operationRef.version, input ?? {}, handlerCtx);
|
|
3007
|
+
return result;
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
// src/tools/subagent-tool.ts
|
|
3012
|
+
import { readUIMessageStream, tool as tool2 } from "ai";
|
|
3013
|
+
import { z as z3 } from "zod";
|
|
3014
|
+
function toReadableStream(iterable) {
|
|
3015
|
+
if (iterable instanceof ReadableStream) {
|
|
3016
|
+
return iterable;
|
|
3017
|
+
}
|
|
3018
|
+
return new ReadableStream({
|
|
3019
|
+
async start(controller) {
|
|
3020
|
+
try {
|
|
3021
|
+
for await (const chunk of iterable) {
|
|
3022
|
+
controller.enqueue(chunk);
|
|
3023
|
+
}
|
|
3024
|
+
} finally {
|
|
3025
|
+
controller.close();
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
}
|
|
3030
|
+
function createSubagentTool(options) {
|
|
3031
|
+
const {
|
|
3032
|
+
subagent,
|
|
3033
|
+
description = "Research a topic or question in depth.",
|
|
3034
|
+
taskParam = "task",
|
|
3035
|
+
toModelSummary = true,
|
|
3036
|
+
passConversationHistory = false
|
|
3037
|
+
} = options;
|
|
3038
|
+
const inputSchema = z3.object({
|
|
3039
|
+
[taskParam]: z3.string().describe("The research task to complete")
|
|
3040
|
+
});
|
|
3041
|
+
const execute = async function* (input, options2) {
|
|
3042
|
+
const task = String(input[taskParam] ?? input.task ?? "");
|
|
3043
|
+
const { abortSignal, messages } = options2 ?? {};
|
|
3044
|
+
if (passConversationHistory && messages && messages.length > 0 && typeof subagent.generate === "function") {
|
|
3045
|
+
const result2 = await subagent.generate({
|
|
3046
|
+
messages: [...messages, { role: "user", content: task }],
|
|
3047
|
+
abortSignal
|
|
3048
|
+
});
|
|
3049
|
+
yield { parts: [{ type: "text", text: result2.text }] };
|
|
3050
|
+
return;
|
|
3051
|
+
}
|
|
3052
|
+
const result = await subagent.stream({
|
|
3053
|
+
prompt: task,
|
|
3054
|
+
abortSignal
|
|
3055
|
+
});
|
|
3056
|
+
const uiStream = result.toUIMessageStream();
|
|
3057
|
+
const stream = toReadableStream(uiStream);
|
|
3058
|
+
for await (const message of readUIMessageStream({
|
|
3059
|
+
stream
|
|
3060
|
+
})) {
|
|
3061
|
+
yield message;
|
|
3062
|
+
}
|
|
3063
|
+
};
|
|
3064
|
+
const toolOptions = {
|
|
3065
|
+
description,
|
|
3066
|
+
inputSchema,
|
|
3067
|
+
execute,
|
|
3068
|
+
...toModelSummary && {
|
|
3069
|
+
toModelOutput: ({
|
|
3070
|
+
output
|
|
3071
|
+
}) => {
|
|
3072
|
+
const parts = output?.parts;
|
|
3073
|
+
if (!Array.isArray(parts)) {
|
|
3074
|
+
return { type: "text", value: "Task completed." };
|
|
3075
|
+
}
|
|
3076
|
+
const lastTextPart = [...parts].reverse().find((p) => p?.type === "text");
|
|
3077
|
+
return {
|
|
3078
|
+
type: "text",
|
|
3079
|
+
value: lastTextPart?.text ?? "Task completed."
|
|
3080
|
+
};
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
return tool2(toolOptions);
|
|
3085
|
+
}
|
|
3086
|
+
var init_subagent_tool = () => {};
|
|
3087
|
+
|
|
3088
|
+
// src/tools/tool-adapter.ts
|
|
3089
|
+
import { tool as tool3 } from "ai";
|
|
3090
|
+
function isAsyncGenerator(value) {
|
|
3091
|
+
return typeof value === "object" && value !== null && typeof value.next === "function" && typeof value[Symbol.asyncIterator] === "function";
|
|
3092
|
+
}
|
|
3093
|
+
function specToolToAISDKTool(specTool, handler, context = {}, effectiveInputSchema, operationSpec) {
|
|
3094
|
+
let lastInvocationAt;
|
|
3095
|
+
const inputSchema = effectiveInputSchema ?? jsonSchemaToZodSafe(specTool.schema);
|
|
3096
|
+
const buildContext = (signal) => ({
|
|
3097
|
+
agentId: context.agentId ?? "unknown",
|
|
3098
|
+
sessionId: context.sessionId ?? "unknown",
|
|
3099
|
+
tenantId: context.tenantId,
|
|
3100
|
+
actorId: context.actorId,
|
|
3101
|
+
locale: context.locale,
|
|
3102
|
+
metadata: context.metadata,
|
|
3103
|
+
signal
|
|
3104
|
+
});
|
|
3105
|
+
return tool3({
|
|
3106
|
+
description: specTool.description ?? specTool.name,
|
|
3107
|
+
inputSchema,
|
|
3108
|
+
needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
|
|
3109
|
+
execute: async function* (input, options) {
|
|
3110
|
+
const now = Date.now();
|
|
3111
|
+
const cooldownMs = normalizeDuration(specTool.cooldownMs);
|
|
3112
|
+
if (cooldownMs && lastInvocationAt !== undefined) {
|
|
3113
|
+
const elapsed = now - lastInvocationAt;
|
|
3114
|
+
if (elapsed < cooldownMs) {
|
|
3115
|
+
const retryAfterMs = cooldownMs - elapsed;
|
|
3116
|
+
throw createToolExecutionError(`Tool "${specTool.name}" is cooling down. Retry in ${retryAfterMs}ms.`, "TOOL_COOLDOWN_ACTIVE", retryAfterMs);
|
|
3117
|
+
}
|
|
3118
|
+
}
|
|
3119
|
+
const timeoutMs = normalizeDuration(specTool.timeoutMs);
|
|
3120
|
+
const signal = options?.abortSignal ?? context.signal;
|
|
3121
|
+
const { signal: timeoutSignal, dispose } = createTimeoutSignal(signal, timeoutMs);
|
|
3122
|
+
try {
|
|
3123
|
+
const execution = handler(input, buildContext(timeoutSignal));
|
|
3124
|
+
if (isAsyncGenerator(execution)) {
|
|
3125
|
+
for await (const raw of execution) {
|
|
3126
|
+
const wrapped = wrapToolOutputForRendering(specTool, raw, operationSpec);
|
|
3127
|
+
yield typeof wrapped === "string" ? wrapped : wrapped;
|
|
3128
|
+
}
|
|
3129
|
+
} else {
|
|
3130
|
+
const raw = timeoutMs ? await withTimeout(Promise.resolve(execution), timeoutMs, specTool.name) : await Promise.resolve(execution);
|
|
3131
|
+
const wrapped = wrapToolOutputForRendering(specTool, raw, operationSpec);
|
|
3132
|
+
yield typeof wrapped === "string" ? wrapped : wrapped;
|
|
3133
|
+
}
|
|
3134
|
+
} finally {
|
|
3135
|
+
dispose();
|
|
3136
|
+
lastInvocationAt = Date.now();
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
});
|
|
3140
|
+
}
|
|
3141
|
+
function specToolsToAISDKTools(specTools, handlers, context = {}, options) {
|
|
3142
|
+
const tools = {};
|
|
3143
|
+
for (const specTool of specTools) {
|
|
3144
|
+
if (specTool.subagentRef && options?.subagentRegistry) {
|
|
3145
|
+
const subagent = options.subagentRegistry.get(specTool.subagentRef.agentId);
|
|
3146
|
+
if (!subagent) {
|
|
3147
|
+
throw new Error(`Subagent not found: ${specTool.subagentRef.agentId}. Register it in subagentRegistry.`);
|
|
3148
|
+
}
|
|
3149
|
+
if (specTool.requiresApproval === true || specTool.automationSafe === false) {
|
|
3150
|
+
console.warn(`[ContractSpec] Subagent tool "${specTool.name}" cannot use needsApproval. ` + `requiresApproval and automationSafe are ignored for subagent tools (AI SDK limitation). ` + `See https://ai-sdk.dev/docs/agents/subagents#no-tool-approvals-in-subagents`);
|
|
3151
|
+
}
|
|
3152
|
+
tools[specTool.name] = createSubagentTool({
|
|
3153
|
+
subagent,
|
|
3154
|
+
description: specTool.description ?? specTool.name,
|
|
3155
|
+
toModelSummary: specTool.subagentRef.toModelSummary ?? true,
|
|
3156
|
+
passConversationHistory: specTool.subagentRef.passConversationHistory
|
|
3157
|
+
});
|
|
3158
|
+
continue;
|
|
3159
|
+
}
|
|
3160
|
+
let handler;
|
|
3161
|
+
let effectiveInputSchema;
|
|
3162
|
+
let op;
|
|
3163
|
+
if (specTool.operationRef && options?.operationRegistry) {
|
|
3164
|
+
op = options.operationRegistry.get(specTool.operationRef.key, specTool.operationRef.version);
|
|
3165
|
+
if (!op) {
|
|
3166
|
+
throw new Error(`Operation not found: ${specTool.operationRef.key}${specTool.operationRef.version ? `.v${specTool.operationRef.version}` : ""}`);
|
|
3167
|
+
}
|
|
3168
|
+
handler = createOperationToolHandler(options.operationRegistry, specTool.operationRef);
|
|
3169
|
+
effectiveInputSchema = op.io.input?.getZod?.();
|
|
3170
|
+
} else {
|
|
3171
|
+
const manualHandler = handlers.get(specTool.name);
|
|
3172
|
+
if (!manualHandler) {
|
|
3173
|
+
if (specTool.subagentRef) {
|
|
3174
|
+
throw new Error(`Subagent tool "${specTool.name}" requires subagentRegistry. Pass subagentRegistry in ContractSpecAgentConfig.`);
|
|
3175
|
+
}
|
|
3176
|
+
throw new Error(createAgentI18n(context.locale).t("error.missingToolHandler", {
|
|
3177
|
+
name: specTool.name
|
|
3178
|
+
}));
|
|
3179
|
+
}
|
|
3180
|
+
handler = manualHandler;
|
|
3181
|
+
}
|
|
3182
|
+
tools[specTool.name] = specToolToAISDKTool(specTool, handler, context, effectiveInputSchema, op);
|
|
3183
|
+
}
|
|
3184
|
+
return tools;
|
|
3185
|
+
}
|
|
3186
|
+
function createToolHandler(handler) {
|
|
3187
|
+
return (input, context) => {
|
|
3188
|
+
return handler(input, context);
|
|
3189
|
+
};
|
|
3190
|
+
}
|
|
3191
|
+
function buildToolHandlers(handlersObj) {
|
|
3192
|
+
return new Map(Object.entries(handlersObj));
|
|
3193
|
+
}
|
|
3194
|
+
function wrapToolOutputForRendering(specTool, result, operationSpec) {
|
|
3195
|
+
const presentation = specTool.outputPresentation ?? operationSpec?.outputPresentation;
|
|
3196
|
+
const form = specTool.outputForm ?? operationSpec?.outputForm;
|
|
3197
|
+
const dataView = specTool.outputDataView ?? operationSpec?.outputDataView;
|
|
3198
|
+
if (presentation) {
|
|
3199
|
+
return {
|
|
3200
|
+
presentationKey: presentation.key,
|
|
3201
|
+
data: result
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
if (form) {
|
|
3205
|
+
return {
|
|
3206
|
+
formKey: form.key,
|
|
3207
|
+
defaultValues: typeof result === "object" && result !== null ? result : {}
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
if (dataView) {
|
|
3211
|
+
return {
|
|
3212
|
+
dataViewKey: dataView.key,
|
|
3213
|
+
items: Array.isArray(result) ? result : result != null ? [result] : []
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
return result;
|
|
3217
|
+
}
|
|
3218
|
+
function normalizeDuration(value) {
|
|
3219
|
+
if (value === undefined) {
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
if (!Number.isFinite(value)) {
|
|
3223
|
+
return;
|
|
3224
|
+
}
|
|
3225
|
+
if (value <= 0) {
|
|
3226
|
+
return;
|
|
3227
|
+
}
|
|
3228
|
+
return Math.round(value);
|
|
3229
|
+
}
|
|
3230
|
+
function withTimeout(execution, timeoutMs, toolName) {
|
|
3231
|
+
return new Promise((resolve, reject) => {
|
|
3232
|
+
const timeoutHandle = setTimeout(() => {
|
|
3233
|
+
reject(createToolExecutionError(`Tool "${toolName}" timed out after ${timeoutMs}ms.`, "TOOL_EXECUTION_TIMEOUT"));
|
|
3234
|
+
}, timeoutMs);
|
|
3235
|
+
execution.then((result) => {
|
|
3236
|
+
clearTimeout(timeoutHandle);
|
|
3237
|
+
resolve(result);
|
|
3238
|
+
}).catch((error) => {
|
|
3239
|
+
clearTimeout(timeoutHandle);
|
|
3240
|
+
reject(error);
|
|
3241
|
+
});
|
|
3242
|
+
});
|
|
3243
|
+
}
|
|
3244
|
+
function createTimeoutSignal(signal, timeoutMs) {
|
|
3245
|
+
const controller = new AbortController;
|
|
3246
|
+
const abortFromSource = () => controller.abort();
|
|
3247
|
+
if (signal) {
|
|
3248
|
+
if (signal.aborted) {
|
|
3249
|
+
controller.abort();
|
|
3250
|
+
} else {
|
|
3251
|
+
signal.addEventListener("abort", abortFromSource);
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
const timeoutHandle = timeoutMs !== undefined ? setTimeout(() => {
|
|
3255
|
+
controller.abort();
|
|
3256
|
+
}, timeoutMs) : undefined;
|
|
3257
|
+
return {
|
|
3258
|
+
signal: controller.signal,
|
|
3259
|
+
dispose: () => {
|
|
3260
|
+
if (timeoutHandle !== undefined) {
|
|
3261
|
+
clearTimeout(timeoutHandle);
|
|
3262
|
+
}
|
|
3263
|
+
if (signal) {
|
|
3264
|
+
signal.removeEventListener("abort", abortFromSource);
|
|
3265
|
+
}
|
|
3266
|
+
}
|
|
3267
|
+
};
|
|
3268
|
+
}
|
|
3269
|
+
function createToolExecutionError(message, code, retryAfterMs) {
|
|
3270
|
+
return Object.assign(new Error(message), {
|
|
3271
|
+
code,
|
|
3272
|
+
kind: "retryable",
|
|
3273
|
+
retryAfterMs
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
var init_tool_adapter = __esm(() => {
|
|
3277
|
+
init_i18n();
|
|
3278
|
+
init_json_schema_to_zod();
|
|
3279
|
+
init_subagent_tool();
|
|
3280
|
+
});
|
|
3281
|
+
|
|
3282
|
+
// src/telemetry/posthog.ts
|
|
3283
|
+
var exports_posthog = {};
|
|
3284
|
+
__export(exports_posthog, {
|
|
3285
|
+
createPostHogTracedModel: () => createPostHogTracedModel,
|
|
3286
|
+
createPostHogTelemetryCollector: () => createPostHogTelemetryCollector,
|
|
3287
|
+
createCompositeTelemetryCollector: () => createCompositeTelemetryCollector,
|
|
3288
|
+
PostHogTelemetryCollector: () => PostHogTelemetryCollector,
|
|
3289
|
+
CompositeTelemetryCollector: () => CompositeTelemetryCollector
|
|
3290
|
+
});
|
|
3291
|
+
async function createPostHogTracedModel(model, config, overrides) {
|
|
3292
|
+
const { withTracing } = await importPostHogAI();
|
|
3293
|
+
const phClient = await resolvePostHogClient(config);
|
|
3294
|
+
const tracingOptions = {
|
|
3295
|
+
...config.defaults,
|
|
3296
|
+
...overrides
|
|
3297
|
+
};
|
|
3298
|
+
return withTracing(model, phClient, tracingOptions);
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
class PostHogTelemetryCollector {
|
|
3302
|
+
phClient;
|
|
3303
|
+
config;
|
|
3304
|
+
initPromise;
|
|
3305
|
+
constructor(config) {
|
|
3306
|
+
this.config = config;
|
|
3307
|
+
this.phClient = config.client;
|
|
3308
|
+
}
|
|
3309
|
+
async collect(sample) {
|
|
3310
|
+
const client = await this.getClient();
|
|
3311
|
+
const metadata = asRecord(sample.metadata);
|
|
3312
|
+
const distinctId = this.config.defaults?.posthogDistinctId ?? asString(metadata["actorId"]) ?? "system";
|
|
3313
|
+
const traceId = asString(metadata["traceId"]) ?? this.config.defaults?.posthogTraceId;
|
|
3314
|
+
const sessionId = asString(metadata["sessionId"]);
|
|
3315
|
+
const telemetryEvent = asString(metadata["telemetryEvent"]);
|
|
3316
|
+
const event = telemetryEvent === "span" ? "$ai_span" : "$ai_generation";
|
|
3317
|
+
const tokenUsage = metadata["tokenUsage"];
|
|
3318
|
+
const totalUsage = metadata["totalUsage"];
|
|
3319
|
+
const errorMessage = asString(metadata["errorMessage"]);
|
|
3320
|
+
client.capture({
|
|
3321
|
+
distinctId,
|
|
3322
|
+
event,
|
|
3323
|
+
properties: {
|
|
3324
|
+
$ai_model: asString(metadata["responseModelId"]) ?? sample.operation.name,
|
|
3325
|
+
$ai_provider: asString(metadata["provider"]) ?? "contractspec",
|
|
3326
|
+
$ai_latency: sample.durationMs / 1000,
|
|
3327
|
+
$ai_is_error: !sample.success,
|
|
3328
|
+
$ai_error: !sample.success ? errorMessage : undefined,
|
|
3329
|
+
$ai_trace_id: traceId,
|
|
3330
|
+
$ai_session_id: sessionId,
|
|
3331
|
+
$ai_span_id: asString(metadata["spanId"]),
|
|
3332
|
+
$ai_parent_id: asString(metadata["parentSpanId"]),
|
|
3333
|
+
$ai_span_name: asString(metadata["spanName"]) ?? sample.operation.name,
|
|
3334
|
+
...tokenUsage ? mapTokenUsage(tokenUsage) : {},
|
|
3335
|
+
...totalUsage ? mapTotalUsage(totalUsage) : {},
|
|
3336
|
+
$ai_http_status: asNumber(metadata["httpStatus"]),
|
|
3337
|
+
$ai_request_url: asString(metadata["requestUrl"]),
|
|
3338
|
+
$ai_base_url: asString(metadata["baseUrl"]),
|
|
3339
|
+
...this.config.defaults?.posthogProperties,
|
|
3340
|
+
contractspec_operation: sample.operation.name,
|
|
3341
|
+
contractspec_version: sample.operation.version,
|
|
3342
|
+
contractspec_agent_id: asString(metadata["agentId"]),
|
|
3343
|
+
contractspec_tenant_id: asString(metadata["tenantId"]),
|
|
3344
|
+
contractspec_actor_id: asString(metadata["actorId"]),
|
|
3345
|
+
contractspec_step_index: asNumber(metadata["stepIndex"]),
|
|
3346
|
+
contractspec_step_started_at: asDateIso(metadata["stepStartedAt"]),
|
|
3347
|
+
contractspec_finish_reason: asString(metadata["finishReason"]),
|
|
3348
|
+
contractspec_finish_reason_raw: asString(metadata["rawFinishReason"]),
|
|
3349
|
+
contractspec_tool_count: asNumber(metadata["toolCallCount"]),
|
|
3350
|
+
contractspec_tool_name: asString(metadata["toolName"]),
|
|
3351
|
+
contractspec_tool_call_args: metadata["toolCallArgs"],
|
|
3352
|
+
contractspec_tool_result_output: metadata["toolResultOutput"],
|
|
3353
|
+
contractspec_provider_metadata: metadata["providerMetadata"],
|
|
3354
|
+
contractspec_step_warnings: metadata["warnings"],
|
|
3355
|
+
contractspec_response_id: asString(metadata["responseId"]),
|
|
3356
|
+
contractspec_response_model_id: asString(metadata["responseModelId"]),
|
|
3357
|
+
contractspec_response_timestamp: asDateIso(metadata["responseTimestamp"]),
|
|
3358
|
+
contractspec_response_headers: metadata["responseHeaders"],
|
|
3359
|
+
contractspec_response_body: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["responseBody"],
|
|
3360
|
+
contractspec_response_messages: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["responseMessages"],
|
|
3361
|
+
contractspec_request_headers: metadata["requestHeaders"],
|
|
3362
|
+
contractspec_request_body: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["requestBody"],
|
|
3363
|
+
contractspec_step_text: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["stepText"],
|
|
3364
|
+
contractspec_step_reasoning_text: this.config.defaults?.posthogPrivacyMode ? undefined : metadata["stepReasoningText"]
|
|
3365
|
+
},
|
|
3366
|
+
groups: this.config.defaults?.posthogGroups
|
|
3367
|
+
});
|
|
3368
|
+
}
|
|
3369
|
+
async shutdown() {
|
|
3370
|
+
if (this.phClient?.shutdown) {
|
|
3371
|
+
await this.phClient.shutdown();
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
async getClient() {
|
|
3375
|
+
if (this.phClient)
|
|
3376
|
+
return this.phClient;
|
|
3377
|
+
if (!this.initPromise) {
|
|
3378
|
+
this.initPromise = resolvePostHogClient(this.config).then((client) => {
|
|
3379
|
+
this.phClient = client;
|
|
3380
|
+
return client;
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
return this.initPromise;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
function createPostHogTelemetryCollector(config) {
|
|
3387
|
+
return new PostHogTelemetryCollector(config);
|
|
3388
|
+
}
|
|
3389
|
+
|
|
3390
|
+
class CompositeTelemetryCollector {
|
|
3391
|
+
collectors;
|
|
3392
|
+
constructor(collectors) {
|
|
3393
|
+
this.collectors = collectors;
|
|
3394
|
+
}
|
|
3395
|
+
async collect(sample) {
|
|
3396
|
+
await Promise.all(this.collectors.map((c) => c.collect(sample)));
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
function createCompositeTelemetryCollector(collectors) {
|
|
3400
|
+
return new CompositeTelemetryCollector(collectors);
|
|
3401
|
+
}
|
|
3402
|
+
async function importPostHogAI() {
|
|
3403
|
+
try {
|
|
3404
|
+
return await runtimeImport("@posthog/ai");
|
|
3405
|
+
} catch {
|
|
3406
|
+
throw new Error(createAgentI18n().t("error.telemetry.posthogAiRequired"));
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
async function resolvePostHogClient(config) {
|
|
3410
|
+
if (config.client)
|
|
3411
|
+
return config.client;
|
|
3412
|
+
if (!config.apiKey) {
|
|
3413
|
+
throw new Error(createAgentI18n().t("error.telemetry.posthogClientOrKeyRequired"));
|
|
3414
|
+
}
|
|
3415
|
+
try {
|
|
3416
|
+
const { PostHog } = await runtimeImport("posthog-node");
|
|
3417
|
+
return new PostHog(config.apiKey, {
|
|
3418
|
+
host: config.host ?? "https://us.i.posthog.com"
|
|
3419
|
+
});
|
|
3420
|
+
} catch {
|
|
3421
|
+
throw new Error(createAgentI18n().t("error.telemetry.posthogNodeRequired"));
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
function mapTokenUsage(usage) {
|
|
3425
|
+
const usageRecord = asRecord(usage);
|
|
3426
|
+
const inputTokenDetails = asRecord(usageRecord["inputTokenDetails"]);
|
|
3427
|
+
const outputTokenDetails = asRecord(usageRecord["outputTokenDetails"]);
|
|
3428
|
+
return {
|
|
3429
|
+
$ai_input_tokens: asNumber(usageRecord["inputTokens"]) ?? asNumber(usageRecord["promptTokens"]),
|
|
3430
|
+
$ai_output_tokens: asNumber(usageRecord["outputTokens"]) ?? asNumber(usageRecord["completionTokens"]),
|
|
3431
|
+
$ai_reasoning_tokens: asNumber(outputTokenDetails["reasoningTokens"]) ?? asNumber(usageRecord["reasoningTokens"]),
|
|
3432
|
+
$ai_cache_read_input_tokens: asNumber(inputTokenDetails["cacheReadTokens"]) ?? asNumber(usageRecord["cachedInputTokens"]),
|
|
3433
|
+
$ai_cache_creation_input_tokens: asNumber(inputTokenDetails["cacheWriteTokens"]),
|
|
3434
|
+
$ai_usage: maybeRecord(usageRecord["raw"]) ?? usageRecord
|
|
3435
|
+
};
|
|
3436
|
+
}
|
|
3437
|
+
function mapTotalUsage(usage) {
|
|
3438
|
+
const usageRecord = asRecord(usage);
|
|
3439
|
+
return {
|
|
3440
|
+
contractspec_total_input_tokens: asNumber(usageRecord["inputTokens"]),
|
|
3441
|
+
contractspec_total_output_tokens: asNumber(usageRecord["outputTokens"]),
|
|
3442
|
+
contractspec_total_tokens: asNumber(usageRecord["totalTokens"]),
|
|
3443
|
+
contractspec_total_usage_raw: maybeRecord(usageRecord["raw"]) ?? usageRecord
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
function asRecord(value) {
|
|
3447
|
+
return value && typeof value === "object" ? value : {};
|
|
3448
|
+
}
|
|
3449
|
+
function maybeRecord(value) {
|
|
3450
|
+
return value && typeof value === "object" ? value : undefined;
|
|
3451
|
+
}
|
|
3452
|
+
function asString(value) {
|
|
3453
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
3454
|
+
}
|
|
3455
|
+
function asNumber(value) {
|
|
3456
|
+
return typeof value === "number" ? value : undefined;
|
|
3457
|
+
}
|
|
3458
|
+
function asDateIso(value) {
|
|
3459
|
+
if (value instanceof Date)
|
|
3460
|
+
return value.toISOString();
|
|
3461
|
+
return;
|
|
3462
|
+
}
|
|
3463
|
+
var runtimeImport;
|
|
3464
|
+
var init_posthog = __esm(() => {
|
|
3465
|
+
init_i18n();
|
|
3466
|
+
runtimeImport = new Function("specifier", "return import(specifier)");
|
|
3467
|
+
});
|
|
3468
|
+
|
|
3469
|
+
// src/agent/contract-spec-agent.ts
|
|
3470
|
+
var exports_contract_spec_agent = {};
|
|
3471
|
+
__export(exports_contract_spec_agent, {
|
|
3472
|
+
ContractSpecAgent: () => ContractSpecAgent
|
|
3473
|
+
});
|
|
3474
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3475
|
+
import {
|
|
3476
|
+
agentKey
|
|
3477
|
+
} from "@contractspec/lib.contracts-spec/agent";
|
|
3478
|
+
import {
|
|
3479
|
+
stepCountIs,
|
|
3480
|
+
Experimental_Agent as ToolLoopAgent
|
|
3481
|
+
} from "ai";
|
|
3482
|
+
import * as z4 from "zod";
|
|
3483
|
+
|
|
3484
|
+
class ContractSpecAgent {
|
|
3485
|
+
version = "agent-v1";
|
|
3486
|
+
id;
|
|
3487
|
+
spec;
|
|
3488
|
+
tools;
|
|
3489
|
+
config;
|
|
3490
|
+
instructions;
|
|
3491
|
+
mcpCleanup;
|
|
3492
|
+
activeStepContexts = new Map;
|
|
3493
|
+
constructor(config, instructions, tools, mcpCleanup) {
|
|
3494
|
+
this.config = config;
|
|
3495
|
+
this.spec = config.spec;
|
|
3496
|
+
this.id = agentKey(config.spec.meta);
|
|
3497
|
+
this.tools = tools;
|
|
3498
|
+
this.instructions = instructions;
|
|
3499
|
+
this.mcpCleanup = mcpCleanup;
|
|
3500
|
+
}
|
|
3501
|
+
static async create(config) {
|
|
3502
|
+
const effectiveConfig = {
|
|
3503
|
+
...config,
|
|
3504
|
+
approvalWorkflow: config.approvalWorkflow ?? (config.spec.policy?.escalation?.approvalWorkflow ? createApprovalWorkflow() : undefined)
|
|
3505
|
+
};
|
|
3506
|
+
let mcpToolset = null;
|
|
3507
|
+
if ((effectiveConfig.mcpServers?.length ?? 0) > 0) {
|
|
3508
|
+
mcpToolset = await createMcpToolsets(effectiveConfig.mcpServers ?? [], {
|
|
3509
|
+
onNameCollision: "error"
|
|
3510
|
+
});
|
|
3511
|
+
}
|
|
3512
|
+
try {
|
|
3513
|
+
const instructions = await injectStaticKnowledge(effectiveConfig.spec.instructions, effectiveConfig.spec.knowledge ?? [], effectiveConfig.knowledgeRetriever);
|
|
3514
|
+
const specTools = specToolsToAISDKTools(effectiveConfig.spec.tools, effectiveConfig.toolHandlers, { agentId: agentKey(effectiveConfig.spec.meta) }, {
|
|
3515
|
+
operationRegistry: effectiveConfig.operationRegistry,
|
|
3516
|
+
subagentRegistry: effectiveConfig.subagentRegistry
|
|
3517
|
+
});
|
|
3518
|
+
const knowledgeTool = effectiveConfig.knowledgeRetriever ? createKnowledgeQueryTool(effectiveConfig.knowledgeRetriever, effectiveConfig.spec.knowledge ?? []) : null;
|
|
3519
|
+
const memoryTool = effectiveConfig.spec.memoryTools?.provider === "anthropic" && effectiveConfig.agentMemoryStore ? createAnthropicMemoryTool(effectiveConfig.agentMemoryStore) : null;
|
|
3520
|
+
const reservedToolNames = new Set(Object.keys(specTools));
|
|
3521
|
+
if (knowledgeTool) {
|
|
3522
|
+
reservedToolNames.add("query_knowledge");
|
|
3523
|
+
}
|
|
3524
|
+
if (memoryTool) {
|
|
3525
|
+
reservedToolNames.add("memory");
|
|
3526
|
+
}
|
|
3527
|
+
const conflictingMcpTools = Object.keys(mcpToolset?.tools ?? {}).filter((toolName) => reservedToolNames.has(toolName));
|
|
3528
|
+
if (conflictingMcpTools.length > 0) {
|
|
3529
|
+
throw new Error(`MCP tools conflict with agent tools: ${conflictingMcpTools.join(", ")}. Configure MCP toolPrefix values to avoid collisions.`);
|
|
3530
|
+
}
|
|
3531
|
+
const tools = {
|
|
3532
|
+
...specTools,
|
|
3533
|
+
...knowledgeTool ? { query_knowledge: knowledgeTool } : {},
|
|
3534
|
+
...memoryTool ? { memory: memoryTool } : {},
|
|
3535
|
+
...mcpToolset?.tools ?? {},
|
|
3536
|
+
...effectiveConfig.additionalTools ?? {}
|
|
3537
|
+
};
|
|
3538
|
+
return new ContractSpecAgent(effectiveConfig, instructions, tools, mcpToolset?.cleanup);
|
|
3539
|
+
} catch (error) {
|
|
3540
|
+
if (mcpToolset) {
|
|
3541
|
+
await mcpToolset.cleanup().catch(() => {
|
|
3542
|
+
return;
|
|
3543
|
+
});
|
|
3544
|
+
}
|
|
3545
|
+
throw error;
|
|
3546
|
+
}
|
|
3547
|
+
}
|
|
3548
|
+
async cleanup() {
|
|
3549
|
+
if (!this.mcpCleanup) {
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
const cleanup = this.mcpCleanup;
|
|
3553
|
+
this.mcpCleanup = undefined;
|
|
3554
|
+
await cleanup();
|
|
3555
|
+
}
|
|
3556
|
+
async generate(params) {
|
|
3557
|
+
const sessionId = params.options?.sessionId ?? generateSessionId();
|
|
3558
|
+
const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID2();
|
|
3559
|
+
const workflowId = params.options?.workflowId ?? params.options?.metadata?.["workflowId"];
|
|
3560
|
+
const threadId = params.options?.threadId ?? params.options?.metadata?.["threadId"];
|
|
3561
|
+
const runtimeAdapter = this.resolveRuntimeAdapter();
|
|
3562
|
+
this.activeStepContexts.set(sessionId, {
|
|
3563
|
+
traceId,
|
|
3564
|
+
tenantId: params.options?.tenantId,
|
|
3565
|
+
actorId: params.options?.actorId,
|
|
3566
|
+
workflowId,
|
|
3567
|
+
threadId,
|
|
3568
|
+
stepIndex: 0,
|
|
3569
|
+
stepStartedAt: new Date
|
|
3570
|
+
});
|
|
3571
|
+
if (!params.messages?.length) {
|
|
3572
|
+
await this.ensureSession({
|
|
3573
|
+
sessionId,
|
|
3574
|
+
prompt: params.prompt ?? "",
|
|
3575
|
+
options: params.options,
|
|
3576
|
+
traceId
|
|
3577
|
+
});
|
|
3578
|
+
await this.runSessionMiddleware(sessionId, runtimeAdapter?.middleware?.beforeModel);
|
|
3579
|
+
}
|
|
3580
|
+
const model = await this.resolveModelForCall({
|
|
3581
|
+
sessionId,
|
|
3582
|
+
traceId,
|
|
3583
|
+
options: params.options
|
|
3584
|
+
});
|
|
3585
|
+
const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
|
|
3586
|
+
const inner = this.createInnerAgent(model, effectiveMaxSteps);
|
|
3587
|
+
const generateOptions = {
|
|
3588
|
+
abortSignal: params.signal,
|
|
3589
|
+
options: {
|
|
3590
|
+
tenantId: params.options?.tenantId,
|
|
3591
|
+
actorId: params.options?.actorId,
|
|
3592
|
+
sessionId,
|
|
3593
|
+
workflowId,
|
|
3594
|
+
threadId,
|
|
3595
|
+
metadata: params.options?.metadata
|
|
3596
|
+
}
|
|
3597
|
+
};
|
|
3598
|
+
let result;
|
|
3599
|
+
try {
|
|
3600
|
+
if (params.messages && params.messages.length > 0) {
|
|
3601
|
+
result = await inner.generate({
|
|
3602
|
+
messages: params.messages,
|
|
3603
|
+
...generateOptions
|
|
3604
|
+
});
|
|
3605
|
+
} else {
|
|
3606
|
+
const prompt = params.systemOverride && params.prompt ? `${this.instructions}
|
|
3607
|
+
|
|
3608
|
+
${params.systemOverride}
|
|
3609
|
+
|
|
3610
|
+
${params.prompt}` : params.prompt ?? "";
|
|
3611
|
+
result = await inner.generate({
|
|
3612
|
+
prompt,
|
|
3613
|
+
...generateOptions
|
|
3614
|
+
});
|
|
3615
|
+
}
|
|
3616
|
+
} catch (error) {
|
|
3617
|
+
const executionError = toAgentExecutionError(error);
|
|
3618
|
+
if (this.config.sessionStore) {
|
|
3619
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3620
|
+
status: "failed",
|
|
3621
|
+
traceId,
|
|
3622
|
+
workflowId,
|
|
3623
|
+
threadId,
|
|
3624
|
+
lastError: executionError
|
|
3625
|
+
});
|
|
3626
|
+
await this.syncSessionCheckpoint(sessionId);
|
|
3627
|
+
}
|
|
3628
|
+
await this.emitAgentEvent("agent.failed", {
|
|
3629
|
+
sessionId,
|
|
3630
|
+
tenantId: params.options?.tenantId,
|
|
3631
|
+
workflowId,
|
|
3632
|
+
traceId,
|
|
3633
|
+
metadata: {
|
|
3634
|
+
error: executionError.message,
|
|
3635
|
+
code: executionError.code ?? executionError.kind
|
|
3636
|
+
}
|
|
3637
|
+
});
|
|
3638
|
+
this.activeStepContexts.delete(sessionId);
|
|
3639
|
+
throw error;
|
|
3640
|
+
}
|
|
3641
|
+
if (!params.messages?.length) {
|
|
3642
|
+
await this.runSessionMiddleware(sessionId, runtimeAdapter?.middleware?.afterModel);
|
|
3643
|
+
}
|
|
3644
|
+
let pendingApproval = this.activeStepContexts.get(sessionId)?.pendingApproval;
|
|
3645
|
+
const escalationError = pendingApproval ? undefined : resolveEscalationError(this.spec, result.finishReason);
|
|
3646
|
+
if (this.config.sessionStore && result.text.trim().length > 0) {
|
|
3647
|
+
const currentSession = await this.config.sessionStore.get(sessionId);
|
|
3648
|
+
const lastMessage = currentSession?.messages.at(-1);
|
|
3649
|
+
const lastContent = lastMessage && "content" in lastMessage ? lastMessage.content : undefined;
|
|
3650
|
+
if (lastMessage?.role !== "assistant" || lastContent !== result.text) {
|
|
3651
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3652
|
+
role: "assistant",
|
|
3653
|
+
content: result.text
|
|
3654
|
+
});
|
|
3655
|
+
}
|
|
3656
|
+
}
|
|
3657
|
+
if (!pendingApproval && escalationError) {
|
|
3658
|
+
pendingApproval = await this.requestApproval(sessionId, {
|
|
3659
|
+
toolName: this.spec.policy?.escalation?.approvalWorkflow ?? "approval_required",
|
|
3660
|
+
toolCallId: `approval_${sessionId}`,
|
|
3661
|
+
args: {
|
|
3662
|
+
finishReason: result.finishReason,
|
|
3663
|
+
threshold: this.spec.policy?.escalation?.confidenceThreshold ?? this.spec.policy?.confidence?.min
|
|
3664
|
+
},
|
|
3665
|
+
reason: escalationError.message,
|
|
3666
|
+
error: escalationError
|
|
3667
|
+
});
|
|
3668
|
+
}
|
|
3669
|
+
const finalStatus = pendingApproval ? "escalated" : "completed";
|
|
3670
|
+
if (this.config.sessionStore) {
|
|
3671
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3672
|
+
status: finalStatus,
|
|
3673
|
+
traceId,
|
|
3674
|
+
workflowId,
|
|
3675
|
+
threadId,
|
|
3676
|
+
pendingApprovalRequestId: pendingApproval?.id,
|
|
3677
|
+
lastError: pendingApproval ? escalationError : undefined
|
|
3678
|
+
});
|
|
3679
|
+
await this.syncSessionCheckpoint(sessionId);
|
|
3680
|
+
}
|
|
3681
|
+
if (pendingApproval) {
|
|
3682
|
+
await this.emitAgentEvent("agent.escalated", {
|
|
3683
|
+
sessionId,
|
|
3684
|
+
tenantId: params.options?.tenantId,
|
|
3685
|
+
workflowId,
|
|
3686
|
+
traceId,
|
|
3687
|
+
metadata: {
|
|
3688
|
+
approvalRequestId: pendingApproval.id,
|
|
3689
|
+
reason: pendingApproval.reason
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
} else {
|
|
3693
|
+
await this.emitAgentEvent("agent.completed", {
|
|
3694
|
+
sessionId,
|
|
3695
|
+
tenantId: params.options?.tenantId,
|
|
3696
|
+
workflowId,
|
|
3697
|
+
traceId,
|
|
3698
|
+
metadata: {
|
|
3699
|
+
finishReason: result.finishReason
|
|
3700
|
+
}
|
|
3701
|
+
});
|
|
3702
|
+
}
|
|
3703
|
+
const session = this.config.sessionStore ? await this.config.sessionStore.get(sessionId) : null;
|
|
3704
|
+
this.activeStepContexts.delete(sessionId);
|
|
3705
|
+
return {
|
|
3706
|
+
text: result.text,
|
|
3707
|
+
steps: result.steps,
|
|
3708
|
+
toolCalls: result.toolCalls.map((tc) => ({
|
|
3709
|
+
type: "tool-call",
|
|
3710
|
+
toolCallId: tc.toolCallId,
|
|
3711
|
+
toolName: tc.toolName,
|
|
3712
|
+
args: "args" in tc ? tc.args : ("input" in tc) ? tc.input : undefined
|
|
3713
|
+
})),
|
|
3714
|
+
toolResults: result.toolResults.map((tr) => ({
|
|
3715
|
+
type: "tool-result",
|
|
3716
|
+
toolCallId: tr.toolCallId,
|
|
3717
|
+
toolName: tr.toolName,
|
|
3718
|
+
output: tr.output
|
|
3719
|
+
})),
|
|
3720
|
+
finishReason: result.finishReason,
|
|
3721
|
+
usage: result.usage,
|
|
3722
|
+
session: session ?? undefined,
|
|
3723
|
+
pendingApproval: pendingApproval ? {
|
|
3724
|
+
toolName: pendingApproval.toolName,
|
|
3725
|
+
toolCallId: pendingApproval.toolCallId,
|
|
3726
|
+
args: pendingApproval.toolArgs
|
|
3727
|
+
} : undefined
|
|
3728
|
+
};
|
|
3729
|
+
}
|
|
3730
|
+
async stream(params) {
|
|
3731
|
+
const sessionId = params.options?.sessionId ?? generateSessionId();
|
|
3732
|
+
const traceId = params.options?.metadata?.["traceId"] ?? this.config.posthogConfig?.tracingOptions?.posthogTraceId ?? randomUUID2();
|
|
3733
|
+
const workflowId = params.options?.workflowId ?? params.options?.metadata?.["workflowId"];
|
|
3734
|
+
const threadId = params.options?.threadId ?? params.options?.metadata?.["threadId"];
|
|
3735
|
+
const runtimeAdapter = this.resolveRuntimeAdapter();
|
|
3736
|
+
this.activeStepContexts.set(sessionId, {
|
|
3737
|
+
traceId,
|
|
3738
|
+
tenantId: params.options?.tenantId,
|
|
3739
|
+
actorId: params.options?.actorId,
|
|
3740
|
+
workflowId,
|
|
3741
|
+
threadId,
|
|
3742
|
+
stepIndex: 0,
|
|
3743
|
+
stepStartedAt: new Date
|
|
3744
|
+
});
|
|
3745
|
+
const prompt = params.systemOverride && params.prompt ? `${this.instructions}
|
|
3746
|
+
|
|
3747
|
+
${params.systemOverride}
|
|
3748
|
+
|
|
3749
|
+
${params.prompt}` : params.prompt ?? "";
|
|
3750
|
+
await this.ensureSession({
|
|
3751
|
+
sessionId,
|
|
3752
|
+
prompt,
|
|
3753
|
+
options: params.options,
|
|
3754
|
+
traceId
|
|
3755
|
+
});
|
|
3756
|
+
await this.runSessionMiddleware(sessionId, runtimeAdapter?.middleware?.beforeModel);
|
|
3757
|
+
const model = await this.resolveModelForCall({
|
|
3758
|
+
sessionId,
|
|
3759
|
+
traceId,
|
|
3760
|
+
options: params.options
|
|
3761
|
+
});
|
|
3762
|
+
const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
|
|
3763
|
+
const inner = this.createInnerAgent(model, effectiveMaxSteps);
|
|
3764
|
+
return inner.stream({
|
|
3765
|
+
prompt,
|
|
3766
|
+
abortSignal: params.signal,
|
|
3767
|
+
options: {
|
|
3768
|
+
tenantId: params.options?.tenantId,
|
|
3769
|
+
actorId: params.options?.actorId,
|
|
3770
|
+
sessionId,
|
|
3771
|
+
workflowId,
|
|
3772
|
+
threadId,
|
|
3773
|
+
metadata: params.options?.metadata
|
|
3774
|
+
}
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
async handleStepFinish(step) {
|
|
3778
|
+
const sessionId = step.options?.sessionId;
|
|
3779
|
+
const context = sessionId ? this.activeStepContexts.get(sessionId) : undefined;
|
|
3780
|
+
if (sessionId && this.config.sessionStore) {
|
|
3781
|
+
await this.config.sessionStore.appendStep(sessionId, step);
|
|
3782
|
+
if (step.text.trim().length > 0) {
|
|
3783
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3784
|
+
role: "assistant",
|
|
3785
|
+
content: step.text
|
|
3786
|
+
});
|
|
3787
|
+
}
|
|
3788
|
+
for (const toolCall of step.toolCalls ?? []) {
|
|
3789
|
+
await this.emitAgentEvent("agent.tool.called", {
|
|
3790
|
+
sessionId,
|
|
3791
|
+
tenantId: context?.tenantId,
|
|
3792
|
+
workflowId: context?.workflowId,
|
|
3793
|
+
traceId: context?.traceId,
|
|
3794
|
+
stepIndex: context?.stepIndex,
|
|
3795
|
+
toolName: toolCall.toolName,
|
|
3796
|
+
metadata: {
|
|
3797
|
+
toolCallId: toolCall.toolCallId
|
|
3798
|
+
}
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
for (const toolResult of step.toolResults ?? []) {
|
|
3802
|
+
const toolCall = step.toolCalls?.find((candidate) => candidate.toolCallId === toolResult.toolCallId);
|
|
3803
|
+
const toolError = extractToolExecutionError(toolResult.output);
|
|
3804
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3805
|
+
role: "tool",
|
|
3806
|
+
content: stringifyToolResult(toolResult.toolName, toolResult.output)
|
|
3807
|
+
});
|
|
3808
|
+
if (toolError) {
|
|
3809
|
+
await this.emitAgentEvent("agent.tool.failed", {
|
|
3810
|
+
sessionId,
|
|
3811
|
+
tenantId: context?.tenantId,
|
|
3812
|
+
workflowId: context?.workflowId,
|
|
3813
|
+
traceId: context?.traceId,
|
|
3814
|
+
stepIndex: context?.stepIndex,
|
|
3815
|
+
toolName: toolResult.toolName,
|
|
3816
|
+
metadata: {
|
|
3817
|
+
toolCallId: toolResult.toolCallId,
|
|
3818
|
+
error: toolError.message,
|
|
3819
|
+
code: toolError.code
|
|
3820
|
+
}
|
|
3821
|
+
});
|
|
3822
|
+
const shouldEscalate = toolError.kind === "timeout" && this.spec.policy?.escalation?.onTimeout || toolError.kind !== "timeout" && this.spec.policy?.escalation?.onToolFailure;
|
|
3823
|
+
if (shouldEscalate && toolCall && !context?.pendingApproval) {
|
|
3824
|
+
await this.requestApproval(sessionId, {
|
|
3825
|
+
toolName: toolCall.toolName,
|
|
3826
|
+
toolCallId: toolCall.toolCallId,
|
|
3827
|
+
args: toolCall.input,
|
|
3828
|
+
reason: toolError.message,
|
|
3829
|
+
error: toolError
|
|
3830
|
+
});
|
|
3831
|
+
}
|
|
3832
|
+
} else {
|
|
3833
|
+
await this.emitAgentEvent("agent.tool.completed", {
|
|
3834
|
+
sessionId,
|
|
3835
|
+
tenantId: context?.tenantId,
|
|
3836
|
+
workflowId: context?.workflowId,
|
|
3837
|
+
traceId: context?.traceId,
|
|
3838
|
+
stepIndex: context?.stepIndex,
|
|
3839
|
+
toolName: toolResult.toolName,
|
|
3840
|
+
metadata: {
|
|
3841
|
+
toolCallId: toolResult.toolCallId
|
|
3842
|
+
}
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3847
|
+
status: context?.pendingApproval ? "escalated" : step.finishReason === "tool-calls" ? "waiting" : "running",
|
|
3848
|
+
workflowId: context?.workflowId,
|
|
3849
|
+
threadId: context?.threadId,
|
|
3850
|
+
traceId: context?.traceId,
|
|
3851
|
+
pendingApprovalRequestId: context?.pendingApproval?.id
|
|
3852
|
+
});
|
|
3853
|
+
await this.syncSessionCheckpoint(sessionId);
|
|
3854
|
+
}
|
|
3855
|
+
if (this.config.telemetryCollector) {
|
|
3856
|
+
const now = new Date;
|
|
3857
|
+
const stepStartedAt = context?.stepStartedAt ?? now;
|
|
3858
|
+
const durationMs = Math.max(now.getTime() - stepStartedAt.getTime(), 0);
|
|
3859
|
+
if (context) {
|
|
3860
|
+
context.stepIndex += 1;
|
|
3861
|
+
context.stepStartedAt = now;
|
|
3862
|
+
}
|
|
3863
|
+
await trackAgentStep(this.config.telemetryCollector, this.id, step, durationMs, {
|
|
3864
|
+
sessionId,
|
|
3865
|
+
tenantId: context?.tenantId,
|
|
3866
|
+
actorId: context?.actorId,
|
|
3867
|
+
workflowId: context?.workflowId,
|
|
3868
|
+
traceId: context?.traceId,
|
|
3869
|
+
stepIndex: context?.stepIndex,
|
|
3870
|
+
stepStartedAt
|
|
3871
|
+
});
|
|
3872
|
+
if (sessionId && step.finishReason !== "tool-calls") {
|
|
3873
|
+
this.activeStepContexts.delete(sessionId);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
async ensureSession(params) {
|
|
3878
|
+
if (!this.config.sessionStore) {
|
|
3879
|
+
return;
|
|
3880
|
+
}
|
|
3881
|
+
const runtimeAdapter = this.resolveRuntimeAdapter();
|
|
3882
|
+
let session = await this.config.sessionStore.get(params.sessionId);
|
|
3883
|
+
const previousStatus = session?.status;
|
|
3884
|
+
if (!session && runtimeAdapter?.checkpoint) {
|
|
3885
|
+
const checkpoint = await runtimeAdapter.checkpoint.load(params.sessionId);
|
|
3886
|
+
if (checkpoint) {
|
|
3887
|
+
session = await this.config.sessionStore.create({
|
|
3888
|
+
...omitSessionTimestamps(checkpoint.state),
|
|
3889
|
+
workflowId: params.options?.workflowId ?? checkpoint.state.workflowId,
|
|
3890
|
+
threadId: params.options?.threadId ?? checkpoint.state.threadId,
|
|
3891
|
+
traceId: params.traceId,
|
|
3892
|
+
checkpointId: checkpoint.checkpointId,
|
|
3893
|
+
status: "running",
|
|
3894
|
+
pendingApprovalRequestId: undefined,
|
|
3895
|
+
lastError: undefined,
|
|
3896
|
+
metadata: params.options?.metadata ?? checkpoint.state.metadata
|
|
3897
|
+
});
|
|
3898
|
+
}
|
|
3899
|
+
}
|
|
3900
|
+
if (!session) {
|
|
3901
|
+
session = await this.config.sessionStore.create({
|
|
3902
|
+
sessionId: params.sessionId,
|
|
3903
|
+
agentId: this.id,
|
|
3904
|
+
tenantId: params.options?.tenantId,
|
|
3905
|
+
actorId: params.options?.actorId,
|
|
3906
|
+
workflowId: params.options?.workflowId,
|
|
3907
|
+
threadId: params.options?.threadId,
|
|
3908
|
+
traceId: params.traceId,
|
|
3909
|
+
status: "running",
|
|
3910
|
+
messages: [],
|
|
3911
|
+
steps: [],
|
|
3912
|
+
metadata: params.options?.metadata
|
|
3913
|
+
});
|
|
3914
|
+
await this.emitAgentEvent("agent.session.created", {
|
|
3915
|
+
sessionId: params.sessionId,
|
|
3916
|
+
tenantId: params.options?.tenantId,
|
|
3917
|
+
workflowId: params.options?.workflowId,
|
|
3918
|
+
traceId: params.traceId
|
|
3919
|
+
});
|
|
3920
|
+
} else {
|
|
3921
|
+
await this.config.sessionStore.update(params.sessionId, {
|
|
3922
|
+
status: "running",
|
|
3923
|
+
workflowId: params.options?.workflowId ?? session.workflowId,
|
|
3924
|
+
threadId: params.options?.threadId ?? session.threadId,
|
|
3925
|
+
traceId: params.traceId,
|
|
3926
|
+
metadata: params.options?.metadata ?? session.metadata,
|
|
3927
|
+
pendingApprovalRequestId: undefined,
|
|
3928
|
+
lastError: undefined
|
|
3929
|
+
});
|
|
3930
|
+
await this.emitAgentEvent("agent.session.updated", {
|
|
3931
|
+
sessionId: params.sessionId,
|
|
3932
|
+
tenantId: params.options?.tenantId ?? session.tenantId,
|
|
3933
|
+
workflowId: params.options?.workflowId ?? session.workflowId,
|
|
3934
|
+
traceId: params.traceId,
|
|
3935
|
+
metadata: {
|
|
3936
|
+
previousStatus: previousStatus ?? "idle",
|
|
3937
|
+
nextStatus: "running"
|
|
3938
|
+
}
|
|
3939
|
+
});
|
|
3940
|
+
}
|
|
3941
|
+
await this.config.sessionStore.appendMessage(params.sessionId, {
|
|
3942
|
+
role: "user",
|
|
3943
|
+
content: params.prompt
|
|
3944
|
+
});
|
|
3945
|
+
if (runtimeAdapter?.suspendResume && (previousStatus === "waiting" || previousStatus === "escalated")) {
|
|
3946
|
+
await runtimeAdapter.suspendResume.resume({
|
|
3947
|
+
sessionId: params.sessionId,
|
|
3948
|
+
input: params.prompt,
|
|
3949
|
+
metadata: compactStringRecord({
|
|
3950
|
+
traceId: params.traceId,
|
|
3951
|
+
workflowId: params.options?.workflowId,
|
|
3952
|
+
threadId: params.options?.threadId
|
|
3953
|
+
})
|
|
3954
|
+
});
|
|
3955
|
+
}
|
|
3956
|
+
await this.syncSessionCheckpoint(params.sessionId);
|
|
3957
|
+
}
|
|
3958
|
+
resolveRuntimeAdapter() {
|
|
3959
|
+
return resolveRuntimeAdapterBundle(this.spec, this.config.runtimeAdapters);
|
|
3960
|
+
}
|
|
3961
|
+
async syncSessionCheckpoint(sessionId) {
|
|
3962
|
+
if (!this.config.sessionStore) {
|
|
3963
|
+
return null;
|
|
3964
|
+
}
|
|
3965
|
+
const runtimeAdapter = this.resolveRuntimeAdapter();
|
|
3966
|
+
const session = await this.config.sessionStore.get(sessionId);
|
|
3967
|
+
if (!runtimeAdapter?.checkpoint || !session) {
|
|
3968
|
+
return session;
|
|
3969
|
+
}
|
|
3970
|
+
const checkpointId = `${sessionId}:${Date.now()}`;
|
|
3971
|
+
await runtimeAdapter.checkpoint.save({
|
|
3972
|
+
sessionId,
|
|
3973
|
+
threadId: session.threadId,
|
|
3974
|
+
state: session,
|
|
3975
|
+
checkpointId,
|
|
3976
|
+
createdAt: new Date
|
|
3977
|
+
});
|
|
3978
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3979
|
+
checkpointId
|
|
3980
|
+
});
|
|
3981
|
+
return this.config.sessionStore.get(sessionId);
|
|
3982
|
+
}
|
|
3983
|
+
async runSessionMiddleware(sessionId, hook) {
|
|
3984
|
+
if (!hook || !this.config.sessionStore) {
|
|
3985
|
+
return;
|
|
3986
|
+
}
|
|
3987
|
+
const session = await this.config.sessionStore.get(sessionId);
|
|
3988
|
+
if (!session) {
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
const next = await hook(session);
|
|
3992
|
+
if (!next) {
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3995
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3996
|
+
status: next.status,
|
|
3997
|
+
metadata: next.metadata,
|
|
3998
|
+
workflowId: next.workflowId,
|
|
3999
|
+
threadId: next.threadId,
|
|
4000
|
+
traceId: next.traceId,
|
|
4001
|
+
checkpointId: next.checkpointId,
|
|
4002
|
+
pendingApprovalRequestId: next.pendingApprovalRequestId,
|
|
4003
|
+
lastError: next.lastError
|
|
4004
|
+
});
|
|
4005
|
+
await this.syncSessionCheckpoint(sessionId);
|
|
4006
|
+
}
|
|
4007
|
+
async requestApproval(sessionId, params) {
|
|
4008
|
+
const approvalWorkflow = this.config.approvalWorkflow;
|
|
4009
|
+
const context = this.activeStepContexts.get(sessionId);
|
|
4010
|
+
if (!approvalWorkflow || !context || context.pendingApproval) {
|
|
4011
|
+
return context?.pendingApproval;
|
|
4012
|
+
}
|
|
4013
|
+
const request = await approvalWorkflow.requestApproval({
|
|
4014
|
+
sessionId,
|
|
4015
|
+
agentId: this.id,
|
|
4016
|
+
tenantId: context.tenantId,
|
|
4017
|
+
toolName: params.toolName,
|
|
4018
|
+
toolCallId: params.toolCallId,
|
|
4019
|
+
toolArgs: params.args,
|
|
4020
|
+
reason: params.reason,
|
|
4021
|
+
payload: {
|
|
4022
|
+
traceId: context.traceId,
|
|
4023
|
+
workflowId: context.workflowId,
|
|
4024
|
+
threadId: context.threadId,
|
|
4025
|
+
code: params.error?.code
|
|
4026
|
+
}
|
|
4027
|
+
});
|
|
4028
|
+
context.pendingApproval = request;
|
|
4029
|
+
if (this.config.sessionStore) {
|
|
4030
|
+
await this.config.sessionStore.update(sessionId, {
|
|
4031
|
+
status: "escalated",
|
|
4032
|
+
pendingApprovalRequestId: request.id,
|
|
4033
|
+
lastError: params.error
|
|
4034
|
+
});
|
|
4035
|
+
await this.syncSessionCheckpoint(sessionId);
|
|
4036
|
+
}
|
|
4037
|
+
await this.emitAgentEvent("agent.tool.approval_requested", {
|
|
4038
|
+
sessionId,
|
|
4039
|
+
tenantId: context.tenantId,
|
|
4040
|
+
workflowId: context.workflowId,
|
|
4041
|
+
traceId: context.traceId,
|
|
4042
|
+
stepIndex: context.stepIndex,
|
|
4043
|
+
toolName: params.toolName,
|
|
4044
|
+
metadata: {
|
|
4045
|
+
approvalRequestId: request.id,
|
|
4046
|
+
toolCallId: params.toolCallId,
|
|
4047
|
+
reason: params.reason
|
|
4048
|
+
}
|
|
4049
|
+
});
|
|
4050
|
+
await this.emitAgentEvent("agent.escalated", {
|
|
4051
|
+
sessionId,
|
|
4052
|
+
tenantId: context.tenantId,
|
|
4053
|
+
workflowId: context.workflowId,
|
|
4054
|
+
traceId: context.traceId,
|
|
4055
|
+
stepIndex: context.stepIndex,
|
|
4056
|
+
toolName: params.toolName,
|
|
4057
|
+
metadata: {
|
|
4058
|
+
approvalRequestId: request.id,
|
|
4059
|
+
reason: params.reason
|
|
4060
|
+
}
|
|
4061
|
+
});
|
|
4062
|
+
const runtimeAdapter = this.resolveRuntimeAdapter();
|
|
4063
|
+
if (runtimeAdapter?.suspendResume) {
|
|
4064
|
+
await runtimeAdapter.suspendResume.suspend({
|
|
4065
|
+
sessionId,
|
|
4066
|
+
reason: params.reason,
|
|
4067
|
+
metadata: compactStringRecord({
|
|
4068
|
+
traceId: context.traceId,
|
|
4069
|
+
workflowId: context.workflowId,
|
|
4070
|
+
threadId: context.threadId,
|
|
4071
|
+
approvalRequestId: request.id
|
|
4072
|
+
})
|
|
4073
|
+
});
|
|
4074
|
+
}
|
|
4075
|
+
return request;
|
|
4076
|
+
}
|
|
4077
|
+
async emitAgentEvent(event, payload) {
|
|
4078
|
+
if (!this.config.eventEmitter) {
|
|
4079
|
+
return;
|
|
4080
|
+
}
|
|
4081
|
+
await this.config.eventEmitter(event, {
|
|
4082
|
+
agentId: this.id,
|
|
4083
|
+
...payload
|
|
4084
|
+
});
|
|
4085
|
+
}
|
|
4086
|
+
createInnerAgent(model, maxSteps) {
|
|
4087
|
+
return new ToolLoopAgent({
|
|
4088
|
+
model,
|
|
4089
|
+
instructions: this.instructions,
|
|
4090
|
+
tools: this.tools,
|
|
4091
|
+
stopWhen: stepCountIs(maxSteps),
|
|
4092
|
+
callOptionsSchema: ContractSpecCallOptionsSchema,
|
|
4093
|
+
onStepFinish: async (step) => {
|
|
4094
|
+
await this.handleStepFinish(step);
|
|
4095
|
+
}
|
|
4096
|
+
});
|
|
4097
|
+
}
|
|
4098
|
+
async resolveModelForCall(params) {
|
|
4099
|
+
if (this.config.modelSelector && params.options?.selectionContext) {
|
|
4100
|
+
const { model } = await this.config.modelSelector.selectAndCreate(params.options.selectionContext);
|
|
4101
|
+
return model;
|
|
4102
|
+
}
|
|
4103
|
+
const posthogConfig = this.config.posthogConfig;
|
|
4104
|
+
if (!posthogConfig) {
|
|
4105
|
+
return this.config.model;
|
|
4106
|
+
}
|
|
4107
|
+
const mergedProperties = {
|
|
4108
|
+
...posthogConfig.defaults?.posthogProperties,
|
|
4109
|
+
...posthogConfig.tracingOptions?.posthogProperties,
|
|
4110
|
+
$ai_session_id: params.sessionId,
|
|
4111
|
+
contractspec_session_id: params.sessionId,
|
|
4112
|
+
contractspec_trace_id: params.traceId,
|
|
4113
|
+
contractspec_agent_id: this.id,
|
|
4114
|
+
contractspec_tenant_id: params.options?.tenantId,
|
|
4115
|
+
contractspec_actor_id: params.options?.actorId,
|
|
4116
|
+
contractspec_workflow_id: params.options?.workflowId,
|
|
4117
|
+
contractspec_thread_id: params.options?.threadId
|
|
4118
|
+
};
|
|
4119
|
+
const tracingOptions = {
|
|
4120
|
+
...posthogConfig.tracingOptions,
|
|
4121
|
+
posthogDistinctId: posthogConfig.tracingOptions?.posthogDistinctId ?? params.options?.actorId,
|
|
4122
|
+
posthogTraceId: params.traceId,
|
|
4123
|
+
posthogProperties: mergedProperties
|
|
4124
|
+
};
|
|
4125
|
+
const { createPostHogTracedModel: createPostHogTracedModel2 } = await Promise.resolve().then(() => (init_posthog(), exports_posthog));
|
|
4126
|
+
return createPostHogTracedModel2(this.config.model, posthogConfig, tracingOptions);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
function omitSessionTimestamps(session) {
|
|
4130
|
+
const { createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = session;
|
|
4131
|
+
return rest;
|
|
4132
|
+
}
|
|
4133
|
+
function resolveRuntimeAdapterBundle(spec, runtimeAdapters) {
|
|
4134
|
+
if (!runtimeAdapters) {
|
|
4135
|
+
return;
|
|
4136
|
+
}
|
|
4137
|
+
const preferredKeys = [
|
|
4138
|
+
"langgraph",
|
|
4139
|
+
"langchain",
|
|
4140
|
+
"workflow-devkit"
|
|
4141
|
+
];
|
|
4142
|
+
for (const key of preferredKeys) {
|
|
4143
|
+
if (spec.runtime?.capabilities?.adapters?.[key] && runtimeAdapters[key]) {
|
|
4144
|
+
return runtimeAdapters[key];
|
|
4145
|
+
}
|
|
4146
|
+
}
|
|
4147
|
+
for (const key of preferredKeys) {
|
|
4148
|
+
if (runtimeAdapters[key]) {
|
|
4149
|
+
return runtimeAdapters[key];
|
|
4150
|
+
}
|
|
4151
|
+
}
|
|
4152
|
+
return;
|
|
4153
|
+
}
|
|
4154
|
+
function compactStringRecord(input) {
|
|
4155
|
+
return Object.fromEntries(Object.entries(input).filter((entry) => typeof entry[1] === "string" && entry[1].length > 0));
|
|
4156
|
+
}
|
|
4157
|
+
function toAgentExecutionError(error) {
|
|
4158
|
+
if (error && typeof error === "object" && "kind" in error && typeof error.kind === "string" && "message" in error && typeof error.message === "string") {
|
|
4159
|
+
return {
|
|
4160
|
+
kind: error.kind,
|
|
4161
|
+
message: error.message,
|
|
4162
|
+
code: "code" in error && typeof error.code === "string" ? error.code : undefined,
|
|
4163
|
+
retryAfterMs: "retryAfterMs" in error && typeof error.retryAfterMs === "number" ? error.retryAfterMs : undefined
|
|
4164
|
+
};
|
|
4165
|
+
}
|
|
4166
|
+
if (error instanceof Error) {
|
|
4167
|
+
return {
|
|
4168
|
+
kind: error.message.toLowerCase().includes("timeout") || error.code === "TOOL_EXECUTION_TIMEOUT" ? "timeout" : "fatal",
|
|
4169
|
+
message: error.message,
|
|
4170
|
+
code: error.code
|
|
4171
|
+
};
|
|
4172
|
+
}
|
|
4173
|
+
return {
|
|
4174
|
+
kind: "fatal",
|
|
4175
|
+
message: String(error)
|
|
4176
|
+
};
|
|
4177
|
+
}
|
|
4178
|
+
function extractToolExecutionError(output) {
|
|
4179
|
+
if (!output) {
|
|
4180
|
+
return;
|
|
4181
|
+
}
|
|
4182
|
+
if (typeof output === "string") {
|
|
4183
|
+
if (output.toLowerCase().includes("error")) {
|
|
4184
|
+
return {
|
|
4185
|
+
kind: output.toLowerCase().includes("timeout") ? "timeout" : "retryable",
|
|
4186
|
+
message: output
|
|
4187
|
+
};
|
|
4188
|
+
}
|
|
4189
|
+
return;
|
|
4190
|
+
}
|
|
4191
|
+
if (typeof output !== "object") {
|
|
4192
|
+
return;
|
|
4193
|
+
}
|
|
4194
|
+
const record2 = output;
|
|
4195
|
+
const nestedError = record2["error"];
|
|
4196
|
+
if (nestedError instanceof Error) {
|
|
4197
|
+
return toAgentExecutionError(nestedError);
|
|
4198
|
+
}
|
|
4199
|
+
if (nestedError && typeof nestedError === "object") {
|
|
4200
|
+
return toAgentExecutionError(nestedError);
|
|
4201
|
+
}
|
|
4202
|
+
if (typeof record2["message"] === "string" && typeof record2["kind"] === "string") {
|
|
4203
|
+
return {
|
|
4204
|
+
kind: record2["kind"],
|
|
4205
|
+
message: record2["message"],
|
|
4206
|
+
code: typeof record2["code"] === "string" ? record2["code"] : undefined,
|
|
4207
|
+
retryAfterMs: typeof record2["retryAfterMs"] === "number" ? record2["retryAfterMs"] : undefined
|
|
4208
|
+
};
|
|
4209
|
+
}
|
|
4210
|
+
if (typeof record2["errorMessage"] === "string") {
|
|
4211
|
+
return {
|
|
4212
|
+
kind: typeof record2["errorCode"] === "string" && record2["errorCode"].includes("TIMEOUT") ? "timeout" : "retryable",
|
|
4213
|
+
message: record2["errorMessage"],
|
|
4214
|
+
code: typeof record2["errorCode"] === "string" ? record2["errorCode"] : undefined
|
|
4215
|
+
};
|
|
4216
|
+
}
|
|
4217
|
+
return;
|
|
4218
|
+
}
|
|
4219
|
+
function stringifyToolResult(toolName, output) {
|
|
4220
|
+
if (typeof output === "string") {
|
|
4221
|
+
return `[${toolName}] ${output}`;
|
|
4222
|
+
}
|
|
4223
|
+
try {
|
|
4224
|
+
return `[${toolName}] ${JSON.stringify(output)}`;
|
|
4225
|
+
} catch {
|
|
4226
|
+
return `[${toolName}] [unserializable result]`;
|
|
4227
|
+
}
|
|
4228
|
+
}
|
|
4229
|
+
function resolveMaxSteps(overrideMaxSteps, specMaxSteps) {
|
|
4230
|
+
const candidate = overrideMaxSteps ?? specMaxSteps ?? 10;
|
|
4231
|
+
if (!Number.isFinite(candidate)) {
|
|
4232
|
+
return 10;
|
|
4233
|
+
}
|
|
4234
|
+
if (candidate < 1) {
|
|
4235
|
+
return 1;
|
|
4236
|
+
}
|
|
4237
|
+
return Math.round(candidate);
|
|
4238
|
+
}
|
|
4239
|
+
function resolveEscalationError(spec, finishReason) {
|
|
4240
|
+
const escalation = spec.policy?.escalation;
|
|
4241
|
+
if (!escalation) {
|
|
4242
|
+
return;
|
|
4243
|
+
}
|
|
4244
|
+
if (escalation.onTimeout && finishReason === "length") {
|
|
4245
|
+
return {
|
|
4246
|
+
kind: "timeout",
|
|
4247
|
+
code: "AGENT_TIMEOUT_ESCALATION",
|
|
4248
|
+
message: "Agent reached max step budget and requires escalation."
|
|
4249
|
+
};
|
|
4250
|
+
}
|
|
4251
|
+
if (escalation.onToolFailure && finishReason === "error") {
|
|
4252
|
+
return {
|
|
4253
|
+
kind: "retryable",
|
|
4254
|
+
code: "AGENT_TOOL_FAILURE_ESCALATION",
|
|
4255
|
+
message: "Agent encountered a tool failure and requires escalation."
|
|
4256
|
+
};
|
|
4257
|
+
}
|
|
4258
|
+
const confidenceThreshold = escalation.confidenceThreshold;
|
|
4259
|
+
const defaultConfidence = spec.policy?.confidence?.default;
|
|
4260
|
+
if (confidenceThreshold !== undefined && defaultConfidence !== undefined && defaultConfidence < confidenceThreshold) {
|
|
4261
|
+
return {
|
|
4262
|
+
kind: "policy_blocked",
|
|
4263
|
+
code: "AGENT_CONFIDENCE_ESCALATION",
|
|
4264
|
+
message: `Agent default confidence (${defaultConfidence}) is below escalation threshold (${confidenceThreshold}).`
|
|
4265
|
+
};
|
|
4266
|
+
}
|
|
4267
|
+
return;
|
|
4268
|
+
}
|
|
4269
|
+
var ContractSpecCallOptionsSchema;
|
|
4270
|
+
var init_contract_spec_agent = __esm(() => {
|
|
4271
|
+
init_workflow();
|
|
4272
|
+
init_injector();
|
|
4273
|
+
init_adapter();
|
|
4274
|
+
init_knowledge_tool();
|
|
4275
|
+
init_mcp_client();
|
|
4276
|
+
init_memory_tools();
|
|
4277
|
+
init_tool_adapter();
|
|
4278
|
+
ContractSpecCallOptionsSchema = z4.object({
|
|
4279
|
+
tenantId: z4.string().optional(),
|
|
4280
|
+
actorId: z4.string().optional(),
|
|
4281
|
+
sessionId: z4.string().optional(),
|
|
4282
|
+
workflowId: z4.string().optional(),
|
|
4283
|
+
threadId: z4.string().optional(),
|
|
4284
|
+
metadata: z4.record(z4.string(), z4.unknown()).optional()
|
|
4285
|
+
});
|
|
4286
|
+
});
|
|
4287
|
+
|
|
4288
|
+
// src/providers/types.ts
|
|
4289
|
+
var ExternalProviderError, ProviderNotAvailableError, ProviderExecutionError, ContextCreationError;
|
|
4290
|
+
var init_types = __esm(() => {
|
|
4291
|
+
init_i18n();
|
|
4292
|
+
ExternalProviderError = class ExternalProviderError extends Error {
|
|
4293
|
+
provider;
|
|
4294
|
+
code;
|
|
4295
|
+
cause;
|
|
4296
|
+
constructor(message, provider, code, cause) {
|
|
4297
|
+
super(message);
|
|
4298
|
+
this.provider = provider;
|
|
4299
|
+
this.code = code;
|
|
4300
|
+
this.cause = cause;
|
|
4301
|
+
this.name = "ExternalProviderError";
|
|
4302
|
+
}
|
|
4303
|
+
};
|
|
4304
|
+
ProviderNotAvailableError = class ProviderNotAvailableError extends ExternalProviderError {
|
|
4305
|
+
constructor(provider, reason, locale) {
|
|
4306
|
+
const i18n = createAgentI18n(locale);
|
|
4307
|
+
super(i18n.t("error.provider.notAvailable", {
|
|
4308
|
+
provider,
|
|
4309
|
+
reason: reason ? `: ${reason}` : ""
|
|
4310
|
+
}), provider, "PROVIDER_NOT_AVAILABLE");
|
|
4311
|
+
this.name = "ProviderNotAvailableError";
|
|
4312
|
+
}
|
|
4313
|
+
};
|
|
4314
|
+
ProviderExecutionError = class ProviderExecutionError extends ExternalProviderError {
|
|
4315
|
+
constructor(provider, message, cause) {
|
|
4316
|
+
super(message, provider, "EXECUTION_FAILED", cause);
|
|
4317
|
+
this.name = "ProviderExecutionError";
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
ContextCreationError = class ContextCreationError extends ExternalProviderError {
|
|
4321
|
+
constructor(provider, message, cause) {
|
|
4322
|
+
super(message, provider, "CONTEXT_CREATION_FAILED", cause);
|
|
4323
|
+
this.name = "ContextCreationError";
|
|
4324
|
+
}
|
|
4325
|
+
};
|
|
4326
|
+
});
|
|
4327
|
+
|
|
4328
|
+
// src/providers/claude-agent-sdk/session-bridge.ts
|
|
4329
|
+
function toClaudeAgentSession(state) {
|
|
4330
|
+
const messages = [];
|
|
4331
|
+
for (const msg of state.messages) {
|
|
4332
|
+
if (msg.role === "user" || msg.role === "assistant") {
|
|
4333
|
+
const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
|
|
4334
|
+
messages.push({
|
|
4335
|
+
role: msg.role,
|
|
4336
|
+
content
|
|
4337
|
+
});
|
|
4338
|
+
}
|
|
4339
|
+
}
|
|
4340
|
+
return {
|
|
4341
|
+
sessionId: state.sessionId,
|
|
4342
|
+
messages,
|
|
4343
|
+
metadata: {
|
|
4344
|
+
agentId: state.agentId,
|
|
4345
|
+
tenantId: state.tenantId,
|
|
4346
|
+
actorId: state.actorId
|
|
4347
|
+
}
|
|
4348
|
+
};
|
|
4349
|
+
}
|
|
4350
|
+
function createClaudeAgentSession(options) {
|
|
4351
|
+
return {
|
|
4352
|
+
sessionId: options?.sessionId,
|
|
4353
|
+
messages: [],
|
|
4354
|
+
metadata: {
|
|
4355
|
+
tenantId: options?.tenantId,
|
|
4356
|
+
actorId: options?.actorId,
|
|
4357
|
+
...options?.metadata
|
|
4358
|
+
}
|
|
4359
|
+
};
|
|
4360
|
+
}
|
|
4361
|
+
function createEmptyClaudeSession() {
|
|
4362
|
+
return createClaudeAgentSession();
|
|
4363
|
+
}
|
|
4364
|
+
function buildClaudeAgentContext(options) {
|
|
4365
|
+
return {
|
|
4366
|
+
tenantId: options?.tenantId,
|
|
4367
|
+
actorId: options?.actorId,
|
|
4368
|
+
sessionId: options?.sessionId,
|
|
4369
|
+
...options?.metadata
|
|
4370
|
+
};
|
|
4371
|
+
}
|
|
4372
|
+
function appendUserMessage(session, content) {
|
|
4373
|
+
return {
|
|
4374
|
+
...session,
|
|
4375
|
+
messages: [...session.messages, { role: "user", content }]
|
|
4376
|
+
};
|
|
4377
|
+
}
|
|
4378
|
+
function appendAssistantMessage(session, content) {
|
|
4379
|
+
return {
|
|
4380
|
+
...session,
|
|
4381
|
+
messages: [...session.messages, { role: "assistant", content }]
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4384
|
+
function clearSession(session) {
|
|
4385
|
+
return {
|
|
4386
|
+
...session,
|
|
4387
|
+
messages: []
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
function getRecentMessages(session, count) {
|
|
4391
|
+
return session.messages.slice(-count);
|
|
4392
|
+
}
|
|
4393
|
+
function extractToolUseBlocks(content) {
|
|
4394
|
+
if (typeof content === "string") {
|
|
4395
|
+
return [];
|
|
4396
|
+
}
|
|
4397
|
+
return content.filter((block) => block.type === "tool_use");
|
|
4398
|
+
}
|
|
4399
|
+
function createToolResultBlock(toolUseId, result, isError = false) {
|
|
4400
|
+
return {
|
|
4401
|
+
type: "tool_result",
|
|
4402
|
+
tool_use_id: toolUseId,
|
|
4403
|
+
content: typeof result === "string" ? result : JSON.stringify(result),
|
|
4404
|
+
is_error: isError
|
|
4405
|
+
};
|
|
4406
|
+
}
|
|
4407
|
+
function updateSessionMetadata(session, metadata) {
|
|
4408
|
+
return {
|
|
4409
|
+
...session,
|
|
4410
|
+
metadata: {
|
|
4411
|
+
...session.metadata,
|
|
4412
|
+
...metadata
|
|
4413
|
+
}
|
|
4414
|
+
};
|
|
4415
|
+
}
|
|
4416
|
+
function getMessageCount(session) {
|
|
4417
|
+
return session.messages.length;
|
|
4418
|
+
}
|
|
4419
|
+
function estimateTokens(session) {
|
|
4420
|
+
let chars = 0;
|
|
4421
|
+
for (const msg of session.messages) {
|
|
4422
|
+
if (typeof msg.content === "string") {
|
|
4423
|
+
chars += msg.content.length;
|
|
4424
|
+
} else {
|
|
4425
|
+
chars += JSON.stringify(msg.content).length;
|
|
4426
|
+
}
|
|
4427
|
+
}
|
|
4428
|
+
return Math.ceil(chars / 4);
|
|
4429
|
+
}
|
|
4430
|
+
function summarizeSession(session) {
|
|
4431
|
+
let userMessages = 0;
|
|
4432
|
+
let assistantMessages = 0;
|
|
4433
|
+
for (const msg of session.messages) {
|
|
4434
|
+
if (msg.role === "user") {
|
|
4435
|
+
userMessages++;
|
|
4436
|
+
} else {
|
|
4437
|
+
assistantMessages++;
|
|
4438
|
+
}
|
|
4439
|
+
}
|
|
4440
|
+
return {
|
|
4441
|
+
messageCount: session.messages.length,
|
|
4442
|
+
userMessages,
|
|
4443
|
+
assistantMessages,
|
|
4444
|
+
estimatedTokens: estimateTokens(session)
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
|
|
4448
|
+
// src/providers/claude-agent-sdk/tool-bridge.ts
|
|
4449
|
+
function specToolToClaudeAgentTool(tool4, handler, context) {
|
|
4450
|
+
return {
|
|
4451
|
+
name: tool4.name,
|
|
4452
|
+
description: tool4.description ?? createAgentI18n().t("tool.fallbackDescription", { name: tool4.name }),
|
|
4453
|
+
input_schema: normalizeSchema(tool4.schema),
|
|
4454
|
+
requires_confirmation: tool4.requiresApproval ?? !tool4.automationSafe,
|
|
4455
|
+
execute: async (input) => {
|
|
4456
|
+
const fullContext = {
|
|
4457
|
+
agentId: context.agentId ?? "unknown",
|
|
4458
|
+
sessionId: context.sessionId ?? "unknown",
|
|
4459
|
+
tenantId: context.tenantId,
|
|
4460
|
+
actorId: context.actorId,
|
|
4461
|
+
metadata: context.metadata,
|
|
4462
|
+
signal: context.signal
|
|
4463
|
+
};
|
|
4464
|
+
return handler(input, fullContext);
|
|
4465
|
+
}
|
|
4466
|
+
};
|
|
4467
|
+
}
|
|
4468
|
+
function specToolsToClaudeAgentTools(tools, handlers, context) {
|
|
4469
|
+
return tools.filter((tool4) => handlers.has(tool4.name)).map((tool4) => {
|
|
4470
|
+
const handler = handlers.get(tool4.name);
|
|
4471
|
+
if (!handler) {
|
|
4472
|
+
throw new Error(createAgentI18n().t("error.handlerNotFoundForTool", {
|
|
4473
|
+
name: tool4.name
|
|
4474
|
+
}));
|
|
4475
|
+
}
|
|
4476
|
+
return specToolToClaudeAgentTool(tool4, handler, context);
|
|
4477
|
+
});
|
|
4478
|
+
}
|
|
4479
|
+
function claudeAgentToolToSpecTool(claudeTool, execute) {
|
|
4480
|
+
const config = {
|
|
4481
|
+
name: claudeTool.name,
|
|
4482
|
+
description: claudeTool.description,
|
|
4483
|
+
schema: claudeTool.input_schema,
|
|
4484
|
+
requiresApproval: claudeTool.requires_confirmation,
|
|
4485
|
+
automationSafe: !claudeTool.requires_confirmation
|
|
4486
|
+
};
|
|
4487
|
+
const handler = execute ? async (input, _ctx) => String(await execute(input)) : undefined;
|
|
4488
|
+
return { config, handler };
|
|
4489
|
+
}
|
|
4490
|
+
function claudeAgentToolsToSpecTools(claudeTools) {
|
|
4491
|
+
const configs = [];
|
|
4492
|
+
const handlers = new Map;
|
|
4493
|
+
for (const claudeTool of claudeTools) {
|
|
4494
|
+
const { config, handler } = claudeAgentToolToSpecTool(claudeTool, claudeTool.execute);
|
|
4495
|
+
configs.push(config);
|
|
4496
|
+
if (handler) {
|
|
4497
|
+
handlers.set(config.name, handler);
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
return { configs, handlers };
|
|
4501
|
+
}
|
|
4502
|
+
function specToolToExternalTool(tool4, handler, context) {
|
|
4503
|
+
return {
|
|
4504
|
+
name: tool4.name,
|
|
4505
|
+
description: tool4.description ?? createAgentI18n().t("tool.fallbackDescription", { name: tool4.name }),
|
|
4506
|
+
inputSchema: tool4.schema ?? { type: "object" },
|
|
4507
|
+
requiresApproval: tool4.requiresApproval ?? !tool4.automationSafe,
|
|
4508
|
+
execute: handler ? async (input) => {
|
|
4509
|
+
const fullContext = {
|
|
4510
|
+
agentId: context?.agentId ?? "unknown",
|
|
4511
|
+
sessionId: context?.sessionId ?? "unknown",
|
|
4512
|
+
tenantId: context?.tenantId,
|
|
4513
|
+
actorId: context?.actorId,
|
|
4514
|
+
metadata: context?.metadata,
|
|
4515
|
+
signal: context?.signal
|
|
4516
|
+
};
|
|
4517
|
+
return handler(input, fullContext);
|
|
4518
|
+
} : undefined
|
|
4519
|
+
};
|
|
4520
|
+
}
|
|
4521
|
+
function normalizeSchema(schema) {
|
|
4522
|
+
if (!schema) {
|
|
4523
|
+
return { type: "object" };
|
|
2139
4524
|
}
|
|
2140
|
-
if (
|
|
2141
|
-
|
|
4525
|
+
if (schema.type === "object") {
|
|
4526
|
+
return schema;
|
|
2142
4527
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
4528
|
+
return {
|
|
4529
|
+
type: "object",
|
|
4530
|
+
properties: {
|
|
4531
|
+
value: schema
|
|
4532
|
+
},
|
|
4533
|
+
required: ["value"]
|
|
4534
|
+
};
|
|
4535
|
+
}
|
|
4536
|
+
function extractToolCalls(response) {
|
|
4537
|
+
if (!response.content) {
|
|
4538
|
+
return [];
|
|
2145
4539
|
}
|
|
2146
|
-
|
|
2147
|
-
|
|
4540
|
+
return response.content.filter((block) => block.type === "tool_use").map((block) => ({
|
|
4541
|
+
toolCallId: block.id ?? "",
|
|
4542
|
+
toolName: block.name ?? "",
|
|
4543
|
+
args: block.input
|
|
4544
|
+
}));
|
|
4545
|
+
}
|
|
4546
|
+
var init_tool_bridge = __esm(() => {
|
|
4547
|
+
init_i18n();
|
|
4548
|
+
});
|
|
4549
|
+
|
|
4550
|
+
// src/providers/claude-agent-sdk/adapter.ts
|
|
4551
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
4552
|
+
import {
|
|
4553
|
+
agentKey as agentKey2
|
|
4554
|
+
} from "@contractspec/lib.contracts-spec/agent";
|
|
4555
|
+
|
|
4556
|
+
class ClaudeAgentSDKProvider {
|
|
4557
|
+
name = "claude-agent-sdk";
|
|
4558
|
+
version = "1.0.0";
|
|
4559
|
+
config;
|
|
4560
|
+
sdkAvailable = null;
|
|
4561
|
+
constructor(config = {}) {
|
|
4562
|
+
this.config = {
|
|
4563
|
+
model: "claude-sonnet-4-20250514",
|
|
4564
|
+
extendedThinking: false,
|
|
4565
|
+
computerUse: false,
|
|
4566
|
+
maxTokens: 4096,
|
|
4567
|
+
temperature: 0.7,
|
|
4568
|
+
...config
|
|
4569
|
+
};
|
|
2148
4570
|
}
|
|
2149
|
-
|
|
2150
|
-
if (
|
|
2151
|
-
|
|
4571
|
+
isAvailable() {
|
|
4572
|
+
if (this.sdkAvailable !== null) {
|
|
4573
|
+
return this.sdkAvailable;
|
|
4574
|
+
}
|
|
4575
|
+
try {
|
|
4576
|
+
__require.resolve("@anthropic-ai/claude-agent-sdk");
|
|
4577
|
+
const apiKey = this.config.apiKey ?? process.env.ANTHROPIC_API_KEY;
|
|
4578
|
+
this.sdkAvailable = Boolean(apiKey);
|
|
4579
|
+
} catch {
|
|
4580
|
+
this.sdkAvailable = false;
|
|
2152
4581
|
}
|
|
4582
|
+
return this.sdkAvailable;
|
|
2153
4583
|
}
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
}
|
|
4584
|
+
async createContext(spec) {
|
|
4585
|
+
if (!this.isAvailable()) {
|
|
4586
|
+
throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.sdkNotConfigured"));
|
|
4587
|
+
}
|
|
4588
|
+
let mcpToolset = null;
|
|
4589
|
+
try {
|
|
4590
|
+
const toolSet = {};
|
|
4591
|
+
for (const tool4 of spec.tools) {
|
|
4592
|
+
toolSet[tool4.name] = specToolToExternalTool(tool4);
|
|
4593
|
+
}
|
|
4594
|
+
if ((this.config.mcpServers?.length ?? 0) > 0) {
|
|
4595
|
+
mcpToolset = await createMcpToolsets(this.config.mcpServers ?? [], {
|
|
4596
|
+
onNameCollision: "error"
|
|
4597
|
+
});
|
|
4598
|
+
for (const [toolName, mcpTool] of Object.entries(mcpToolset.tools)) {
|
|
4599
|
+
if (toolSet[toolName]) {
|
|
4600
|
+
throw new Error(`MCP tool "${toolName}" collides with a ContractSpec tool. Configure MCP toolPrefix values to avoid collisions.`);
|
|
4601
|
+
}
|
|
4602
|
+
toolSet[toolName] = this.mcpToolToExternalTool(toolName, mcpTool);
|
|
4603
|
+
}
|
|
4604
|
+
}
|
|
4605
|
+
const instructions = await injectStaticKnowledge(spec.instructions, spec.knowledge ?? [], undefined);
|
|
4606
|
+
const contextId = `claude-${agentKey2(spec.meta)}-${Date.now()}`;
|
|
4607
|
+
const metadata = {
|
|
4608
|
+
computerUseEnabled: this.config.computerUse ?? false,
|
|
4609
|
+
extendedThinkingEnabled: this.config.extendedThinking ?? false,
|
|
4610
|
+
mcpServerIds: this.config.mcpServers?.map((s) => s.name) ?? []
|
|
4611
|
+
};
|
|
4612
|
+
const cleanupMcp = mcpToolset?.cleanup;
|
|
4613
|
+
return {
|
|
4614
|
+
id: contextId,
|
|
4615
|
+
spec: {
|
|
4616
|
+
...spec,
|
|
4617
|
+
instructions
|
|
4618
|
+
},
|
|
4619
|
+
tools: toolSet,
|
|
4620
|
+
metadata,
|
|
4621
|
+
cleanup: async () => {
|
|
4622
|
+
if (cleanupMcp) {
|
|
4623
|
+
await cleanupMcp();
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
};
|
|
4627
|
+
} catch (error) {
|
|
4628
|
+
if (mcpToolset) {
|
|
4629
|
+
await mcpToolset.cleanup().catch(() => {
|
|
4630
|
+
return;
|
|
4631
|
+
});
|
|
4632
|
+
}
|
|
4633
|
+
throw new ContextCreationError(this.name, createAgentI18n(this.config.locale).t("error.provider.contextCreation", {
|
|
4634
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4635
|
+
}), error instanceof Error ? error : undefined);
|
|
4636
|
+
}
|
|
4637
|
+
}
|
|
4638
|
+
async execute(context, params) {
|
|
4639
|
+
try {
|
|
4640
|
+
const sdk = await this.loadSDK();
|
|
4641
|
+
const systemPrompt = params.systemOverride ? `${context.spec.instructions}
|
|
4642
|
+
|
|
4643
|
+
${params.systemOverride}` : context.spec.instructions;
|
|
4644
|
+
const claudeContext = buildClaudeAgentContext(params.options);
|
|
4645
|
+
let session = createEmptyClaudeSession();
|
|
4646
|
+
session = appendUserMessage(session, params.prompt);
|
|
4647
|
+
const claudeTools = this.prepareToolsForSDK(context);
|
|
4648
|
+
const rawResponse = await sdk.execute({
|
|
4649
|
+
model: this.config.model,
|
|
4650
|
+
system: systemPrompt,
|
|
4651
|
+
messages: session.messages,
|
|
4652
|
+
tools: claudeTools,
|
|
4653
|
+
max_tokens: this.config.maxTokens,
|
|
4654
|
+
temperature: this.config.temperature,
|
|
4655
|
+
metadata: claudeContext,
|
|
4656
|
+
extended_thinking: this.config.extendedThinking,
|
|
4657
|
+
computer_use: this.config.computerUse
|
|
4658
|
+
});
|
|
4659
|
+
const response = rawResponse;
|
|
4660
|
+
const toolCalls = extractToolCalls(response);
|
|
4661
|
+
const toolResults = await this.executeTools(toolCalls, context, params);
|
|
4662
|
+
if (response.content) {
|
|
4663
|
+
const content = response.content;
|
|
4664
|
+
session = appendAssistantMessage(session, content);
|
|
4665
|
+
}
|
|
4666
|
+
return {
|
|
4667
|
+
text: this.extractTextContent(response),
|
|
4668
|
+
toolCalls: toolCalls.map((tc) => ({
|
|
4669
|
+
type: "tool-call",
|
|
4670
|
+
toolCallId: tc.toolCallId,
|
|
4671
|
+
toolName: tc.toolName,
|
|
4672
|
+
args: tc.args
|
|
4673
|
+
})),
|
|
4674
|
+
toolResults: toolResults.map((tr) => ({
|
|
4675
|
+
type: "tool-result",
|
|
4676
|
+
toolCallId: tr.toolCallId,
|
|
4677
|
+
toolName: tr.toolName,
|
|
4678
|
+
output: tr.output
|
|
4679
|
+
})),
|
|
4680
|
+
usage: {
|
|
4681
|
+
inputTokens: response.usage?.input_tokens ?? 0,
|
|
4682
|
+
outputTokens: response.usage?.output_tokens ?? 0
|
|
4683
|
+
},
|
|
4684
|
+
finishReason: this.mapStopReason(response.stop_reason),
|
|
4685
|
+
metadata: {
|
|
4686
|
+
sessionId: context.metadata?.sessionId,
|
|
4687
|
+
model: response.model
|
|
4688
|
+
}
|
|
4689
|
+
};
|
|
4690
|
+
} catch (error) {
|
|
4691
|
+
throw new ProviderExecutionError(this.name, createAgentI18n(this.config.locale).t("error.provider.executionFailed", {
|
|
4692
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4693
|
+
}), error instanceof Error ? error : undefined);
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
async* stream(context, params) {
|
|
4697
|
+
try {
|
|
4698
|
+
const sdk = await this.loadSDK();
|
|
4699
|
+
const systemPrompt = params.systemOverride ? `${context.spec.instructions}
|
|
4700
|
+
|
|
4701
|
+
${params.systemOverride}` : context.spec.instructions;
|
|
4702
|
+
const claudeContext = buildClaudeAgentContext(params.options);
|
|
4703
|
+
const claudeTools = this.prepareToolsForSDK(context);
|
|
4704
|
+
const stream = await sdk.stream({
|
|
4705
|
+
model: this.config.model,
|
|
4706
|
+
system: systemPrompt,
|
|
4707
|
+
messages: [{ role: "user", content: params.prompt }],
|
|
4708
|
+
tools: claudeTools,
|
|
4709
|
+
max_tokens: this.config.maxTokens,
|
|
4710
|
+
temperature: this.config.temperature,
|
|
4711
|
+
metadata: claudeContext,
|
|
4712
|
+
extended_thinking: this.config.extendedThinking,
|
|
4713
|
+
computer_use: this.config.computerUse
|
|
4714
|
+
});
|
|
4715
|
+
let fullText = "";
|
|
4716
|
+
const allToolCalls = [];
|
|
4717
|
+
const allToolResults = [];
|
|
4718
|
+
let stepIndex = 0;
|
|
4719
|
+
for await (const event of stream) {
|
|
4720
|
+
if (event.type === "content_block_delta" && event.delta?.type === "text_delta") {
|
|
4721
|
+
const text = event.delta.text ?? "";
|
|
4722
|
+
fullText += text;
|
|
4723
|
+
yield { type: "text", text };
|
|
4724
|
+
}
|
|
4725
|
+
if (event.type === "content_block_start" && event.content_block?.type === "tool_use") {
|
|
4726
|
+
const toolCall = {
|
|
4727
|
+
toolCallId: event.content_block.id ?? "",
|
|
4728
|
+
toolName: event.content_block.name ?? "",
|
|
4729
|
+
args: event.content_block.input
|
|
4730
|
+
};
|
|
4731
|
+
allToolCalls.push(toolCall);
|
|
4732
|
+
yield {
|
|
4733
|
+
type: "tool-call",
|
|
4734
|
+
toolCall: {
|
|
4735
|
+
type: "tool-call",
|
|
4736
|
+
...toolCall
|
|
4737
|
+
}
|
|
4738
|
+
};
|
|
4739
|
+
}
|
|
4740
|
+
if (event.type === "message_stop") {
|
|
4741
|
+
stepIndex++;
|
|
4742
|
+
yield { type: "step-complete", stepIndex };
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4745
|
+
for (const toolCall of allToolCalls) {
|
|
4746
|
+
const result = await this.executeTool(toolCall, context, params);
|
|
4747
|
+
allToolResults.push(result);
|
|
4748
|
+
yield {
|
|
4749
|
+
type: "tool-result",
|
|
4750
|
+
toolResult: {
|
|
4751
|
+
type: "tool-result",
|
|
4752
|
+
...result
|
|
4753
|
+
}
|
|
4754
|
+
};
|
|
4755
|
+
}
|
|
4756
|
+
yield {
|
|
4757
|
+
type: "done",
|
|
4758
|
+
result: {
|
|
4759
|
+
text: fullText,
|
|
4760
|
+
toolCalls: allToolCalls.map((tc) => ({
|
|
4761
|
+
type: "tool-call",
|
|
4762
|
+
...tc
|
|
4763
|
+
})),
|
|
4764
|
+
toolResults: allToolResults.map((tr) => ({
|
|
4765
|
+
type: "tool-result",
|
|
4766
|
+
...tr
|
|
4767
|
+
})),
|
|
4768
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
4769
|
+
finishReason: "stop"
|
|
4770
|
+
}
|
|
4771
|
+
};
|
|
4772
|
+
} catch (error) {
|
|
4773
|
+
throw new ProviderExecutionError(this.name, createAgentI18n(this.config.locale).t("error.provider.streamFailed", {
|
|
4774
|
+
error: error instanceof Error ? error.message : String(error)
|
|
4775
|
+
}), error instanceof Error ? error : undefined);
|
|
4776
|
+
}
|
|
4777
|
+
}
|
|
4778
|
+
async loadSDK() {
|
|
4779
|
+
try {
|
|
4780
|
+
const module = __require("@anthropic-ai/claude-agent-sdk");
|
|
4781
|
+
return module.default ?? module;
|
|
4782
|
+
} catch {
|
|
4783
|
+
throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.claudeSdkMissing"));
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4786
|
+
prepareToolsForSDK(context) {
|
|
4787
|
+
const i18n = createAgentI18n(this.config.locale);
|
|
4788
|
+
const toolsForSdk = [];
|
|
4789
|
+
for (const [toolName, externalTool] of Object.entries(context.tools)) {
|
|
4790
|
+
if (!externalTool.execute) {
|
|
4791
|
+
continue;
|
|
4792
|
+
}
|
|
4793
|
+
toolsForSdk.push({
|
|
4794
|
+
name: toolName,
|
|
4795
|
+
description: externalTool.description ?? i18n.t("tool.fallbackDescription", { name: toolName }),
|
|
4796
|
+
input_schema: this.normalizeToolSchemaForClaude(externalTool.inputSchema),
|
|
4797
|
+
requires_confirmation: externalTool.requiresApproval,
|
|
4798
|
+
execute: async (input) => {
|
|
4799
|
+
const result = await externalTool.execute?.(input);
|
|
4800
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
4801
|
+
}
|
|
4802
|
+
});
|
|
4803
|
+
}
|
|
4804
|
+
return toolsForSdk;
|
|
4805
|
+
}
|
|
4806
|
+
mcpToolToExternalTool(toolName, tool4) {
|
|
4807
|
+
return {
|
|
4808
|
+
name: toolName,
|
|
4809
|
+
description: tool4.description ?? createAgentI18n(this.config.locale).t("tool.fallbackDescription", {
|
|
4810
|
+
name: toolName
|
|
4811
|
+
}),
|
|
4812
|
+
inputSchema: this.normalizeExternalInputSchema(tool4.inputSchema),
|
|
4813
|
+
execute: async (input) => {
|
|
4814
|
+
if (!tool4.execute) {
|
|
4815
|
+
throw new Error(createAgentI18n(this.config.locale).t("error.toolNoExecuteHandler", {
|
|
4816
|
+
name: toolName
|
|
4817
|
+
}));
|
|
4818
|
+
}
|
|
4819
|
+
return tool4.execute(input, {
|
|
4820
|
+
toolCallId: `mcp-${randomUUID3()}`,
|
|
4821
|
+
messages: []
|
|
4822
|
+
});
|
|
4823
|
+
}
|
|
4824
|
+
};
|
|
4825
|
+
}
|
|
4826
|
+
normalizeExternalInputSchema(schema) {
|
|
4827
|
+
if (this.isRecord(schema)) {
|
|
4828
|
+
const type = schema["type"];
|
|
4829
|
+
if (type === "object" || schema["properties"]) {
|
|
4830
|
+
return schema;
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
return {
|
|
4834
|
+
type: "object",
|
|
4835
|
+
properties: {}
|
|
4836
|
+
};
|
|
4837
|
+
}
|
|
4838
|
+
normalizeToolSchemaForClaude(schema) {
|
|
4839
|
+
if (schema.type === "object") {
|
|
4840
|
+
return {
|
|
4841
|
+
type: "object",
|
|
4842
|
+
properties: schema.properties,
|
|
4843
|
+
required: schema.required,
|
|
4844
|
+
additionalProperties: schema.additionalProperties
|
|
4845
|
+
};
|
|
4846
|
+
}
|
|
4847
|
+
return {
|
|
4848
|
+
type: "object",
|
|
4849
|
+
properties: {
|
|
4850
|
+
value: schema
|
|
4851
|
+
},
|
|
4852
|
+
required: ["value"]
|
|
4853
|
+
};
|
|
4854
|
+
}
|
|
4855
|
+
isRecord(value) {
|
|
4856
|
+
return typeof value === "object" && value !== null;
|
|
4857
|
+
}
|
|
4858
|
+
async executeTool(toolCall, context, _params) {
|
|
4859
|
+
const tool4 = context.tools[toolCall.toolName];
|
|
4860
|
+
if (!tool4?.execute) {
|
|
4861
|
+
return {
|
|
4862
|
+
toolCallId: toolCall.toolCallId,
|
|
4863
|
+
toolName: toolCall.toolName,
|
|
4864
|
+
output: createAgentI18n(this.config.locale).t("error.toolNotFoundOrNoHandler", {
|
|
4865
|
+
name: toolCall.toolName
|
|
4866
|
+
})
|
|
4867
|
+
};
|
|
4868
|
+
}
|
|
4869
|
+
try {
|
|
4870
|
+
const output = await tool4.execute(toolCall.args);
|
|
4871
|
+
return {
|
|
4872
|
+
toolCallId: toolCall.toolCallId,
|
|
4873
|
+
toolName: toolCall.toolName,
|
|
4874
|
+
output
|
|
4875
|
+
};
|
|
4876
|
+
} catch (error) {
|
|
4877
|
+
return {
|
|
4878
|
+
toolCallId: toolCall.toolCallId,
|
|
4879
|
+
toolName: toolCall.toolName,
|
|
4880
|
+
output: `Error: ${error instanceof Error ? error.message : String(error)}`
|
|
4881
|
+
};
|
|
4882
|
+
}
|
|
4883
|
+
}
|
|
4884
|
+
async executeTools(toolCalls, context, params) {
|
|
4885
|
+
return Promise.all(toolCalls.map((tc) => this.executeTool(tc, context, params)));
|
|
4886
|
+
}
|
|
4887
|
+
extractTextContent(response) {
|
|
4888
|
+
if (!response.content) {
|
|
4889
|
+
return "";
|
|
4890
|
+
}
|
|
4891
|
+
return response.content.filter((block) => typeof block === "object" && block !== null && block.type === "text").map((block) => block.text).join("");
|
|
4892
|
+
}
|
|
4893
|
+
mapStopReason(stopReason) {
|
|
4894
|
+
switch (stopReason) {
|
|
4895
|
+
case "end_turn":
|
|
4896
|
+
case "stop_sequence":
|
|
4897
|
+
return "stop";
|
|
4898
|
+
case "tool_use":
|
|
4899
|
+
return "tool-calls";
|
|
4900
|
+
case "max_tokens":
|
|
4901
|
+
return "length";
|
|
4902
|
+
default:
|
|
4903
|
+
return "stop";
|
|
4904
|
+
}
|
|
4905
|
+
}
|
|
4906
|
+
}
|
|
4907
|
+
var init_adapter2 = __esm(() => {
|
|
4908
|
+
init_i18n();
|
|
4909
|
+
init_injector();
|
|
4910
|
+
init_mcp_client();
|
|
4911
|
+
init_types();
|
|
4912
|
+
init_tool_bridge();
|
|
4913
|
+
});
|
|
4914
|
+
|
|
4915
|
+
// src/providers/claude-agent-sdk/index.ts
|
|
4916
|
+
var exports_claude_agent_sdk = {};
|
|
4917
|
+
__export(exports_claude_agent_sdk, {
|
|
4918
|
+
updateSessionMetadata: () => updateSessionMetadata,
|
|
4919
|
+
toClaudeAgentSession: () => toClaudeAgentSession,
|
|
4920
|
+
summarizeSession: () => summarizeSession,
|
|
4921
|
+
specToolsToClaudeAgentTools: () => specToolsToClaudeAgentTools,
|
|
4922
|
+
specToolToExternalTool: () => specToolToExternalTool,
|
|
4923
|
+
specToolToClaudeAgentTool: () => specToolToClaudeAgentTool,
|
|
4924
|
+
getRecentMessages: () => getRecentMessages,
|
|
4925
|
+
getMessageCount: () => getMessageCount,
|
|
4926
|
+
extractToolUseBlocks: () => extractToolUseBlocks,
|
|
4927
|
+
extractToolCalls: () => extractToolCalls,
|
|
4928
|
+
estimateTokens: () => estimateTokens,
|
|
4929
|
+
createToolResultBlock: () => createToolResultBlock,
|
|
4930
|
+
createEmptyClaudeSession: () => createEmptyClaudeSession,
|
|
4931
|
+
createClaudeAgentSession: () => createClaudeAgentSession,
|
|
4932
|
+
clearSession: () => clearSession,
|
|
4933
|
+
claudeAgentToolsToSpecTools: () => claudeAgentToolsToSpecTools,
|
|
4934
|
+
claudeAgentToolToSpecTool: () => claudeAgentToolToSpecTool,
|
|
4935
|
+
buildClaudeAgentContext: () => buildClaudeAgentContext,
|
|
4936
|
+
appendUserMessage: () => appendUserMessage,
|
|
4937
|
+
appendAssistantMessage: () => appendAssistantMessage,
|
|
4938
|
+
ClaudeAgentSDKProvider: () => ClaudeAgentSDKProvider
|
|
4939
|
+
});
|
|
4940
|
+
var init_claude_agent_sdk = __esm(() => {
|
|
4941
|
+
init_adapter2();
|
|
4942
|
+
init_tool_bridge();
|
|
4943
|
+
});
|
|
4944
|
+
|
|
4945
|
+
// src/providers/opencode-sdk/tool-bridge.ts
|
|
4946
|
+
function specToolToOpenCodeTool(tool4) {
|
|
4947
|
+
return {
|
|
4948
|
+
name: tool4.name,
|
|
4949
|
+
description: tool4.description ?? createAgentI18n().t("tool.fallbackDescription", { name: tool4.name }),
|
|
4950
|
+
parameters: normalizeToOpenCodeParameters(tool4.schema),
|
|
4951
|
+
permission: getPermissionLevel(tool4)
|
|
4952
|
+
};
|
|
4953
|
+
}
|
|
4954
|
+
function specToolsToOpenCodeTools(tools) {
|
|
4955
|
+
return tools.map(specToolToOpenCodeTool);
|
|
4956
|
+
}
|
|
4957
|
+
function getPermissionLevel(tool4) {
|
|
4958
|
+
if (tool4.requiresApproval) {
|
|
4959
|
+
return "ask";
|
|
4960
|
+
}
|
|
4961
|
+
if (tool4.automationSafe === false) {
|
|
4962
|
+
return "ask";
|
|
4963
|
+
}
|
|
4964
|
+
return "allow";
|
|
4965
|
+
}
|
|
4966
|
+
function openCodeToolToSpecTool(openCodeTool) {
|
|
4967
|
+
return {
|
|
4968
|
+
name: openCodeTool.name,
|
|
4969
|
+
description: openCodeTool.description,
|
|
4970
|
+
schema: openCodeTool.parameters,
|
|
4971
|
+
requiresApproval: openCodeTool.permission === "ask",
|
|
4972
|
+
automationSafe: openCodeTool.permission === "allow"
|
|
4973
|
+
};
|
|
4974
|
+
}
|
|
4975
|
+
function openCodeToolsToSpecTools(openCodeTools) {
|
|
4976
|
+
return openCodeTools.map(openCodeToolToSpecTool);
|
|
4977
|
+
}
|
|
4978
|
+
function specToolToExternalToolForOpenCode(tool4, handler, context) {
|
|
4979
|
+
return {
|
|
4980
|
+
name: tool4.name,
|
|
4981
|
+
description: tool4.description ?? createAgentI18n().t("tool.fallbackDescription", { name: tool4.name }),
|
|
4982
|
+
inputSchema: tool4.schema ?? { type: "object" },
|
|
4983
|
+
requiresApproval: tool4.requiresApproval ?? !tool4.automationSafe,
|
|
4984
|
+
execute: handler ? async (input) => {
|
|
4985
|
+
const fullContext = {
|
|
4986
|
+
agentId: context?.agentId ?? "unknown",
|
|
4987
|
+
sessionId: context?.sessionId ?? "unknown",
|
|
4988
|
+
tenantId: context?.tenantId,
|
|
4989
|
+
actorId: context?.actorId,
|
|
4990
|
+
metadata: context?.metadata,
|
|
4991
|
+
signal: context?.signal
|
|
4992
|
+
};
|
|
4993
|
+
return handler(input, fullContext);
|
|
4994
|
+
} : undefined
|
|
4995
|
+
};
|
|
4996
|
+
}
|
|
4997
|
+
function normalizeToOpenCodeParameters(schema) {
|
|
4998
|
+
if (!schema) {
|
|
4999
|
+
return { type: "object" };
|
|
5000
|
+
}
|
|
5001
|
+
if (schema.type === "object") {
|
|
5002
|
+
return {
|
|
5003
|
+
type: "object",
|
|
5004
|
+
properties: schema.properties,
|
|
5005
|
+
required: schema.required
|
|
5006
|
+
};
|
|
5007
|
+
}
|
|
5008
|
+
return {
|
|
5009
|
+
type: "object",
|
|
5010
|
+
properties: {
|
|
5011
|
+
value: convertToOpenCodeParameter(schema)
|
|
5012
|
+
},
|
|
5013
|
+
required: ["value"]
|
|
5014
|
+
};
|
|
5015
|
+
}
|
|
5016
|
+
function convertToOpenCodeParameter(schema) {
|
|
5017
|
+
const param = {
|
|
5018
|
+
type: schema.type ?? "string"
|
|
5019
|
+
};
|
|
5020
|
+
if (schema.description) {
|
|
5021
|
+
param.description = schema.description;
|
|
5022
|
+
}
|
|
5023
|
+
if (schema.enum) {
|
|
5024
|
+
param.enum = schema.enum;
|
|
5025
|
+
}
|
|
5026
|
+
if (schema.default !== undefined) {
|
|
5027
|
+
param.default = schema.default;
|
|
5028
|
+
}
|
|
5029
|
+
return param;
|
|
5030
|
+
}
|
|
5031
|
+
function createToolHandlerMap(tools) {
|
|
5032
|
+
const handlers = new Map;
|
|
5033
|
+
for (const [name, tool4] of Object.entries(tools)) {
|
|
5034
|
+
if (tool4.execute) {
|
|
5035
|
+
handlers.set(name, tool4.execute);
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
return handlers;
|
|
5039
|
+
}
|
|
5040
|
+
async function executeToolCall(toolCall, handlers) {
|
|
5041
|
+
const handler = handlers.get(toolCall.name);
|
|
5042
|
+
if (!handler) {
|
|
5043
|
+
return {
|
|
5044
|
+
tool_call_id: toolCall.id,
|
|
5045
|
+
output: createAgentI18n().t("error.toolNotFoundOrNoHandler", {
|
|
5046
|
+
name: toolCall.name
|
|
5047
|
+
}),
|
|
5048
|
+
is_error: true
|
|
5049
|
+
};
|
|
5050
|
+
}
|
|
5051
|
+
try {
|
|
5052
|
+
const result = await handler(toolCall.arguments);
|
|
5053
|
+
return {
|
|
5054
|
+
tool_call_id: toolCall.id,
|
|
5055
|
+
output: typeof result === "string" ? result : JSON.stringify(result)
|
|
5056
|
+
};
|
|
5057
|
+
} catch (error) {
|
|
5058
|
+
return {
|
|
5059
|
+
tool_call_id: toolCall.id,
|
|
5060
|
+
output: `Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
5061
|
+
is_error: true
|
|
5062
|
+
};
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
5065
|
+
var init_tool_bridge2 = __esm(() => {
|
|
5066
|
+
init_i18n();
|
|
5067
|
+
});
|
|
5068
|
+
|
|
5069
|
+
// src/providers/opencode-sdk/agent-bridge.ts
|
|
5070
|
+
function inferAgentType(spec) {
|
|
5071
|
+
const hasCodeTools = spec.tools.some((tool4) => [
|
|
5072
|
+
"write_file",
|
|
5073
|
+
"edit_file",
|
|
5074
|
+
"create_file",
|
|
5075
|
+
"delete_file",
|
|
5076
|
+
"bash",
|
|
5077
|
+
"execute",
|
|
5078
|
+
"run_command",
|
|
5079
|
+
"terminal"
|
|
5080
|
+
].includes(tool4.name.toLowerCase()));
|
|
5081
|
+
const instructionsLower = spec.instructions.toLowerCase();
|
|
5082
|
+
const hasPlanningKeywords = /\b(plan|design|architect|strategy|analyze|review|assess|evaluate)\b/.test(instructionsLower);
|
|
5083
|
+
const hasExplorationKeywords = /\b(search|find|explore|discover|locate|grep|pattern)\b/.test(instructionsLower);
|
|
5084
|
+
const hasResearchKeywords = /\b(research|investigate|understand|learn|gather|collect)\b/.test(instructionsLower);
|
|
5085
|
+
if (hasCodeTools) {
|
|
5086
|
+
return "build";
|
|
5087
|
+
}
|
|
5088
|
+
if (hasPlanningKeywords && !hasCodeTools) {
|
|
5089
|
+
return "plan";
|
|
5090
|
+
}
|
|
5091
|
+
if (hasExplorationKeywords && !hasResearchKeywords) {
|
|
5092
|
+
return "explore";
|
|
5093
|
+
}
|
|
5094
|
+
return "general";
|
|
5095
|
+
}
|
|
5096
|
+
function specToOpenCodeConfig(spec, options) {
|
|
5097
|
+
const agentType = options?.agentType ?? inferAgentType(spec);
|
|
5098
|
+
return {
|
|
5099
|
+
name: spec.meta.key,
|
|
5100
|
+
version: spec.meta.version,
|
|
5101
|
+
description: spec.description ?? spec.meta.description,
|
|
5102
|
+
instructions: spec.instructions,
|
|
5103
|
+
agent_type: agentType,
|
|
5104
|
+
tools: specToolsToOpenCodeTools(spec.tools),
|
|
5105
|
+
config: {
|
|
5106
|
+
max_steps: options?.maxSteps ?? spec.maxSteps ?? 10,
|
|
5107
|
+
temperature: options?.temperature ?? 0.7,
|
|
5108
|
+
model: options?.model,
|
|
5109
|
+
permissions: buildPermissions(spec)
|
|
5110
|
+
}
|
|
5111
|
+
};
|
|
5112
|
+
}
|
|
5113
|
+
function buildPermissions(spec) {
|
|
5114
|
+
const permissions = {};
|
|
5115
|
+
for (const tool4 of spec.tools) {
|
|
5116
|
+
if (tool4.requiresApproval) {
|
|
5117
|
+
permissions[tool4.name] = "ask";
|
|
5118
|
+
} else if (tool4.automationSafe === false) {
|
|
5119
|
+
permissions[tool4.name] = "ask";
|
|
5120
|
+
} else {
|
|
5121
|
+
permissions[tool4.name] = "allow";
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
return permissions;
|
|
5125
|
+
}
|
|
5126
|
+
function specToOpenCodeMarkdown(spec, options) {
|
|
5127
|
+
const agentType = options?.agentType ?? inferAgentType(spec);
|
|
5128
|
+
return {
|
|
5129
|
+
frontmatter: {
|
|
5130
|
+
name: spec.meta.key,
|
|
5131
|
+
type: agentType,
|
|
5132
|
+
version: spec.meta.version,
|
|
5133
|
+
model: options?.model,
|
|
5134
|
+
temperature: options?.temperature ?? 0.7,
|
|
5135
|
+
max_steps: options?.maxSteps ?? spec.maxSteps ?? 10,
|
|
5136
|
+
tools: spec.tools.map((t) => t.name)
|
|
5137
|
+
},
|
|
5138
|
+
body: buildMarkdownBody(spec)
|
|
5139
|
+
};
|
|
5140
|
+
}
|
|
5141
|
+
function buildMarkdownBody(spec) {
|
|
5142
|
+
const i18n = createAgentI18n(spec.locale);
|
|
5143
|
+
const lines = [];
|
|
5144
|
+
lines.push(`# ${spec.meta.key}`);
|
|
5145
|
+
lines.push("");
|
|
5146
|
+
if (spec.description ?? spec.meta.description) {
|
|
5147
|
+
lines.push(spec.description ?? spec.meta.description ?? "");
|
|
5148
|
+
lines.push("");
|
|
5149
|
+
}
|
|
5150
|
+
lines.push(i18n.t("export.instructions"));
|
|
5151
|
+
lines.push("");
|
|
5152
|
+
lines.push(spec.instructions);
|
|
5153
|
+
lines.push("");
|
|
5154
|
+
if (spec.tools.length > 0) {
|
|
5155
|
+
lines.push(i18n.t("export.tools"));
|
|
5156
|
+
lines.push("");
|
|
5157
|
+
for (const tool4 of spec.tools) {
|
|
5158
|
+
const permission = tool4.requiresApproval ? i18n.t("export.bridge.requiresApproval") : tool4.automationSafe === false ? i18n.t("export.bridge.askMode") : "";
|
|
5159
|
+
lines.push(`- **${tool4.name}**: ${tool4.description ?? i18n.t("export.noDescription")} ${permission}`.trim());
|
|
2161
5160
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
5161
|
+
lines.push("");
|
|
5162
|
+
}
|
|
5163
|
+
if (spec.knowledge && spec.knowledge.length > 0) {
|
|
5164
|
+
lines.push(i18n.t("export.knowledgeSources"));
|
|
5165
|
+
lines.push("");
|
|
5166
|
+
for (const k of spec.knowledge) {
|
|
5167
|
+
const required = k.required ? i18n.t("export.required") : i18n.t("export.optional");
|
|
5168
|
+
lines.push(`- ${k.key} ${required}`);
|
|
2165
5169
|
}
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
5170
|
+
lines.push("");
|
|
5171
|
+
}
|
|
5172
|
+
return lines.join(`
|
|
5173
|
+
`);
|
|
5174
|
+
}
|
|
5175
|
+
function serializeOpenCodeMarkdown(markdown) {
|
|
5176
|
+
const lines = [];
|
|
5177
|
+
lines.push("---");
|
|
5178
|
+
lines.push(`name: ${markdown.frontmatter.name}`);
|
|
5179
|
+
lines.push(`type: ${markdown.frontmatter.type}`);
|
|
5180
|
+
if (markdown.frontmatter.version) {
|
|
5181
|
+
lines.push(`version: ${markdown.frontmatter.version}`);
|
|
5182
|
+
}
|
|
5183
|
+
if (markdown.frontmatter.model) {
|
|
5184
|
+
lines.push(`model: ${markdown.frontmatter.model}`);
|
|
5185
|
+
}
|
|
5186
|
+
if (markdown.frontmatter.temperature !== undefined) {
|
|
5187
|
+
lines.push(`temperature: ${markdown.frontmatter.temperature}`);
|
|
5188
|
+
}
|
|
5189
|
+
if (markdown.frontmatter.max_steps !== undefined) {
|
|
5190
|
+
lines.push(`max_steps: ${markdown.frontmatter.max_steps}`);
|
|
5191
|
+
}
|
|
5192
|
+
if (markdown.frontmatter.tools && markdown.frontmatter.tools.length > 0) {
|
|
5193
|
+
lines.push(`tools:`);
|
|
5194
|
+
for (const tool4 of markdown.frontmatter.tools) {
|
|
5195
|
+
lines.push(` - ${tool4}`);
|
|
2173
5196
|
}
|
|
2174
5197
|
}
|
|
2175
|
-
|
|
5198
|
+
lines.push("---");
|
|
5199
|
+
lines.push("");
|
|
5200
|
+
lines.push(markdown.body);
|
|
5201
|
+
return lines.join(`
|
|
5202
|
+
`);
|
|
5203
|
+
}
|
|
5204
|
+
function openCodeConfigToSpec(config) {
|
|
5205
|
+
return {
|
|
5206
|
+
meta: {
|
|
5207
|
+
key: config.name,
|
|
5208
|
+
version: config.version ?? "1.0.0",
|
|
5209
|
+
description: config.description ?? "",
|
|
5210
|
+
stability: "experimental",
|
|
5211
|
+
owners: [],
|
|
5212
|
+
tags: []
|
|
5213
|
+
},
|
|
5214
|
+
description: config.description,
|
|
5215
|
+
instructions: config.instructions ?? "",
|
|
5216
|
+
tools: config.tools?.map((tool4) => ({
|
|
5217
|
+
name: tool4.name,
|
|
5218
|
+
description: tool4.description,
|
|
5219
|
+
schema: tool4.parameters,
|
|
5220
|
+
requiresApproval: tool4.permission === "ask",
|
|
5221
|
+
automationSafe: tool4.permission === "allow"
|
|
5222
|
+
})) ?? [],
|
|
5223
|
+
maxSteps: config.config?.max_steps ?? 10
|
|
5224
|
+
};
|
|
2176
5225
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
5226
|
+
var init_agent_bridge = __esm(() => {
|
|
5227
|
+
init_i18n();
|
|
5228
|
+
init_tool_bridge2();
|
|
5229
|
+
});
|
|
5230
|
+
|
|
5231
|
+
// src/providers/opencode-sdk/adapter.ts
|
|
5232
|
+
import {
|
|
5233
|
+
agentKey as agentKey3
|
|
5234
|
+
} from "@contractspec/lib.contracts-spec/agent";
|
|
5235
|
+
|
|
5236
|
+
class OpenCodeSDKProvider {
|
|
5237
|
+
name = "opencode-sdk";
|
|
5238
|
+
version = "1.0.0";
|
|
5239
|
+
config;
|
|
5240
|
+
sdkAvailable = null;
|
|
5241
|
+
constructor(config = {}) {
|
|
5242
|
+
this.config = {
|
|
5243
|
+
serverUrl: "http://127.0.0.1:4096",
|
|
5244
|
+
port: 4096,
|
|
5245
|
+
agentType: "general",
|
|
5246
|
+
temperature: 0.7,
|
|
5247
|
+
timeout: 30000,
|
|
5248
|
+
...config
|
|
5249
|
+
};
|
|
5250
|
+
if (!config.serverUrl && config.port) {
|
|
5251
|
+
this.config.serverUrl = `http://127.0.0.1:${config.port}`;
|
|
5252
|
+
}
|
|
5253
|
+
}
|
|
5254
|
+
isAvailable() {
|
|
5255
|
+
if (this.sdkAvailable !== null) {
|
|
5256
|
+
return this.sdkAvailable;
|
|
5257
|
+
}
|
|
5258
|
+
try {
|
|
5259
|
+
__require.resolve("@opencode-ai/sdk");
|
|
5260
|
+
this.sdkAvailable = true;
|
|
5261
|
+
} catch {
|
|
5262
|
+
this.sdkAvailable = false;
|
|
5263
|
+
}
|
|
5264
|
+
return this.sdkAvailable;
|
|
5265
|
+
}
|
|
5266
|
+
async createContext(spec) {
|
|
5267
|
+
if (!this.isAvailable()) {
|
|
5268
|
+
throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.sdkNotInstalled"));
|
|
5269
|
+
}
|
|
5270
|
+
try {
|
|
5271
|
+
const sdk = await this.loadSDK();
|
|
5272
|
+
const { client } = await sdk.createOpencode({
|
|
5273
|
+
hostname: this.getHostname(),
|
|
5274
|
+
port: this.config.port ?? 4096,
|
|
5275
|
+
timeout: this.config.timeout
|
|
5276
|
+
});
|
|
5277
|
+
const session = await client.session.create({
|
|
5278
|
+
agent: this.config.agentType ?? inferAgentType(spec),
|
|
5279
|
+
model: this.config.model
|
|
5280
|
+
});
|
|
5281
|
+
const toolSet = {};
|
|
5282
|
+
for (const tool4 of spec.tools) {
|
|
5283
|
+
toolSet[tool4.name] = specToolToExternalToolForOpenCode(tool4);
|
|
5284
|
+
}
|
|
5285
|
+
const instructions = await injectStaticKnowledge(spec.instructions, spec.knowledge ?? [], undefined);
|
|
5286
|
+
const contextId = `opencode-${agentKey3(spec.meta)}-${Date.now()}`;
|
|
5287
|
+
const metadata = {
|
|
5288
|
+
sessionId: session.id,
|
|
5289
|
+
agentType: this.config.agentType ?? inferAgentType(spec),
|
|
5290
|
+
serverUrl: this.config.serverUrl ?? `http://127.0.0.1:${this.config.port ?? 4096}`
|
|
5291
|
+
};
|
|
5292
|
+
return {
|
|
5293
|
+
id: contextId,
|
|
5294
|
+
spec: {
|
|
5295
|
+
...spec,
|
|
5296
|
+
instructions
|
|
5297
|
+
},
|
|
5298
|
+
tools: toolSet,
|
|
5299
|
+
metadata,
|
|
5300
|
+
cleanup: async () => {
|
|
5301
|
+
try {
|
|
5302
|
+
await client.session.delete({ id: session.id });
|
|
5303
|
+
} catch {}
|
|
5304
|
+
}
|
|
5305
|
+
};
|
|
5306
|
+
} catch (error) {
|
|
5307
|
+
throw new ContextCreationError(this.name, createAgentI18n(this.config.locale).t("error.provider.contextCreation", {
|
|
5308
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5309
|
+
}), error instanceof Error ? error : undefined);
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
async execute(context, params) {
|
|
5313
|
+
try {
|
|
5314
|
+
const sdk = await this.loadSDK();
|
|
5315
|
+
const metadata = context.metadata;
|
|
5316
|
+
const { client } = await sdk.createOpencodeClient({
|
|
5317
|
+
baseUrl: metadata.serverUrl
|
|
5318
|
+
});
|
|
5319
|
+
const systemPrompt = params.systemOverride ? `${context.spec.instructions}
|
|
5320
|
+
|
|
5321
|
+
${params.systemOverride}` : context.spec.instructions;
|
|
5322
|
+
const response = await client.session.prompt({
|
|
5323
|
+
id: metadata.sessionId,
|
|
5324
|
+
body: {
|
|
5325
|
+
content: params.prompt,
|
|
5326
|
+
system: systemPrompt
|
|
5327
|
+
}
|
|
5328
|
+
});
|
|
5329
|
+
const toolCalls = this.extractToolCalls(response);
|
|
5330
|
+
const toolHandlers = createToolHandlerMap(context.tools);
|
|
5331
|
+
const toolResults = await Promise.all(toolCalls.map((tc) => executeToolCall(tc, toolHandlers)));
|
|
5332
|
+
return {
|
|
5333
|
+
text: this.extractTextContent(response),
|
|
5334
|
+
toolCalls: toolCalls.map((tc) => ({
|
|
5335
|
+
type: "tool-call",
|
|
5336
|
+
toolCallId: tc.id,
|
|
5337
|
+
toolName: tc.name,
|
|
5338
|
+
args: tc.arguments
|
|
5339
|
+
})),
|
|
5340
|
+
toolResults: toolResults.map((tr) => ({
|
|
5341
|
+
type: "tool-result",
|
|
5342
|
+
toolCallId: tr.tool_call_id,
|
|
5343
|
+
toolName: toolCalls.find((tc) => tc.id === tr.tool_call_id)?.name ?? "",
|
|
5344
|
+
output: tr.output
|
|
5345
|
+
})),
|
|
5346
|
+
usage: {
|
|
5347
|
+
inputTokens: response.usage?.input_tokens ?? 0,
|
|
5348
|
+
outputTokens: response.usage?.output_tokens ?? 0
|
|
5349
|
+
},
|
|
5350
|
+
finishReason: this.mapFinishReason(response.finish_reason),
|
|
5351
|
+
metadata: {
|
|
5352
|
+
sessionId: metadata.sessionId,
|
|
5353
|
+
agentType: metadata.agentType
|
|
5354
|
+
}
|
|
5355
|
+
};
|
|
5356
|
+
} catch (error) {
|
|
5357
|
+
throw new ProviderExecutionError(this.name, createAgentI18n(this.config.locale).t("error.provider.executionFailed", {
|
|
5358
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5359
|
+
}), error instanceof Error ? error : undefined);
|
|
5360
|
+
}
|
|
5361
|
+
}
|
|
5362
|
+
async* stream(context, params) {
|
|
5363
|
+
try {
|
|
5364
|
+
const sdk = await this.loadSDK();
|
|
5365
|
+
const metadata = context.metadata;
|
|
5366
|
+
const { client } = await sdk.createOpencodeClient({
|
|
5367
|
+
baseUrl: metadata.serverUrl
|
|
5368
|
+
});
|
|
5369
|
+
const systemPrompt = params.systemOverride ? `${context.spec.instructions}
|
|
5370
|
+
|
|
5371
|
+
${params.systemOverride}` : context.spec.instructions;
|
|
5372
|
+
const events = client.session.subscribe({
|
|
5373
|
+
id: metadata.sessionId
|
|
5374
|
+
});
|
|
5375
|
+
await client.session.prompt({
|
|
5376
|
+
id: metadata.sessionId,
|
|
5377
|
+
body: {
|
|
5378
|
+
content: params.prompt,
|
|
5379
|
+
system: systemPrompt
|
|
5380
|
+
}
|
|
5381
|
+
});
|
|
5382
|
+
let fullText = "";
|
|
5383
|
+
const allToolCalls = [];
|
|
5384
|
+
const toolHandlers = createToolHandlerMap(context.tools);
|
|
5385
|
+
let stepIndex = 0;
|
|
5386
|
+
for await (const event of events) {
|
|
5387
|
+
if (event.type === "message.delta") {
|
|
5388
|
+
const text = event.delta?.content ?? "";
|
|
5389
|
+
fullText += text;
|
|
5390
|
+
yield { type: "text", text };
|
|
5391
|
+
}
|
|
5392
|
+
if (event.type === "tool.call") {
|
|
5393
|
+
const toolCall = {
|
|
5394
|
+
id: event.tool_call_id ?? `tc-${Date.now()}`,
|
|
5395
|
+
name: event.tool_name ?? "",
|
|
5396
|
+
arguments: event.arguments ?? {}
|
|
5397
|
+
};
|
|
5398
|
+
allToolCalls.push(toolCall);
|
|
5399
|
+
yield {
|
|
5400
|
+
type: "tool-call",
|
|
5401
|
+
toolCall: {
|
|
5402
|
+
type: "tool-call",
|
|
5403
|
+
toolCallId: toolCall.id,
|
|
5404
|
+
toolName: toolCall.name,
|
|
5405
|
+
args: toolCall.arguments
|
|
5406
|
+
}
|
|
5407
|
+
};
|
|
5408
|
+
}
|
|
5409
|
+
if (event.type === "step.complete") {
|
|
5410
|
+
stepIndex++;
|
|
5411
|
+
yield { type: "step-complete", stepIndex };
|
|
5412
|
+
}
|
|
5413
|
+
if (event.type === "message.complete") {
|
|
5414
|
+
break;
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
for (const toolCall of allToolCalls) {
|
|
5418
|
+
const result = await executeToolCall(toolCall, toolHandlers);
|
|
5419
|
+
yield {
|
|
5420
|
+
type: "tool-result",
|
|
5421
|
+
toolResult: {
|
|
5422
|
+
type: "tool-result",
|
|
5423
|
+
toolCallId: result.tool_call_id,
|
|
5424
|
+
toolName: toolCall.name,
|
|
5425
|
+
output: result.output
|
|
5426
|
+
}
|
|
5427
|
+
};
|
|
5428
|
+
}
|
|
5429
|
+
yield {
|
|
5430
|
+
type: "done",
|
|
5431
|
+
result: {
|
|
5432
|
+
text: fullText,
|
|
5433
|
+
toolCalls: allToolCalls.map((tc) => ({
|
|
5434
|
+
type: "tool-call",
|
|
5435
|
+
toolCallId: tc.id,
|
|
5436
|
+
toolName: tc.name,
|
|
5437
|
+
args: tc.arguments
|
|
5438
|
+
})),
|
|
5439
|
+
toolResults: [],
|
|
5440
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
5441
|
+
finishReason: "stop"
|
|
5442
|
+
}
|
|
5443
|
+
};
|
|
5444
|
+
} catch (error) {
|
|
5445
|
+
throw new ProviderExecutionError(this.name, createAgentI18n(this.config.locale).t("error.provider.streamFailed", {
|
|
5446
|
+
error: error instanceof Error ? error.message : String(error)
|
|
5447
|
+
}), error instanceof Error ? error : undefined);
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
async loadSDK() {
|
|
5451
|
+
try {
|
|
5452
|
+
const module = __require("@opencode-ai/sdk");
|
|
5453
|
+
return module;
|
|
5454
|
+
} catch {
|
|
5455
|
+
throw new ProviderNotAvailableError(this.name, createAgentI18n(this.config.locale).t("error.provider.opencodeSdkMissing"));
|
|
5456
|
+
}
|
|
5457
|
+
}
|
|
5458
|
+
getHostname() {
|
|
5459
|
+
if (!this.config.serverUrl) {
|
|
5460
|
+
return "127.0.0.1";
|
|
5461
|
+
}
|
|
5462
|
+
try {
|
|
5463
|
+
const url = new URL(this.config.serverUrl);
|
|
5464
|
+
return url.hostname;
|
|
5465
|
+
} catch {
|
|
5466
|
+
return "127.0.0.1";
|
|
5467
|
+
}
|
|
5468
|
+
}
|
|
5469
|
+
extractToolCalls(response) {
|
|
5470
|
+
if (!response.tool_calls) {
|
|
5471
|
+
return [];
|
|
5472
|
+
}
|
|
5473
|
+
return response.tool_calls.map((tc) => ({
|
|
5474
|
+
id: tc.id,
|
|
5475
|
+
name: tc.name,
|
|
5476
|
+
arguments: tc.arguments ?? {}
|
|
5477
|
+
}));
|
|
5478
|
+
}
|
|
5479
|
+
extractTextContent(response) {
|
|
5480
|
+
return response.content ?? response.message?.content ?? "";
|
|
5481
|
+
}
|
|
5482
|
+
mapFinishReason(finishReason) {
|
|
5483
|
+
switch (finishReason) {
|
|
5484
|
+
case "stop":
|
|
5485
|
+
case "end":
|
|
5486
|
+
return "stop";
|
|
5487
|
+
case "tool_use":
|
|
5488
|
+
case "tool_calls":
|
|
5489
|
+
return "tool-calls";
|
|
5490
|
+
case "length":
|
|
5491
|
+
case "max_tokens":
|
|
5492
|
+
return "length";
|
|
5493
|
+
default:
|
|
5494
|
+
return "stop";
|
|
5495
|
+
}
|
|
5496
|
+
}
|
|
2179
5497
|
}
|
|
2180
|
-
var
|
|
5498
|
+
var init_adapter3 = __esm(() => {
|
|
2181
5499
|
init_i18n();
|
|
5500
|
+
init_injector();
|
|
5501
|
+
init_types();
|
|
5502
|
+
init_agent_bridge();
|
|
5503
|
+
init_tool_bridge2();
|
|
5504
|
+
});
|
|
5505
|
+
|
|
5506
|
+
// src/providers/opencode-sdk/index.ts
|
|
5507
|
+
var exports_opencode_sdk = {};
|
|
5508
|
+
__export(exports_opencode_sdk, {
|
|
5509
|
+
specToolsToOpenCodeTools: () => specToolsToOpenCodeTools,
|
|
5510
|
+
specToolToOpenCodeTool: () => specToolToOpenCodeTool,
|
|
5511
|
+
specToolToExternalToolForOpenCode: () => specToolToExternalToolForOpenCode,
|
|
5512
|
+
specToOpenCodeMarkdown: () => specToOpenCodeMarkdown,
|
|
5513
|
+
specToOpenCodeConfig: () => specToOpenCodeConfig,
|
|
5514
|
+
serializeOpenCodeMarkdown: () => serializeOpenCodeMarkdown,
|
|
5515
|
+
openCodeToolsToSpecTools: () => openCodeToolsToSpecTools,
|
|
5516
|
+
openCodeToolToSpecTool: () => openCodeToolToSpecTool,
|
|
5517
|
+
openCodeConfigToSpec: () => openCodeConfigToSpec,
|
|
5518
|
+
inferAgentType: () => inferAgentType,
|
|
5519
|
+
executeToolCall: () => executeToolCall,
|
|
5520
|
+
createToolHandlerMap: () => createToolHandlerMap,
|
|
5521
|
+
OpenCodeSDKProvider: () => OpenCodeSDKProvider
|
|
5522
|
+
});
|
|
5523
|
+
var init_opencode_sdk = __esm(() => {
|
|
5524
|
+
init_adapter3();
|
|
5525
|
+
init_agent_bridge();
|
|
5526
|
+
init_tool_bridge2();
|
|
2182
5527
|
});
|
|
2183
5528
|
|
|
2184
|
-
// src/
|
|
5529
|
+
// src/agent/json-runner.ts
|
|
2185
5530
|
init_i18n();
|
|
2186
|
-
|
|
2187
|
-
import {
|
|
5531
|
+
init_contract_spec_agent();
|
|
5532
|
+
import { createProvider } from "@contractspec/lib.ai-providers/factory";
|
|
5533
|
+
import { StabilityEnum } from "@contractspec/lib.contracts-spec/ownership";
|
|
5534
|
+
function getJsonOnlyRules(locale) {
|
|
5535
|
+
const i18n = createAgentI18n(locale);
|
|
5536
|
+
return [
|
|
5537
|
+
i18n.t("agent.json.rules.validJsonOnly"),
|
|
5538
|
+
i18n.t("agent.json.rules.noMarkdownFences"),
|
|
5539
|
+
i18n.t("agent.json.rules.noCommentary"),
|
|
5540
|
+
i18n.t("agent.json.rules.doubleQuotes"),
|
|
5541
|
+
i18n.t("agent.json.rules.noTrailingCommas")
|
|
5542
|
+
].join(`
|
|
5543
|
+
`);
|
|
5544
|
+
}
|
|
5545
|
+
function getDefaultSpec(locale) {
|
|
5546
|
+
const i18n = createAgentI18n(locale);
|
|
5547
|
+
return {
|
|
5548
|
+
meta: {
|
|
5549
|
+
key: "agent.json-runner",
|
|
5550
|
+
version: "1.0.0",
|
|
5551
|
+
description: i18n.t("agent.json.defaultDescription"),
|
|
5552
|
+
stability: StabilityEnum.Experimental,
|
|
5553
|
+
owners: ["platform.core"],
|
|
5554
|
+
tags: ["json", "agent"]
|
|
5555
|
+
},
|
|
5556
|
+
instructions: i18n.t("agent.json.systemPrompt"),
|
|
5557
|
+
tools: []
|
|
5558
|
+
};
|
|
5559
|
+
}
|
|
5560
|
+
async function resolveModel(options) {
|
|
5561
|
+
if (options.modelSelector && options.selectionContext) {
|
|
5562
|
+
const { model } = await options.modelSelector.selectAndCreate(options.selectionContext);
|
|
5563
|
+
return model;
|
|
5564
|
+
}
|
|
5565
|
+
if (options.model)
|
|
5566
|
+
return options.model;
|
|
5567
|
+
if (options.provider) {
|
|
5568
|
+
return createProvider(options.provider).getModel();
|
|
5569
|
+
}
|
|
5570
|
+
throw new Error(createAgentI18n(options.locale).t("error.jsonRunner.requiresModel"));
|
|
5571
|
+
}
|
|
5572
|
+
function applyModelSettings(model, settings) {
|
|
5573
|
+
const { temperature } = settings;
|
|
5574
|
+
if (temperature === undefined)
|
|
5575
|
+
return model;
|
|
5576
|
+
const withSettings = model;
|
|
5577
|
+
if (typeof withSettings.withSettings === "function") {
|
|
5578
|
+
return withSettings.withSettings({ temperature });
|
|
5579
|
+
}
|
|
5580
|
+
return model;
|
|
5581
|
+
}
|
|
5582
|
+
function buildInstructions(base, locale, system) {
|
|
5583
|
+
return [base, getJsonOnlyRules(locale), system].filter(Boolean).join(`
|
|
2188
5584
|
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
names.add(spec.meta.key);
|
|
2197
|
-
}
|
|
2198
|
-
return [...names];
|
|
2199
|
-
}
|
|
2200
|
-
require(name, version, locale) {
|
|
2201
|
-
const spec = this.get(name, version);
|
|
2202
|
-
if (!spec) {
|
|
2203
|
-
throw new Error(createAgentI18n(locale).t("error.agentSpecNotFound", {
|
|
2204
|
-
name: `${name}${version != null ? `.v${version}` : ""}`
|
|
5585
|
+
`);
|
|
5586
|
+
}
|
|
5587
|
+
function ensureToolHandlers(spec, handlers, locale) {
|
|
5588
|
+
for (const tool4 of spec.tools) {
|
|
5589
|
+
if (!handlers.has(tool4.name)) {
|
|
5590
|
+
throw new Error(createAgentI18n(locale).t("error.missingToolHandler", {
|
|
5591
|
+
name: tool4.name
|
|
2205
5592
|
}));
|
|
2206
5593
|
}
|
|
2207
|
-
return spec;
|
|
2208
5594
|
}
|
|
2209
|
-
|
|
2210
|
-
|
|
5595
|
+
}
|
|
5596
|
+
async function createAgentJsonRunner(options) {
|
|
5597
|
+
const resolved = await resolveModel(options);
|
|
5598
|
+
const model = applyModelSettings(resolved, {
|
|
5599
|
+
temperature: options.temperature ?? 0
|
|
5600
|
+
});
|
|
5601
|
+
const baseSpec = options.spec ?? getDefaultSpec(options.locale);
|
|
5602
|
+
const spec = {
|
|
5603
|
+
...baseSpec,
|
|
5604
|
+
locale: options.spec?.locale ?? options.locale,
|
|
5605
|
+
instructions: buildInstructions(baseSpec.instructions, options.locale, options.system),
|
|
5606
|
+
maxSteps: options.maxSteps ?? baseSpec.maxSteps
|
|
5607
|
+
};
|
|
5608
|
+
const toolHandlers = options.toolHandlers ?? new Map;
|
|
5609
|
+
ensureToolHandlers(spec, toolHandlers, options.locale);
|
|
5610
|
+
const agent = await ContractSpecAgent.create({
|
|
5611
|
+
spec,
|
|
5612
|
+
model,
|
|
5613
|
+
toolHandlers
|
|
5614
|
+
});
|
|
5615
|
+
return {
|
|
5616
|
+
async generateJson(prompt) {
|
|
5617
|
+
const result = await agent.generate({ prompt });
|
|
5618
|
+
return result.text;
|
|
5619
|
+
}
|
|
5620
|
+
};
|
|
5621
|
+
}
|
|
5622
|
+
|
|
5623
|
+
// src/agent/unified-agent.ts
|
|
5624
|
+
init_i18n();
|
|
5625
|
+
|
|
5626
|
+
class UnifiedAgent {
|
|
5627
|
+
spec;
|
|
5628
|
+
config;
|
|
5629
|
+
tools;
|
|
5630
|
+
provider;
|
|
5631
|
+
context;
|
|
5632
|
+
state;
|
|
5633
|
+
constructor(spec, config) {
|
|
5634
|
+
this.spec = spec;
|
|
5635
|
+
this.config = config;
|
|
5636
|
+
this.tools = config.tools ?? new Map;
|
|
5637
|
+
this.state = {
|
|
5638
|
+
backend: config.backend,
|
|
5639
|
+
isReady: false,
|
|
5640
|
+
messageCount: 0
|
|
5641
|
+
};
|
|
5642
|
+
}
|
|
5643
|
+
i18n(runtimeLocale) {
|
|
5644
|
+
return createAgentI18n(this.spec.locale, runtimeLocale ?? this.config.locale);
|
|
5645
|
+
}
|
|
5646
|
+
async initialize() {
|
|
5647
|
+
const backend = this.config.backend;
|
|
5648
|
+
try {
|
|
5649
|
+
switch (backend) {
|
|
5650
|
+
case "ai-sdk":
|
|
5651
|
+
this.state.isReady = true;
|
|
5652
|
+
break;
|
|
5653
|
+
case "claude-agent-sdk":
|
|
5654
|
+
await this.initializeClaudeAgentSDK();
|
|
5655
|
+
break;
|
|
5656
|
+
case "opencode-sdk":
|
|
5657
|
+
await this.initializeOpenCodeSDK();
|
|
5658
|
+
break;
|
|
5659
|
+
default:
|
|
5660
|
+
throw new Error(this.i18n().t("error.unknownBackend", {
|
|
5661
|
+
backend: String(backend)
|
|
5662
|
+
}));
|
|
5663
|
+
}
|
|
5664
|
+
} catch (error) {
|
|
5665
|
+
this.state.lastError = error instanceof Error ? error : new Error(String(error));
|
|
5666
|
+
if (this.config.fallbackBackend && this.config.fallbackBackend !== backend) {
|
|
5667
|
+
await this.cleanupProviderContext();
|
|
5668
|
+
console.warn(this.i18n().t("log.unifiedAgent.fallback", {
|
|
5669
|
+
backend: String(backend),
|
|
5670
|
+
fallback: String(this.config.fallbackBackend)
|
|
5671
|
+
}));
|
|
5672
|
+
this.state.backend = this.config.fallbackBackend;
|
|
5673
|
+
await this.initialize();
|
|
5674
|
+
} else {
|
|
5675
|
+
throw error;
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
}
|
|
5679
|
+
async initializeClaudeAgentSDK() {
|
|
5680
|
+
try {
|
|
5681
|
+
const { ClaudeAgentSDKProvider: ClaudeAgentSDKProvider2 } = await Promise.resolve().then(() => (init_claude_agent_sdk(), exports_claude_agent_sdk));
|
|
5682
|
+
const config = this.config.config;
|
|
5683
|
+
this.provider = new ClaudeAgentSDKProvider2(config ?? {});
|
|
5684
|
+
if (!this.provider.isAvailable()) {
|
|
5685
|
+
throw new Error(this.i18n().t("error.claudeSdk.notAvailable"));
|
|
5686
|
+
}
|
|
5687
|
+
this.context = await this.provider.createContext(this.spec);
|
|
5688
|
+
this.state.isReady = true;
|
|
5689
|
+
} catch (error) {
|
|
5690
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
5691
|
+
throw new Error(this.i18n().t("error.claudeSdk.notInstalled"));
|
|
5692
|
+
}
|
|
5693
|
+
throw error;
|
|
5694
|
+
}
|
|
5695
|
+
}
|
|
5696
|
+
async initializeOpenCodeSDK() {
|
|
5697
|
+
try {
|
|
5698
|
+
const { OpenCodeSDKProvider: OpenCodeSDKProvider2 } = await Promise.resolve().then(() => (init_opencode_sdk(), exports_opencode_sdk));
|
|
5699
|
+
const config = this.config.config;
|
|
5700
|
+
this.provider = new OpenCodeSDKProvider2(config ?? {});
|
|
5701
|
+
if (!this.provider.isAvailable()) {
|
|
5702
|
+
throw new Error(this.i18n().t("error.opencodeSdk.notAvailable"));
|
|
5703
|
+
}
|
|
5704
|
+
this.context = await this.provider.createContext(this.spec);
|
|
5705
|
+
this.state.isReady = true;
|
|
5706
|
+
} catch (error) {
|
|
5707
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
5708
|
+
throw new Error(this.i18n().t("error.opencodeSdk.notInstalled"));
|
|
5709
|
+
}
|
|
5710
|
+
throw error;
|
|
5711
|
+
}
|
|
2211
5712
|
}
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
5713
|
+
async run(message, options) {
|
|
5714
|
+
if (!this.state.isReady) {
|
|
5715
|
+
await this.initialize();
|
|
5716
|
+
}
|
|
5717
|
+
const backend = options?.backend ?? this.state.backend;
|
|
5718
|
+
this.state.messageCount++;
|
|
5719
|
+
try {
|
|
5720
|
+
switch (backend) {
|
|
5721
|
+
case "ai-sdk":
|
|
5722
|
+
return await this.runWithAISDK(message, options);
|
|
5723
|
+
case "claude-agent-sdk":
|
|
5724
|
+
case "opencode-sdk":
|
|
5725
|
+
return await this.runWithExternalProvider(message, options);
|
|
5726
|
+
default:
|
|
5727
|
+
throw new Error(this.i18n().t("error.unknownBackend", {
|
|
5728
|
+
backend: String(backend)
|
|
5729
|
+
}));
|
|
2217
5730
|
}
|
|
5731
|
+
} catch (error) {
|
|
5732
|
+
this.state.lastError = error instanceof Error ? error : new Error(String(error));
|
|
5733
|
+
throw error;
|
|
5734
|
+
}
|
|
5735
|
+
}
|
|
5736
|
+
async runWithAISDK(message, options) {
|
|
5737
|
+
const { ContractSpecAgent: ContractSpecAgent2 } = await Promise.resolve().then(() => (init_contract_spec_agent(), exports_contract_spec_agent));
|
|
5738
|
+
const backendConfig = this.getAISDKConfig();
|
|
5739
|
+
const model = await this.resolveAISDKModel();
|
|
5740
|
+
const agent = await ContractSpecAgent2.create({
|
|
5741
|
+
spec: this.spec,
|
|
5742
|
+
model,
|
|
5743
|
+
toolHandlers: this.tools,
|
|
5744
|
+
mcpServers: backendConfig?.mcpServers
|
|
5745
|
+
});
|
|
5746
|
+
try {
|
|
5747
|
+
return await agent.generate({
|
|
5748
|
+
prompt: message,
|
|
5749
|
+
options
|
|
5750
|
+
});
|
|
5751
|
+
} finally {
|
|
5752
|
+
await agent.cleanup().catch(() => {
|
|
5753
|
+
return;
|
|
5754
|
+
});
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
async runWithExternalProvider(message, options) {
|
|
5758
|
+
if (!this.provider || !this.context) {
|
|
5759
|
+
throw new Error(this.i18n().t("error.providerNotInitialized"));
|
|
5760
|
+
}
|
|
5761
|
+
const result = await this.provider.execute(this.context, {
|
|
5762
|
+
prompt: message,
|
|
5763
|
+
options
|
|
5764
|
+
});
|
|
5765
|
+
return this.convertExternalResult(result);
|
|
5766
|
+
}
|
|
5767
|
+
convertExternalResult(result) {
|
|
5768
|
+
return {
|
|
5769
|
+
text: result.text,
|
|
5770
|
+
steps: [],
|
|
5771
|
+
toolCalls: result.toolCalls.map((tc) => ({
|
|
5772
|
+
type: "tool-call",
|
|
5773
|
+
toolCallId: tc.toolCallId,
|
|
5774
|
+
toolName: tc.toolName,
|
|
5775
|
+
args: tc.args
|
|
5776
|
+
})),
|
|
5777
|
+
toolResults: result.toolResults.map((tr) => ({
|
|
5778
|
+
type: "tool-result",
|
|
5779
|
+
toolCallId: tr.toolCallId,
|
|
5780
|
+
toolName: tr.toolName,
|
|
5781
|
+
output: tr.output
|
|
5782
|
+
})),
|
|
5783
|
+
finishReason: result.finishReason,
|
|
5784
|
+
usage: result.usage ? {
|
|
5785
|
+
promptTokens: result.usage.inputTokens,
|
|
5786
|
+
completionTokens: result.usage.outputTokens,
|
|
5787
|
+
totalTokens: result.usage.totalTokens ?? result.usage.inputTokens + result.usage.outputTokens
|
|
5788
|
+
} : undefined
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
getAISDKConfig() {
|
|
5792
|
+
if (this.config.backend !== "ai-sdk")
|
|
5793
|
+
return;
|
|
5794
|
+
return this.config.config;
|
|
5795
|
+
}
|
|
5796
|
+
async resolveAISDKModel() {
|
|
5797
|
+
const backendConfig = this.getAISDKConfig();
|
|
5798
|
+
let model;
|
|
5799
|
+
if (backendConfig?.modelSelector && backendConfig.selectionContext) {
|
|
5800
|
+
const result = await backendConfig.modelSelector.selectAndCreate(backendConfig.selectionContext);
|
|
5801
|
+
model = result.model;
|
|
5802
|
+
} else if (backendConfig?.modelInstance) {
|
|
5803
|
+
model = backendConfig.modelInstance;
|
|
5804
|
+
} else if (backendConfig?.provider) {
|
|
5805
|
+
const { createProvider: createProvider2 } = await import("@contractspec/lib.ai-providers/factory");
|
|
5806
|
+
model = createProvider2(backendConfig.provider).getModel();
|
|
5807
|
+
} else {
|
|
5808
|
+
const { anthropic: anthropic2 } = await import("@ai-sdk/anthropic");
|
|
5809
|
+
model = anthropic2(backendConfig?.model ?? "claude-3-5-sonnet-20240620");
|
|
5810
|
+
}
|
|
5811
|
+
return this.applyModelSettings(model, {
|
|
5812
|
+
temperature: backendConfig?.temperature,
|
|
5813
|
+
maxTokens: backendConfig?.maxTokens
|
|
5814
|
+
});
|
|
5815
|
+
}
|
|
5816
|
+
applyModelSettings(model, settings) {
|
|
5817
|
+
if (settings.temperature === undefined && settings.maxTokens === undefined) {
|
|
5818
|
+
return model;
|
|
5819
|
+
}
|
|
5820
|
+
const withSettings = model;
|
|
5821
|
+
if (typeof withSettings.withSettings === "function") {
|
|
5822
|
+
return withSettings.withSettings({
|
|
5823
|
+
temperature: settings.temperature,
|
|
5824
|
+
maxTokens: settings.maxTokens
|
|
5825
|
+
});
|
|
5826
|
+
}
|
|
5827
|
+
return model;
|
|
5828
|
+
}
|
|
5829
|
+
getState() {
|
|
5830
|
+
return { ...this.state };
|
|
5831
|
+
}
|
|
5832
|
+
getSpec() {
|
|
5833
|
+
return this.spec;
|
|
5834
|
+
}
|
|
5835
|
+
getBackend() {
|
|
5836
|
+
return this.state.backend;
|
|
5837
|
+
}
|
|
5838
|
+
async isBackendAvailable(backend) {
|
|
5839
|
+
switch (backend) {
|
|
5840
|
+
case "ai-sdk":
|
|
5841
|
+
return true;
|
|
5842
|
+
case "claude-agent-sdk":
|
|
5843
|
+
try {
|
|
5844
|
+
const { ClaudeAgentSDKProvider: ClaudeAgentSDKProvider2 } = await Promise.resolve().then(() => (init_claude_agent_sdk(), exports_claude_agent_sdk));
|
|
5845
|
+
const provider = new ClaudeAgentSDKProvider2({});
|
|
5846
|
+
return provider.isAvailable();
|
|
5847
|
+
} catch {
|
|
5848
|
+
return false;
|
|
5849
|
+
}
|
|
5850
|
+
case "opencode-sdk":
|
|
5851
|
+
try {
|
|
5852
|
+
const { OpenCodeSDKProvider: OpenCodeSDKProvider2 } = await Promise.resolve().then(() => (init_opencode_sdk(), exports_opencode_sdk));
|
|
5853
|
+
const provider = new OpenCodeSDKProvider2({});
|
|
5854
|
+
return provider.isAvailable();
|
|
5855
|
+
} catch {
|
|
5856
|
+
return false;
|
|
5857
|
+
}
|
|
5858
|
+
default:
|
|
5859
|
+
return false;
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5862
|
+
async switchBackend(backend) {
|
|
5863
|
+
if (backend === this.state.backend) {
|
|
5864
|
+
return;
|
|
5865
|
+
}
|
|
5866
|
+
await this.cleanupProviderContext();
|
|
5867
|
+
this.state.backend = backend;
|
|
5868
|
+
this.state.isReady = false;
|
|
5869
|
+
this.provider = undefined;
|
|
5870
|
+
this.context = undefined;
|
|
5871
|
+
await this.initialize();
|
|
5872
|
+
}
|
|
5873
|
+
reset() {
|
|
5874
|
+
this.state.messageCount = 0;
|
|
5875
|
+
this.state.sessionId = undefined;
|
|
5876
|
+
this.state.lastError = undefined;
|
|
5877
|
+
this.cleanupProviderContext().catch(() => {
|
|
5878
|
+
return;
|
|
5879
|
+
});
|
|
5880
|
+
this.context = undefined;
|
|
5881
|
+
}
|
|
5882
|
+
async cleanupProviderContext() {
|
|
5883
|
+
const context = this.context;
|
|
5884
|
+
this.context = undefined;
|
|
5885
|
+
if (context) {
|
|
5886
|
+
await context.cleanup().catch(() => {
|
|
5887
|
+
return;
|
|
5888
|
+
});
|
|
2218
5889
|
}
|
|
2219
|
-
return versions.sort((a, b) => compareVersions(a.meta.version, b.meta.version));
|
|
2220
5890
|
}
|
|
5891
|
+
addTool(name, handler) {
|
|
5892
|
+
this.tools.set(name, handler);
|
|
5893
|
+
}
|
|
5894
|
+
removeTool(name) {
|
|
5895
|
+
return this.tools.delete(name);
|
|
5896
|
+
}
|
|
5897
|
+
}
|
|
5898
|
+
function createUnifiedAgent(spec, config) {
|
|
5899
|
+
return new UnifiedAgent(spec, config);
|
|
5900
|
+
}
|
|
5901
|
+
function createAISDKAgent(spec, options) {
|
|
5902
|
+
return new UnifiedAgent(spec, {
|
|
5903
|
+
backend: "ai-sdk",
|
|
5904
|
+
tools: options?.tools,
|
|
5905
|
+
config: {
|
|
5906
|
+
model: options?.model,
|
|
5907
|
+
modelInstance: options?.modelInstance,
|
|
5908
|
+
provider: options?.provider,
|
|
5909
|
+
temperature: options?.temperature,
|
|
5910
|
+
maxTokens: options?.maxTokens,
|
|
5911
|
+
mcpServers: options?.mcpServers
|
|
5912
|
+
}
|
|
5913
|
+
});
|
|
5914
|
+
}
|
|
5915
|
+
function createClaudeAgentSDKAgent(spec, config) {
|
|
5916
|
+
const { tools, ...sdkConfig } = config ?? {};
|
|
5917
|
+
return new UnifiedAgent(spec, {
|
|
5918
|
+
backend: "claude-agent-sdk",
|
|
5919
|
+
tools,
|
|
5920
|
+
config: sdkConfig
|
|
5921
|
+
});
|
|
5922
|
+
}
|
|
5923
|
+
function createOpenCodeSDKAgent(spec, config) {
|
|
5924
|
+
const { tools, ...sdkConfig } = config ?? {};
|
|
5925
|
+
return new UnifiedAgent(spec, {
|
|
5926
|
+
backend: "opencode-sdk",
|
|
5927
|
+
tools,
|
|
5928
|
+
config: sdkConfig
|
|
5929
|
+
});
|
|
2221
5930
|
}
|
|
2222
|
-
function
|
|
2223
|
-
|
|
5931
|
+
async function getAvailableBackends() {
|
|
5932
|
+
const backends = ["ai-sdk"];
|
|
5933
|
+
try {
|
|
5934
|
+
const { ClaudeAgentSDKProvider: ClaudeAgentSDKProvider2 } = await Promise.resolve().then(() => (init_claude_agent_sdk(), exports_claude_agent_sdk));
|
|
5935
|
+
const provider = new ClaudeAgentSDKProvider2({});
|
|
5936
|
+
if (provider.isAvailable()) {
|
|
5937
|
+
backends.push("claude-agent-sdk");
|
|
5938
|
+
}
|
|
5939
|
+
} catch {}
|
|
5940
|
+
try {
|
|
5941
|
+
const { OpenCodeSDKProvider: OpenCodeSDKProvider2 } = await Promise.resolve().then(() => (init_opencode_sdk(), exports_opencode_sdk));
|
|
5942
|
+
const provider = new OpenCodeSDKProvider2({});
|
|
5943
|
+
if (provider.isAvailable()) {
|
|
5944
|
+
backends.push("opencode-sdk");
|
|
5945
|
+
}
|
|
5946
|
+
} catch {}
|
|
5947
|
+
return backends;
|
|
2224
5948
|
}
|
|
2225
5949
|
|
|
2226
|
-
// src/
|
|
2227
|
-
|
|
5950
|
+
// src/agent/index.ts
|
|
5951
|
+
init_contract_spec_agent();
|
|
5952
|
+
|
|
5953
|
+
// src/approval/index.ts
|
|
5954
|
+
init_workflow();
|
|
2228
5955
|
export {
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
5956
|
+
getAvailableBackends,
|
|
5957
|
+
createUnifiedAgent,
|
|
5958
|
+
createOpenCodeSDKAgent,
|
|
5959
|
+
createClaudeAgentSDKAgent,
|
|
5960
|
+
createApprovalWorkflow,
|
|
5961
|
+
createAgentJsonRunner,
|
|
5962
|
+
createAISDKAgent,
|
|
5963
|
+
UnifiedAgent,
|
|
5964
|
+
InMemoryApprovalStore,
|
|
5965
|
+
ContractSpecAgent,
|
|
5966
|
+
ApprovalWorkflow
|
|
2233
5967
|
};
|