@contractspec/lib.ai-agent 2.9.1 → 3.1.1
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 +14 -2
- package/dist/agent/agent-factory.d.ts +3 -0
- package/dist/agent/agent-factory.js +209 -27
- package/dist/agent/contract-spec-agent.d.ts +3 -0
- package/dist/agent/contract-spec-agent.js +207 -26
- package/dist/agent/index.js +218 -29
- package/dist/agent/json-runner.d.ts +3 -0
- package/dist/agent/json-runner.js +214 -28
- package/dist/agent/unified-agent.d.ts +3 -0
- package/dist/agent/unified-agent.js +211 -27
- package/dist/exporters/claude-agent-exporter.js +5 -0
- package/dist/exporters/index.js +5 -0
- package/dist/exporters/opencode-exporter.js +5 -0
- package/dist/index.js +5 -0
- package/dist/interop/index.d.ts +1 -0
- package/dist/interop/index.js +5 -0
- package/dist/interop/runtime-adapters.d.ts +36 -0
- package/dist/interop/runtime-adapters.js +1 -0
- package/dist/interop/spec-consumer.js +5 -0
- package/dist/node/agent/agent-factory.js +209 -27
- package/dist/node/agent/contract-spec-agent.js +207 -26
- package/dist/node/agent/index.js +218 -29
- package/dist/node/agent/json-runner.js +214 -28
- package/dist/node/agent/unified-agent.js +211 -27
- package/dist/node/exporters/claude-agent-exporter.js +5 -0
- package/dist/node/exporters/index.js +5 -0
- package/dist/node/exporters/opencode-exporter.js +5 -0
- package/dist/node/index.js +5 -0
- package/dist/node/interop/index.js +5 -0
- package/dist/node/interop/runtime-adapters.js +0 -0
- package/dist/node/interop/spec-consumer.js +5 -0
- package/dist/node/providers/claude-agent-sdk/adapter.js +5 -0
- package/dist/node/providers/claude-agent-sdk/index.js +5 -0
- package/dist/node/providers/index.js +5 -0
- package/dist/node/providers/opencode-sdk/adapter.js +5 -0
- package/dist/node/providers/opencode-sdk/index.js +5 -0
- package/dist/node/spec/index.js +5 -0
- package/dist/node/spec/spec.js +5 -0
- package/dist/node/tools/index.js +86 -10
- package/dist/node/tools/tool-adapter.js +86 -10
- package/dist/providers/claude-agent-sdk/adapter.js +5 -0
- package/dist/providers/claude-agent-sdk/index.js +5 -0
- package/dist/providers/index.js +5 -0
- package/dist/providers/opencode-sdk/adapter.js +5 -0
- package/dist/providers/opencode-sdk/index.js +5 -0
- package/dist/spec/index.js +5 -0
- package/dist/spec/spec.d.ts +27 -0
- package/dist/spec/spec.js +5 -0
- package/dist/spec/spec.test.d.ts +1 -0
- package/dist/tools/index.js +86 -10
- package/dist/tools/mcp-client.d.ts +6 -0
- package/dist/tools/mcp-server.d.ts +4 -0
- package/dist/tools/tool-adapter.js +86 -10
- package/dist/tools/tool-adapter.test.d.ts +1 -0
- package/dist/types.d.ts +15 -0
- package/package.json +27 -15
|
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
|
|
|
2142
2142
|
if (!spec.tools?.length) {
|
|
2143
2143
|
throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
|
|
2144
2144
|
}
|
|
2145
|
+
for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
|
|
2146
|
+
if (portRef !== undefined && portRef.trim().length === 0) {
|
|
2147
|
+
throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2145
2150
|
const toolNames = new Set;
|
|
2146
2151
|
for (const tool of spec.tools) {
|
|
2147
2152
|
if (toolNames.has(tool.name)) {
|
|
@@ -2297,21 +2302,39 @@ var init_json_schema_to_zod = () => {};
|
|
|
2297
2302
|
// src/tools/tool-adapter.ts
|
|
2298
2303
|
import { tool } from "ai";
|
|
2299
2304
|
function specToolToAISDKTool(specTool, handler, context = {}) {
|
|
2305
|
+
let lastInvocationAt;
|
|
2300
2306
|
return tool({
|
|
2301
2307
|
description: specTool.description ?? specTool.name,
|
|
2302
2308
|
inputSchema: jsonSchemaToZodSafe(specTool.schema),
|
|
2303
2309
|
needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
|
|
2304
2310
|
execute: async (input) => {
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2311
|
+
const now = Date.now();
|
|
2312
|
+
const cooldownMs = normalizeDuration(specTool.cooldownMs);
|
|
2313
|
+
if (cooldownMs && lastInvocationAt !== undefined) {
|
|
2314
|
+
const elapsed = now - lastInvocationAt;
|
|
2315
|
+
if (elapsed < cooldownMs) {
|
|
2316
|
+
const retryAfterMs = cooldownMs - elapsed;
|
|
2317
|
+
throw createToolExecutionError(`Tool "${specTool.name}" is cooling down. Retry in ${retryAfterMs}ms.`, "TOOL_COOLDOWN_ACTIVE", retryAfterMs);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const timeoutMs = normalizeDuration(specTool.timeoutMs);
|
|
2321
|
+
const { signal, dispose } = createTimeoutSignal(context.signal, timeoutMs);
|
|
2322
|
+
try {
|
|
2323
|
+
const execution = handler(input, {
|
|
2324
|
+
agentId: context.agentId ?? "unknown",
|
|
2325
|
+
sessionId: context.sessionId ?? "unknown",
|
|
2326
|
+
tenantId: context.tenantId,
|
|
2327
|
+
actorId: context.actorId,
|
|
2328
|
+
locale: context.locale,
|
|
2329
|
+
metadata: context.metadata,
|
|
2330
|
+
signal
|
|
2331
|
+
});
|
|
2332
|
+
const result = timeoutMs ? await withTimeout(execution, timeoutMs, specTool.name) : await execution;
|
|
2333
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
2334
|
+
} finally {
|
|
2335
|
+
dispose();
|
|
2336
|
+
lastInvocationAt = Date.now();
|
|
2337
|
+
}
|
|
2315
2338
|
}
|
|
2316
2339
|
});
|
|
2317
2340
|
}
|
|
@@ -2336,6 +2359,64 @@ function createToolHandler(handler) {
|
|
|
2336
2359
|
function buildToolHandlers(handlersObj) {
|
|
2337
2360
|
return new Map(Object.entries(handlersObj));
|
|
2338
2361
|
}
|
|
2362
|
+
function normalizeDuration(value) {
|
|
2363
|
+
if (value === undefined) {
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
if (!Number.isFinite(value)) {
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
if (value <= 0) {
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
return Math.round(value);
|
|
2373
|
+
}
|
|
2374
|
+
function withTimeout(execution, timeoutMs, toolName) {
|
|
2375
|
+
return new Promise((resolve, reject) => {
|
|
2376
|
+
const timeoutHandle = setTimeout(() => {
|
|
2377
|
+
reject(createToolExecutionError(`Tool "${toolName}" timed out after ${timeoutMs}ms.`, "TOOL_EXECUTION_TIMEOUT"));
|
|
2378
|
+
}, timeoutMs);
|
|
2379
|
+
execution.then((result) => {
|
|
2380
|
+
clearTimeout(timeoutHandle);
|
|
2381
|
+
resolve(result);
|
|
2382
|
+
}).catch((error) => {
|
|
2383
|
+
clearTimeout(timeoutHandle);
|
|
2384
|
+
reject(error);
|
|
2385
|
+
});
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
function createTimeoutSignal(signal, timeoutMs) {
|
|
2389
|
+
const controller = new AbortController;
|
|
2390
|
+
const abortFromSource = () => controller.abort();
|
|
2391
|
+
if (signal) {
|
|
2392
|
+
if (signal.aborted) {
|
|
2393
|
+
controller.abort();
|
|
2394
|
+
} else {
|
|
2395
|
+
signal.addEventListener("abort", abortFromSource);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
const timeoutHandle = timeoutMs !== undefined ? setTimeout(() => {
|
|
2399
|
+
controller.abort();
|
|
2400
|
+
}, timeoutMs) : undefined;
|
|
2401
|
+
return {
|
|
2402
|
+
signal: controller.signal,
|
|
2403
|
+
dispose: () => {
|
|
2404
|
+
if (timeoutHandle !== undefined) {
|
|
2405
|
+
clearTimeout(timeoutHandle);
|
|
2406
|
+
}
|
|
2407
|
+
if (signal) {
|
|
2408
|
+
signal.removeEventListener("abort", abortFromSource);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
function createToolExecutionError(message, code, retryAfterMs) {
|
|
2414
|
+
return Object.assign(new Error(message), {
|
|
2415
|
+
code,
|
|
2416
|
+
kind: "retryable",
|
|
2417
|
+
retryAfterMs
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2339
2420
|
var init_tool_adapter = __esm(() => {
|
|
2340
2421
|
init_json_schema_to_zod();
|
|
2341
2422
|
init_i18n();
|
|
@@ -3151,7 +3232,13 @@ class ContractSpecAgent {
|
|
|
3151
3232
|
steps: [],
|
|
3152
3233
|
metadata: params.options?.metadata
|
|
3153
3234
|
});
|
|
3235
|
+
} else if (existing.status !== "running") {
|
|
3236
|
+
await this.config.sessionStore.update(sessionId, { status: "running" });
|
|
3154
3237
|
}
|
|
3238
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3239
|
+
role: "user",
|
|
3240
|
+
content: params.prompt
|
|
3241
|
+
});
|
|
3155
3242
|
}
|
|
3156
3243
|
const prompt = params.systemOverride ? `${this.instructions}
|
|
3157
3244
|
|
|
@@ -3163,24 +3250,41 @@ ${params.prompt}` : params.prompt;
|
|
|
3163
3250
|
traceId,
|
|
3164
3251
|
options: params.options
|
|
3165
3252
|
});
|
|
3166
|
-
const
|
|
3167
|
-
const
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3253
|
+
const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
|
|
3254
|
+
const inner = this.createInnerAgent(model, effectiveMaxSteps);
|
|
3255
|
+
let result;
|
|
3256
|
+
try {
|
|
3257
|
+
result = await inner.generate({
|
|
3258
|
+
prompt,
|
|
3259
|
+
abortSignal: params.signal,
|
|
3260
|
+
options: {
|
|
3261
|
+
tenantId: params.options?.tenantId,
|
|
3262
|
+
actorId: params.options?.actorId,
|
|
3263
|
+
sessionId,
|
|
3264
|
+
metadata: params.options?.metadata
|
|
3265
|
+
}
|
|
3266
|
+
});
|
|
3267
|
+
} catch (error) {
|
|
3268
|
+
if (this.config.sessionStore) {
|
|
3269
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3270
|
+
status: "failed"
|
|
3271
|
+
});
|
|
3175
3272
|
}
|
|
3176
|
-
}).finally(() => {
|
|
3177
3273
|
this.activeStepContexts.delete(sessionId);
|
|
3178
|
-
|
|
3274
|
+
throw error;
|
|
3275
|
+
}
|
|
3276
|
+
this.activeStepContexts.delete(sessionId);
|
|
3277
|
+
const escalationError = resolveEscalationError(this.spec, result.finishReason);
|
|
3179
3278
|
if (this.config.sessionStore) {
|
|
3279
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3280
|
+
role: "assistant",
|
|
3281
|
+
content: result.text
|
|
3282
|
+
});
|
|
3180
3283
|
await this.config.sessionStore.update(sessionId, {
|
|
3181
|
-
status: "completed"
|
|
3284
|
+
status: escalationError ? "escalated" : "completed"
|
|
3182
3285
|
});
|
|
3183
3286
|
}
|
|
3287
|
+
const session = this.config.sessionStore ? await this.config.sessionStore.get(sessionId) : null;
|
|
3184
3288
|
return {
|
|
3185
3289
|
text: result.text,
|
|
3186
3290
|
steps: result.steps,
|
|
@@ -3197,7 +3301,16 @@ ${params.prompt}` : params.prompt;
|
|
|
3197
3301
|
output: tr.output
|
|
3198
3302
|
})),
|
|
3199
3303
|
finishReason: result.finishReason,
|
|
3200
|
-
usage: result.usage
|
|
3304
|
+
usage: result.usage,
|
|
3305
|
+
session: session ?? undefined,
|
|
3306
|
+
pendingApproval: escalationError ? {
|
|
3307
|
+
toolName: this.spec.policy?.escalation?.approvalWorkflow ?? "approval_required",
|
|
3308
|
+
toolCallId: `approval_${sessionId}`,
|
|
3309
|
+
args: {
|
|
3310
|
+
reason: escalationError.message,
|
|
3311
|
+
code: escalationError.code
|
|
3312
|
+
}
|
|
3313
|
+
} : undefined
|
|
3201
3314
|
};
|
|
3202
3315
|
}
|
|
3203
3316
|
async stream(params) {
|
|
@@ -3220,7 +3333,28 @@ ${params.prompt}` : params.prompt;
|
|
|
3220
3333
|
traceId,
|
|
3221
3334
|
options: params.options
|
|
3222
3335
|
});
|
|
3223
|
-
const
|
|
3336
|
+
const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
|
|
3337
|
+
const inner = this.createInnerAgent(model, effectiveMaxSteps);
|
|
3338
|
+
if (this.config.sessionStore) {
|
|
3339
|
+
const existing = await this.config.sessionStore.get(sessionId);
|
|
3340
|
+
if (!existing) {
|
|
3341
|
+
await this.config.sessionStore.create({
|
|
3342
|
+
sessionId,
|
|
3343
|
+
agentId: this.id,
|
|
3344
|
+
tenantId: params.options?.tenantId,
|
|
3345
|
+
actorId: params.options?.actorId,
|
|
3346
|
+
status: "running",
|
|
3347
|
+
messages: [],
|
|
3348
|
+
steps: [],
|
|
3349
|
+
metadata: params.options?.metadata
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3353
|
+
role: "user",
|
|
3354
|
+
content: params.prompt
|
|
3355
|
+
});
|
|
3356
|
+
await this.config.sessionStore.update(sessionId, { status: "running" });
|
|
3357
|
+
}
|
|
3224
3358
|
return inner.stream({
|
|
3225
3359
|
prompt,
|
|
3226
3360
|
abortSignal: params.signal,
|
|
@@ -3236,6 +3370,9 @@ ${params.prompt}` : params.prompt;
|
|
|
3236
3370
|
const sessionId = step.options?.sessionId;
|
|
3237
3371
|
if (sessionId && this.config.sessionStore) {
|
|
3238
3372
|
await this.config.sessionStore.appendStep(sessionId, step);
|
|
3373
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3374
|
+
status: step.finishReason === "tool-calls" ? "waiting" : "running"
|
|
3375
|
+
});
|
|
3239
3376
|
}
|
|
3240
3377
|
if (this.config.telemetryCollector) {
|
|
3241
3378
|
const now = new Date;
|
|
@@ -3259,12 +3396,12 @@ ${params.prompt}` : params.prompt;
|
|
|
3259
3396
|
}
|
|
3260
3397
|
}
|
|
3261
3398
|
}
|
|
3262
|
-
createInnerAgent(model) {
|
|
3399
|
+
createInnerAgent(model, maxSteps) {
|
|
3263
3400
|
return new ToolLoopAgent({
|
|
3264
3401
|
model,
|
|
3265
3402
|
instructions: this.instructions,
|
|
3266
3403
|
tools: this.tools,
|
|
3267
|
-
stopWhen: stepCountIs(
|
|
3404
|
+
stopWhen: stepCountIs(maxSteps),
|
|
3268
3405
|
callOptionsSchema: ContractSpecCallOptionsSchema,
|
|
3269
3406
|
onStepFinish: async (step) => {
|
|
3270
3407
|
await this.handleStepFinish(step);
|
|
@@ -3272,6 +3409,10 @@ ${params.prompt}` : params.prompt;
|
|
|
3272
3409
|
});
|
|
3273
3410
|
}
|
|
3274
3411
|
async resolveModelForCall(params) {
|
|
3412
|
+
if (this.config.modelSelector && params.options?.selectionContext) {
|
|
3413
|
+
const { model } = await this.config.modelSelector.selectAndCreate(params.options.selectionContext);
|
|
3414
|
+
return model;
|
|
3415
|
+
}
|
|
3275
3416
|
const posthogConfig = this.config.posthogConfig;
|
|
3276
3417
|
if (!posthogConfig) {
|
|
3277
3418
|
return this.config.model;
|
|
@@ -3296,6 +3437,46 @@ ${params.prompt}` : params.prompt;
|
|
|
3296
3437
|
return createPostHogTracedModel2(this.config.model, posthogConfig, tracingOptions);
|
|
3297
3438
|
}
|
|
3298
3439
|
}
|
|
3440
|
+
function resolveMaxSteps(overrideMaxSteps, specMaxSteps) {
|
|
3441
|
+
const candidate = overrideMaxSteps ?? specMaxSteps ?? 10;
|
|
3442
|
+
if (!Number.isFinite(candidate)) {
|
|
3443
|
+
return 10;
|
|
3444
|
+
}
|
|
3445
|
+
if (candidate < 1) {
|
|
3446
|
+
return 1;
|
|
3447
|
+
}
|
|
3448
|
+
return Math.round(candidate);
|
|
3449
|
+
}
|
|
3450
|
+
function resolveEscalationError(spec, finishReason) {
|
|
3451
|
+
const escalation = spec.policy?.escalation;
|
|
3452
|
+
if (!escalation) {
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
if (escalation.onTimeout && finishReason === "length") {
|
|
3456
|
+
return {
|
|
3457
|
+
kind: "timeout",
|
|
3458
|
+
code: "AGENT_TIMEOUT_ESCALATION",
|
|
3459
|
+
message: "Agent reached max step budget and requires escalation."
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
if (escalation.onToolFailure && finishReason === "error") {
|
|
3463
|
+
return {
|
|
3464
|
+
kind: "retryable",
|
|
3465
|
+
code: "AGENT_TOOL_FAILURE_ESCALATION",
|
|
3466
|
+
message: "Agent encountered a tool failure and requires escalation."
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
const confidenceThreshold = escalation.confidenceThreshold;
|
|
3470
|
+
const defaultConfidence = spec.policy?.confidence?.default;
|
|
3471
|
+
if (confidenceThreshold !== undefined && defaultConfidence !== undefined && defaultConfidence < confidenceThreshold) {
|
|
3472
|
+
return {
|
|
3473
|
+
kind: "policy_blocked",
|
|
3474
|
+
code: "AGENT_CONFIDENCE_ESCALATION",
|
|
3475
|
+
message: `Agent default confidence (${defaultConfidence}) is below escalation threshold (${confidenceThreshold}).`
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3299
3480
|
var ContractSpecCallOptionsSchema;
|
|
3300
3481
|
var init_contract_spec_agent = __esm(() => {
|
|
3301
3482
|
init_spec();
|
package/dist/agent/index.js
CHANGED
|
@@ -2142,6 +2142,11 @@ function defineAgent(spec) {
|
|
|
2142
2142
|
if (!spec.tools?.length) {
|
|
2143
2143
|
throw new Error(i18n.t("error.agentRequiresTool", { key: spec.meta.key }));
|
|
2144
2144
|
}
|
|
2145
|
+
for (const [portName, portRef] of Object.entries(spec.runtime?.ports ?? {})) {
|
|
2146
|
+
if (portRef !== undefined && portRef.trim().length === 0) {
|
|
2147
|
+
throw new Error(`Agent ${spec.meta.key} has invalid runtime config: port "${portName}" must not be empty`);
|
|
2148
|
+
}
|
|
2149
|
+
}
|
|
2145
2150
|
const toolNames = new Set;
|
|
2146
2151
|
for (const tool of spec.tools) {
|
|
2147
2152
|
if (toolNames.has(tool.name)) {
|
|
@@ -2297,21 +2302,39 @@ var init_json_schema_to_zod = () => {};
|
|
|
2297
2302
|
// src/tools/tool-adapter.ts
|
|
2298
2303
|
import { tool } from "ai";
|
|
2299
2304
|
function specToolToAISDKTool(specTool, handler, context = {}) {
|
|
2305
|
+
let lastInvocationAt;
|
|
2300
2306
|
return tool({
|
|
2301
2307
|
description: specTool.description ?? specTool.name,
|
|
2302
2308
|
inputSchema: jsonSchemaToZodSafe(specTool.schema),
|
|
2303
2309
|
needsApproval: specTool.requiresApproval ?? !specTool.automationSafe,
|
|
2304
2310
|
execute: async (input) => {
|
|
2305
|
-
const
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2311
|
+
const now = Date.now();
|
|
2312
|
+
const cooldownMs = normalizeDuration(specTool.cooldownMs);
|
|
2313
|
+
if (cooldownMs && lastInvocationAt !== undefined) {
|
|
2314
|
+
const elapsed = now - lastInvocationAt;
|
|
2315
|
+
if (elapsed < cooldownMs) {
|
|
2316
|
+
const retryAfterMs = cooldownMs - elapsed;
|
|
2317
|
+
throw createToolExecutionError(`Tool "${specTool.name}" is cooling down. Retry in ${retryAfterMs}ms.`, "TOOL_COOLDOWN_ACTIVE", retryAfterMs);
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
const timeoutMs = normalizeDuration(specTool.timeoutMs);
|
|
2321
|
+
const { signal, dispose } = createTimeoutSignal(context.signal, timeoutMs);
|
|
2322
|
+
try {
|
|
2323
|
+
const execution = handler(input, {
|
|
2324
|
+
agentId: context.agentId ?? "unknown",
|
|
2325
|
+
sessionId: context.sessionId ?? "unknown",
|
|
2326
|
+
tenantId: context.tenantId,
|
|
2327
|
+
actorId: context.actorId,
|
|
2328
|
+
locale: context.locale,
|
|
2329
|
+
metadata: context.metadata,
|
|
2330
|
+
signal
|
|
2331
|
+
});
|
|
2332
|
+
const result = timeoutMs ? await withTimeout(execution, timeoutMs, specTool.name) : await execution;
|
|
2333
|
+
return typeof result === "string" ? result : JSON.stringify(result);
|
|
2334
|
+
} finally {
|
|
2335
|
+
dispose();
|
|
2336
|
+
lastInvocationAt = Date.now();
|
|
2337
|
+
}
|
|
2315
2338
|
}
|
|
2316
2339
|
});
|
|
2317
2340
|
}
|
|
@@ -2336,6 +2359,64 @@ function createToolHandler(handler) {
|
|
|
2336
2359
|
function buildToolHandlers(handlersObj) {
|
|
2337
2360
|
return new Map(Object.entries(handlersObj));
|
|
2338
2361
|
}
|
|
2362
|
+
function normalizeDuration(value) {
|
|
2363
|
+
if (value === undefined) {
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
if (!Number.isFinite(value)) {
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2369
|
+
if (value <= 0) {
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
return Math.round(value);
|
|
2373
|
+
}
|
|
2374
|
+
function withTimeout(execution, timeoutMs, toolName) {
|
|
2375
|
+
return new Promise((resolve, reject) => {
|
|
2376
|
+
const timeoutHandle = setTimeout(() => {
|
|
2377
|
+
reject(createToolExecutionError(`Tool "${toolName}" timed out after ${timeoutMs}ms.`, "TOOL_EXECUTION_TIMEOUT"));
|
|
2378
|
+
}, timeoutMs);
|
|
2379
|
+
execution.then((result) => {
|
|
2380
|
+
clearTimeout(timeoutHandle);
|
|
2381
|
+
resolve(result);
|
|
2382
|
+
}).catch((error) => {
|
|
2383
|
+
clearTimeout(timeoutHandle);
|
|
2384
|
+
reject(error);
|
|
2385
|
+
});
|
|
2386
|
+
});
|
|
2387
|
+
}
|
|
2388
|
+
function createTimeoutSignal(signal, timeoutMs) {
|
|
2389
|
+
const controller = new AbortController;
|
|
2390
|
+
const abortFromSource = () => controller.abort();
|
|
2391
|
+
if (signal) {
|
|
2392
|
+
if (signal.aborted) {
|
|
2393
|
+
controller.abort();
|
|
2394
|
+
} else {
|
|
2395
|
+
signal.addEventListener("abort", abortFromSource);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
const timeoutHandle = timeoutMs !== undefined ? setTimeout(() => {
|
|
2399
|
+
controller.abort();
|
|
2400
|
+
}, timeoutMs) : undefined;
|
|
2401
|
+
return {
|
|
2402
|
+
signal: controller.signal,
|
|
2403
|
+
dispose: () => {
|
|
2404
|
+
if (timeoutHandle !== undefined) {
|
|
2405
|
+
clearTimeout(timeoutHandle);
|
|
2406
|
+
}
|
|
2407
|
+
if (signal) {
|
|
2408
|
+
signal.removeEventListener("abort", abortFromSource);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
function createToolExecutionError(message, code, retryAfterMs) {
|
|
2414
|
+
return Object.assign(new Error(message), {
|
|
2415
|
+
code,
|
|
2416
|
+
kind: "retryable",
|
|
2417
|
+
retryAfterMs
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2339
2420
|
var init_tool_adapter = __esm(() => {
|
|
2340
2421
|
init_json_schema_to_zod();
|
|
2341
2422
|
init_i18n();
|
|
@@ -3151,7 +3232,13 @@ class ContractSpecAgent {
|
|
|
3151
3232
|
steps: [],
|
|
3152
3233
|
metadata: params.options?.metadata
|
|
3153
3234
|
});
|
|
3235
|
+
} else if (existing.status !== "running") {
|
|
3236
|
+
await this.config.sessionStore.update(sessionId, { status: "running" });
|
|
3154
3237
|
}
|
|
3238
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3239
|
+
role: "user",
|
|
3240
|
+
content: params.prompt
|
|
3241
|
+
});
|
|
3155
3242
|
}
|
|
3156
3243
|
const prompt = params.systemOverride ? `${this.instructions}
|
|
3157
3244
|
|
|
@@ -3163,24 +3250,41 @@ ${params.prompt}` : params.prompt;
|
|
|
3163
3250
|
traceId,
|
|
3164
3251
|
options: params.options
|
|
3165
3252
|
});
|
|
3166
|
-
const
|
|
3167
|
-
const
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3253
|
+
const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
|
|
3254
|
+
const inner = this.createInnerAgent(model, effectiveMaxSteps);
|
|
3255
|
+
let result;
|
|
3256
|
+
try {
|
|
3257
|
+
result = await inner.generate({
|
|
3258
|
+
prompt,
|
|
3259
|
+
abortSignal: params.signal,
|
|
3260
|
+
options: {
|
|
3261
|
+
tenantId: params.options?.tenantId,
|
|
3262
|
+
actorId: params.options?.actorId,
|
|
3263
|
+
sessionId,
|
|
3264
|
+
metadata: params.options?.metadata
|
|
3265
|
+
}
|
|
3266
|
+
});
|
|
3267
|
+
} catch (error) {
|
|
3268
|
+
if (this.config.sessionStore) {
|
|
3269
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3270
|
+
status: "failed"
|
|
3271
|
+
});
|
|
3175
3272
|
}
|
|
3176
|
-
}).finally(() => {
|
|
3177
3273
|
this.activeStepContexts.delete(sessionId);
|
|
3178
|
-
|
|
3274
|
+
throw error;
|
|
3275
|
+
}
|
|
3276
|
+
this.activeStepContexts.delete(sessionId);
|
|
3277
|
+
const escalationError = resolveEscalationError(this.spec, result.finishReason);
|
|
3179
3278
|
if (this.config.sessionStore) {
|
|
3279
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3280
|
+
role: "assistant",
|
|
3281
|
+
content: result.text
|
|
3282
|
+
});
|
|
3180
3283
|
await this.config.sessionStore.update(sessionId, {
|
|
3181
|
-
status: "completed"
|
|
3284
|
+
status: escalationError ? "escalated" : "completed"
|
|
3182
3285
|
});
|
|
3183
3286
|
}
|
|
3287
|
+
const session = this.config.sessionStore ? await this.config.sessionStore.get(sessionId) : null;
|
|
3184
3288
|
return {
|
|
3185
3289
|
text: result.text,
|
|
3186
3290
|
steps: result.steps,
|
|
@@ -3197,7 +3301,16 @@ ${params.prompt}` : params.prompt;
|
|
|
3197
3301
|
output: tr.output
|
|
3198
3302
|
})),
|
|
3199
3303
|
finishReason: result.finishReason,
|
|
3200
|
-
usage: result.usage
|
|
3304
|
+
usage: result.usage,
|
|
3305
|
+
session: session ?? undefined,
|
|
3306
|
+
pendingApproval: escalationError ? {
|
|
3307
|
+
toolName: this.spec.policy?.escalation?.approvalWorkflow ?? "approval_required",
|
|
3308
|
+
toolCallId: `approval_${sessionId}`,
|
|
3309
|
+
args: {
|
|
3310
|
+
reason: escalationError.message,
|
|
3311
|
+
code: escalationError.code
|
|
3312
|
+
}
|
|
3313
|
+
} : undefined
|
|
3201
3314
|
};
|
|
3202
3315
|
}
|
|
3203
3316
|
async stream(params) {
|
|
@@ -3220,7 +3333,28 @@ ${params.prompt}` : params.prompt;
|
|
|
3220
3333
|
traceId,
|
|
3221
3334
|
options: params.options
|
|
3222
3335
|
});
|
|
3223
|
-
const
|
|
3336
|
+
const effectiveMaxSteps = resolveMaxSteps(params.maxSteps, this.spec.maxSteps);
|
|
3337
|
+
const inner = this.createInnerAgent(model, effectiveMaxSteps);
|
|
3338
|
+
if (this.config.sessionStore) {
|
|
3339
|
+
const existing = await this.config.sessionStore.get(sessionId);
|
|
3340
|
+
if (!existing) {
|
|
3341
|
+
await this.config.sessionStore.create({
|
|
3342
|
+
sessionId,
|
|
3343
|
+
agentId: this.id,
|
|
3344
|
+
tenantId: params.options?.tenantId,
|
|
3345
|
+
actorId: params.options?.actorId,
|
|
3346
|
+
status: "running",
|
|
3347
|
+
messages: [],
|
|
3348
|
+
steps: [],
|
|
3349
|
+
metadata: params.options?.metadata
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3352
|
+
await this.config.sessionStore.appendMessage(sessionId, {
|
|
3353
|
+
role: "user",
|
|
3354
|
+
content: params.prompt
|
|
3355
|
+
});
|
|
3356
|
+
await this.config.sessionStore.update(sessionId, { status: "running" });
|
|
3357
|
+
}
|
|
3224
3358
|
return inner.stream({
|
|
3225
3359
|
prompt,
|
|
3226
3360
|
abortSignal: params.signal,
|
|
@@ -3236,6 +3370,9 @@ ${params.prompt}` : params.prompt;
|
|
|
3236
3370
|
const sessionId = step.options?.sessionId;
|
|
3237
3371
|
if (sessionId && this.config.sessionStore) {
|
|
3238
3372
|
await this.config.sessionStore.appendStep(sessionId, step);
|
|
3373
|
+
await this.config.sessionStore.update(sessionId, {
|
|
3374
|
+
status: step.finishReason === "tool-calls" ? "waiting" : "running"
|
|
3375
|
+
});
|
|
3239
3376
|
}
|
|
3240
3377
|
if (this.config.telemetryCollector) {
|
|
3241
3378
|
const now = new Date;
|
|
@@ -3259,12 +3396,12 @@ ${params.prompt}` : params.prompt;
|
|
|
3259
3396
|
}
|
|
3260
3397
|
}
|
|
3261
3398
|
}
|
|
3262
|
-
createInnerAgent(model) {
|
|
3399
|
+
createInnerAgent(model, maxSteps) {
|
|
3263
3400
|
return new ToolLoopAgent({
|
|
3264
3401
|
model,
|
|
3265
3402
|
instructions: this.instructions,
|
|
3266
3403
|
tools: this.tools,
|
|
3267
|
-
stopWhen: stepCountIs(
|
|
3404
|
+
stopWhen: stepCountIs(maxSteps),
|
|
3268
3405
|
callOptionsSchema: ContractSpecCallOptionsSchema,
|
|
3269
3406
|
onStepFinish: async (step) => {
|
|
3270
3407
|
await this.handleStepFinish(step);
|
|
@@ -3272,6 +3409,10 @@ ${params.prompt}` : params.prompt;
|
|
|
3272
3409
|
});
|
|
3273
3410
|
}
|
|
3274
3411
|
async resolveModelForCall(params) {
|
|
3412
|
+
if (this.config.modelSelector && params.options?.selectionContext) {
|
|
3413
|
+
const { model } = await this.config.modelSelector.selectAndCreate(params.options.selectionContext);
|
|
3414
|
+
return model;
|
|
3415
|
+
}
|
|
3275
3416
|
const posthogConfig = this.config.posthogConfig;
|
|
3276
3417
|
if (!posthogConfig) {
|
|
3277
3418
|
return this.config.model;
|
|
@@ -3296,6 +3437,46 @@ ${params.prompt}` : params.prompt;
|
|
|
3296
3437
|
return createPostHogTracedModel2(this.config.model, posthogConfig, tracingOptions);
|
|
3297
3438
|
}
|
|
3298
3439
|
}
|
|
3440
|
+
function resolveMaxSteps(overrideMaxSteps, specMaxSteps) {
|
|
3441
|
+
const candidate = overrideMaxSteps ?? specMaxSteps ?? 10;
|
|
3442
|
+
if (!Number.isFinite(candidate)) {
|
|
3443
|
+
return 10;
|
|
3444
|
+
}
|
|
3445
|
+
if (candidate < 1) {
|
|
3446
|
+
return 1;
|
|
3447
|
+
}
|
|
3448
|
+
return Math.round(candidate);
|
|
3449
|
+
}
|
|
3450
|
+
function resolveEscalationError(spec, finishReason) {
|
|
3451
|
+
const escalation = spec.policy?.escalation;
|
|
3452
|
+
if (!escalation) {
|
|
3453
|
+
return;
|
|
3454
|
+
}
|
|
3455
|
+
if (escalation.onTimeout && finishReason === "length") {
|
|
3456
|
+
return {
|
|
3457
|
+
kind: "timeout",
|
|
3458
|
+
code: "AGENT_TIMEOUT_ESCALATION",
|
|
3459
|
+
message: "Agent reached max step budget and requires escalation."
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
if (escalation.onToolFailure && finishReason === "error") {
|
|
3463
|
+
return {
|
|
3464
|
+
kind: "retryable",
|
|
3465
|
+
code: "AGENT_TOOL_FAILURE_ESCALATION",
|
|
3466
|
+
message: "Agent encountered a tool failure and requires escalation."
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
const confidenceThreshold = escalation.confidenceThreshold;
|
|
3470
|
+
const defaultConfidence = spec.policy?.confidence?.default;
|
|
3471
|
+
if (confidenceThreshold !== undefined && defaultConfidence !== undefined && defaultConfidence < confidenceThreshold) {
|
|
3472
|
+
return {
|
|
3473
|
+
kind: "policy_blocked",
|
|
3474
|
+
code: "AGENT_CONFIDENCE_ESCALATION",
|
|
3475
|
+
message: `Agent default confidence (${defaultConfidence}) is below escalation threshold (${confidenceThreshold}).`
|
|
3476
|
+
};
|
|
3477
|
+
}
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3299
3480
|
var ContractSpecCallOptionsSchema;
|
|
3300
3481
|
var init_contract_spec_agent = __esm(() => {
|
|
3301
3482
|
init_spec();
|
|
@@ -4579,7 +4760,11 @@ function getDefaultSpec(locale) {
|
|
|
4579
4760
|
tools: []
|
|
4580
4761
|
};
|
|
4581
4762
|
}
|
|
4582
|
-
function resolveModel(options) {
|
|
4763
|
+
async function resolveModel(options) {
|
|
4764
|
+
if (options.modelSelector && options.selectionContext) {
|
|
4765
|
+
const { model } = await options.modelSelector.selectAndCreate(options.selectionContext);
|
|
4766
|
+
return model;
|
|
4767
|
+
}
|
|
4583
4768
|
if (options.model)
|
|
4584
4769
|
return options.model;
|
|
4585
4770
|
if (options.provider) {
|
|
@@ -4612,7 +4797,8 @@ function ensureToolHandlers(spec, handlers, locale) {
|
|
|
4612
4797
|
}
|
|
4613
4798
|
}
|
|
4614
4799
|
async function createAgentJsonRunner(options) {
|
|
4615
|
-
const
|
|
4800
|
+
const resolved = await resolveModel(options);
|
|
4801
|
+
const model = applyModelSettings(resolved, {
|
|
4616
4802
|
temperature: options.temperature ?? 0
|
|
4617
4803
|
});
|
|
4618
4804
|
const baseSpec = options.spec ?? getDefaultSpec(options.locale);
|
|
@@ -4813,7 +4999,10 @@ class UnifiedAgent {
|
|
|
4813
4999
|
async resolveAISDKModel() {
|
|
4814
5000
|
const backendConfig = this.getAISDKConfig();
|
|
4815
5001
|
let model;
|
|
4816
|
-
if (backendConfig?.
|
|
5002
|
+
if (backendConfig?.modelSelector && backendConfig.selectionContext) {
|
|
5003
|
+
const result = await backendConfig.modelSelector.selectAndCreate(backendConfig.selectionContext);
|
|
5004
|
+
model = result.model;
|
|
5005
|
+
} else if (backendConfig?.modelInstance) {
|
|
4817
5006
|
model = backendConfig.modelInstance;
|
|
4818
5007
|
} else if (backendConfig?.provider) {
|
|
4819
5008
|
const { createProvider: createProvider2 } = await import("@contractspec/lib.ai-providers/factory");
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { LanguageModel } from 'ai';
|
|
2
2
|
import type { ProviderConfig } from '@contractspec/lib.ai-providers/types';
|
|
3
|
+
import type { ModelSelector, ModelSelectionContext } from '@contractspec/lib.ai-providers/selector-types';
|
|
3
4
|
import type { AgentSpec } from '../spec/spec';
|
|
4
5
|
import type { ToolHandler } from '../types';
|
|
5
6
|
export interface AgentJsonRunnerOptions {
|
|
@@ -11,6 +12,8 @@ export interface AgentJsonRunnerOptions {
|
|
|
11
12
|
maxSteps?: number;
|
|
12
13
|
temperature?: number;
|
|
13
14
|
locale?: string;
|
|
15
|
+
modelSelector?: ModelSelector;
|
|
16
|
+
selectionContext?: ModelSelectionContext;
|
|
14
17
|
}
|
|
15
18
|
export interface AgentJsonRunner {
|
|
16
19
|
generateJson: (prompt: string) => Promise<string>;
|