@artemiskit/core 0.6.0 → 0.6.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/CHANGELOG.md +15 -0
- package/README.md +12 -0
- package/dist/adapters/types.d.ts +7 -0
- package/dist/adapters/types.d.ts.map +1 -1
- package/dist/agent-workflow/catalog.d.ts +2 -1
- package/dist/agent-workflow/catalog.d.ts.map +1 -1
- package/dist/agent-workflow/environment.d.ts +47 -0
- package/dist/agent-workflow/environment.d.ts.map +1 -0
- package/dist/agent-workflow/index.d.ts +4 -1
- package/dist/agent-workflow/index.d.ts.map +1 -1
- package/dist/agent-workflow/parser.d.ts +1 -1
- package/dist/agent-workflow/parser.d.ts.map +1 -1
- package/dist/agent-workflow/sandbox-fixtures/qualify.d.ts +2 -0
- package/dist/agent-workflow/sandbox-fixtures/qualify.d.ts.map +1 -0
- package/dist/agent-workflow/sandbox.d.ts +12 -0
- package/dist/agent-workflow/sandbox.d.ts.map +1 -0
- package/dist/agent-workflow/schema.d.ts +117 -7
- package/dist/agent-workflow/schema.d.ts.map +1 -1
- package/dist/agent-workflow/session.d.ts +113 -0
- package/dist/agent-workflow/session.d.ts.map +1 -0
- package/dist/agent-workflow/target.d.ts +20 -7
- package/dist/agent-workflow/target.d.ts.map +1 -1
- package/dist/index.js +1366 -21
- package/package.json +1 -1
- package/src/adapters/types.ts +7 -0
- package/src/agent-workflow/catalog.ts +4 -3
- package/src/agent-workflow/environment.ts +207 -0
- package/src/agent-workflow/index.ts +5 -1
- package/src/agent-workflow/parser.ts +1 -1
- package/src/agent-workflow/sandbox-fixtures/qualify.ts +305 -0
- package/src/agent-workflow/sandbox.test.ts +117 -0
- package/src/agent-workflow/sandbox.ts +438 -0
- package/src/agent-workflow/schema.ts +18 -2
- package/src/agent-workflow/session.test.ts +629 -0
- package/src/agent-workflow/session.ts +1119 -0
- package/src/agent-workflow/target.test.ts +5 -1
- package/src/agent-workflow/target.ts +82 -17
package/dist/index.js
CHANGED
|
@@ -33425,8 +33425,8 @@ var tools = [
|
|
|
33425
33425
|
descriptor("search", "retrieval", "Search declared documents by case-insensitive literal text.", "documents", "read", object({ query: text }), object({ matches: { type: "array", maxItems: 100, items: object({ id: identifier }) } })),
|
|
33426
33426
|
descriptor("read_document", "documents", "Read one document from simulated state.", "documents", "read", object({ id: identifier }), object({ id: identifier, content: { type: "string", maxLength: 16384 } })),
|
|
33427
33427
|
descriptor("query_records", "records", "Read a bounded declared collection of structured records.", "records", "read", object({ collection: identifier }), object({ records: { type: "array", maxItems: 100, items: { type: "object" } } })),
|
|
33428
|
-
descriptor("read_file", "files", "Read one relative file from
|
|
33429
|
-
descriptor("write_file", "files", "Write one relative file in isolated
|
|
33428
|
+
descriptor("read_file", "files", "Read one relative file from the isolated workflow environment.", "files", "read", object({ path: relativePath }), object({ content: { type: "string", maxLength: 16384 } })),
|
|
33429
|
+
descriptor("write_file", "files", "Write one relative file in the isolated workflow environment.", "files", "write", object({ path: relativePath, content: { type: "string", maxLength: 16384 } }), object({ path: relativePath, written: { const: true } })),
|
|
33430
33430
|
descriptor("calculator", "computation", "Perform one finite arithmetic operation without evaluating code.", "computation", "none", object({
|
|
33431
33431
|
operation: { enum: ["add", "subtract", "multiply", "divide"] },
|
|
33432
33432
|
a: { type: "number" },
|
|
@@ -33473,10 +33473,12 @@ function isWorkflowJson(value) {
|
|
|
33473
33473
|
if (Array.isArray(item) && (item.length > 1e4 || Object.keys(item).length !== item.length || Object.keys(item).some((key, index) => key !== String(index))))
|
|
33474
33474
|
return false;
|
|
33475
33475
|
ancestors.add(item);
|
|
33476
|
-
for (const key of Object.
|
|
33476
|
+
for (const key of Object.getOwnPropertyNames(item)) {
|
|
33477
|
+
if (Array.isArray(item) && key === "length")
|
|
33478
|
+
continue;
|
|
33477
33479
|
textBytes += Buffer.byteLength(key);
|
|
33478
33480
|
const entry = Object.getOwnPropertyDescriptor(item, key);
|
|
33479
|
-
if (textBytes > 1048576 || forbiddenKeys.has(key) || !entry || !("value" in entry) || !visit(entry.value, depth + 1))
|
|
33481
|
+
if (textBytes > 1048576 || forbiddenKeys.has(key) || !entry || !("value" in entry) || !entry.enumerable || !visit(entry.value, depth + 1))
|
|
33480
33482
|
return false;
|
|
33481
33483
|
}
|
|
33482
33484
|
ancestors.delete(item);
|
|
@@ -33503,8 +33505,10 @@ var WorkflowPolicySchema = exports_external.object({
|
|
|
33503
33505
|
communication: permission.optional(),
|
|
33504
33506
|
coordination: permission.optional()
|
|
33505
33507
|
}).strict(),
|
|
33508
|
+
paths: exports_external.object({ read: exports_external.array(relativePath2).max(1000), write: exports_external.array(relativePath2).max(1000) }).strict().optional(),
|
|
33506
33509
|
budgets: exports_external.object({
|
|
33507
33510
|
max_actions: exports_external.number().int().min(1).max(1000),
|
|
33511
|
+
max_model_requests: exports_external.number().int().min(1).max(1000).optional(),
|
|
33508
33512
|
max_tool_calls: exports_external.number().int().min(1).max(1000).optional(),
|
|
33509
33513
|
timeout_ms: exports_external.number().int().min(1).max(3600000),
|
|
33510
33514
|
max_tokens: exports_external.number().int().min(1).max(1e6).optional()
|
|
@@ -33543,9 +33547,13 @@ var definition = exports_external.object({
|
|
|
33543
33547
|
description: exports_external.string().max(4096).optional(),
|
|
33544
33548
|
target: exports_external.object({
|
|
33545
33549
|
provider: exports_external.string().min(1).max(64).regex(/^[a-z0-9_-]+$/),
|
|
33546
|
-
model: exports_external.string().min(1).max(256)
|
|
33550
|
+
model: exports_external.string().min(1).max(256),
|
|
33551
|
+
generation: exports_external.object({
|
|
33552
|
+
max_tokens: exports_external.number().int().min(1).max(1e6).optional(),
|
|
33553
|
+
temperature: exports_external.number().min(0).max(2).optional()
|
|
33554
|
+
}).strict().optional()
|
|
33547
33555
|
}).strict(),
|
|
33548
|
-
environment: exports_external.object({ type: exports_external.
|
|
33556
|
+
environment: exports_external.object({ type: exports_external.enum(["simulated", "sandbox"]), policy: WorkflowPolicySchema }).strict(),
|
|
33549
33557
|
tools: exports_external.array(exports_external.enum(WORKFLOW_TOOL_IDS)).min(1).max(WORKFLOW_TOOL_IDS.length),
|
|
33550
33558
|
workflow: exports_external.object({
|
|
33551
33559
|
system_instructions: exports_external.string().min(1).max(32768),
|
|
@@ -33808,6 +33816,7 @@ function executeSimulatedTool(request) {
|
|
|
33808
33816
|
}
|
|
33809
33817
|
// src/agent-workflow/target.ts
|
|
33810
33818
|
var import_ajv4 = __toESM(require_ajv(), 1);
|
|
33819
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
33811
33820
|
var identifier2 = exports_external.string().min(1).max(256);
|
|
33812
33821
|
var toolName = exports_external.string().regex(/^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/);
|
|
33813
33822
|
var text2 = exports_external.string().max(1e6);
|
|
@@ -33823,7 +33832,7 @@ var messageSchema = exports_external.object({
|
|
|
33823
33832
|
tool_calls: exports_external.array(toolCallSchema).min(1).max(100).optional()
|
|
33824
33833
|
}).strict();
|
|
33825
33834
|
var timeoutSchema = exports_external.number().int().min(1).max(2147483647);
|
|
33826
|
-
var
|
|
33835
|
+
var agentTurnRequestSchema = exports_external.object({
|
|
33827
33836
|
messages: exports_external.array(messageSchema).min(1).max(1000),
|
|
33828
33837
|
tools: exports_external.array(exports_external.object({
|
|
33829
33838
|
type: exports_external.literal("function"),
|
|
@@ -33847,6 +33856,7 @@ var requestSchema = exports_external.object({
|
|
|
33847
33856
|
}).strict()
|
|
33848
33857
|
}).strict();
|
|
33849
33858
|
var resultSchema = exports_external.object({
|
|
33859
|
+
usageAvailable: exports_external.boolean().optional(),
|
|
33850
33860
|
id: identifier2,
|
|
33851
33861
|
model: identifier2,
|
|
33852
33862
|
text: text2,
|
|
@@ -33880,7 +33890,7 @@ function bounded(run, timeoutMs, signal) {
|
|
|
33880
33890
|
Promise.resolve().then(() => signal?.aborted ? failure("error", "aborted") : run()).then(finish, () => finish(failure("error", "target_error")));
|
|
33881
33891
|
});
|
|
33882
33892
|
}
|
|
33883
|
-
function
|
|
33893
|
+
function validWorkflowTranscript(messages) {
|
|
33884
33894
|
const seen = new Set;
|
|
33885
33895
|
const pending = new Set;
|
|
33886
33896
|
for (const message of messages) {
|
|
@@ -33911,14 +33921,30 @@ function createModelClientTarget(client) {
|
|
|
33911
33921
|
if (!client || !identifier2.safeParse(client.provider).success || typeof client.generate !== "function" || typeof client.capabilities !== "function") {
|
|
33912
33922
|
throw new TypeError("Invalid ModelClient");
|
|
33913
33923
|
}
|
|
33924
|
+
const pending = new Set;
|
|
33925
|
+
const track = (promise) => {
|
|
33926
|
+
pending.add(promise);
|
|
33927
|
+
promise.then(() => pending.delete(promise), () => pending.delete(promise));
|
|
33928
|
+
return promise;
|
|
33929
|
+
};
|
|
33914
33930
|
const readCapabilities = async () => {
|
|
33915
|
-
const value = await client.capabilities();
|
|
33931
|
+
const value = await track(client.capabilities());
|
|
33916
33932
|
if (!value || typeof value.toolUse !== "boolean")
|
|
33917
33933
|
return failure("invalid", "invalid_response");
|
|
33918
|
-
return {
|
|
33934
|
+
return {
|
|
33935
|
+
status: "available",
|
|
33936
|
+
toolUse: value.toolUse,
|
|
33937
|
+
transportCancellation: value.transportCancellation === true
|
|
33938
|
+
};
|
|
33919
33939
|
};
|
|
33920
33940
|
return {
|
|
33921
33941
|
provider: client.provider,
|
|
33942
|
+
async drain({ timeoutMs }) {
|
|
33943
|
+
if (!timeoutSchema.safeParse(timeoutMs).success)
|
|
33944
|
+
return { pendingOperations: pending.size };
|
|
33945
|
+
await bounded(() => Promise.allSettled([...pending]), timeoutMs);
|
|
33946
|
+
return { pendingOperations: pending.size };
|
|
33947
|
+
},
|
|
33922
33948
|
async capabilities(options, signal) {
|
|
33923
33949
|
if (!timeoutSchema.safeParse(options?.timeoutMs).success)
|
|
33924
33950
|
return failure("invalid", "invalid_request");
|
|
@@ -33927,11 +33953,11 @@ function createModelClientTarget(client) {
|
|
|
33927
33953
|
async turn(request, signal) {
|
|
33928
33954
|
let parsed;
|
|
33929
33955
|
try {
|
|
33930
|
-
parsed =
|
|
33956
|
+
parsed = agentTurnRequestSchema.safeParse(request);
|
|
33931
33957
|
} catch {
|
|
33932
33958
|
return failure("invalid", "invalid_request");
|
|
33933
33959
|
}
|
|
33934
|
-
if (!parsed.success || !
|
|
33960
|
+
if (!parsed.success || !validWorkflowTranscript(parsed.data.messages))
|
|
33935
33961
|
return failure("invalid", "invalid_request");
|
|
33936
33962
|
const input = parsed.data;
|
|
33937
33963
|
const validators4 = new Map;
|
|
@@ -33952,6 +33978,12 @@ function createModelClientTarget(client) {
|
|
|
33952
33978
|
return failure("invalid", "invalid_request");
|
|
33953
33979
|
}
|
|
33954
33980
|
const started = Date.now();
|
|
33981
|
+
const controller = new AbortController;
|
|
33982
|
+
const abort = () => controller.abort();
|
|
33983
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
33984
|
+
if (signal?.aborted)
|
|
33985
|
+
controller.abort();
|
|
33986
|
+
const timer = setTimeout(abort, input.budgets.timeoutMs);
|
|
33955
33987
|
return bounded(async () => {
|
|
33956
33988
|
const capabilities = await readCapabilities();
|
|
33957
33989
|
if (capabilities.status !== "available")
|
|
@@ -33963,12 +33995,14 @@ function createModelClientTarget(client) {
|
|
|
33963
33995
|
if (Date.now() - started >= input.budgets.timeoutMs)
|
|
33964
33996
|
return failure("error", "timeout");
|
|
33965
33997
|
const options = {
|
|
33998
|
+
maxRetries: 0,
|
|
33966
33999
|
prompt: input.messages,
|
|
33967
34000
|
tools: input.tools,
|
|
33968
34001
|
model: input.model,
|
|
33969
|
-
...input.generation
|
|
34002
|
+
...input.generation,
|
|
34003
|
+
...capabilities.transportCancellation ? { signal: controller.signal } : {}
|
|
33970
34004
|
};
|
|
33971
|
-
const generated = resultSchema.safeParse(await client.generate(options));
|
|
34005
|
+
const generated = resultSchema.safeParse(await track(client.generate(options)));
|
|
33972
34006
|
if (!generated.success)
|
|
33973
34007
|
return failure("invalid", "invalid_response");
|
|
33974
34008
|
const result = generated.data;
|
|
@@ -33976,17 +34010,29 @@ function createModelClientTarget(client) {
|
|
|
33976
34010
|
if (result.functionCall !== undefined || result.finishReason === "function_call" || result.finishReason === "tool_calls" && calls.length === 0 || calls.length > input.budgets.maxToolCalls || result.tokens.total !== result.tokens.prompt + result.tokens.completion)
|
|
33977
34011
|
return failure("invalid", "invalid_response");
|
|
33978
34012
|
const ids = new Set(input.messages.flatMap((message) => message.tool_calls?.map((call) => call.id) ?? []));
|
|
34013
|
+
const rejected = (call, reason) => ({
|
|
34014
|
+
...failure("invalid", "invalid_response"),
|
|
34015
|
+
tokens: result.tokens,
|
|
34016
|
+
...result.usageAvailable !== undefined ? { usageAvailable: result.usageAvailable } : {},
|
|
34017
|
+
rejectedCall: {
|
|
34018
|
+
requestedCallIdHash: createHash3("sha256").update(call.id).digest("hex"),
|
|
34019
|
+
tool: getWorkflowTool(call.function.name)?.id ?? "unknown",
|
|
34020
|
+
reason
|
|
34021
|
+
}
|
|
34022
|
+
});
|
|
33979
34023
|
for (const call of calls) {
|
|
33980
34024
|
if (ids.has(call.id))
|
|
33981
|
-
return
|
|
34025
|
+
return rejected(call, "duplicate_id");
|
|
33982
34026
|
ids.add(call.id);
|
|
33983
34027
|
const validate2 = validators4.get(call.function.name);
|
|
34028
|
+
if (!validate2)
|
|
34029
|
+
return rejected(call, "undeclared_tool");
|
|
33984
34030
|
try {
|
|
33985
34031
|
const args = JSON.parse(call.function.arguments);
|
|
33986
34032
|
if (!args || typeof args !== "object" || Array.isArray(args) || !validate2 || validate2(args) !== true)
|
|
33987
|
-
return
|
|
34033
|
+
return rejected(call, "invalid_arguments");
|
|
33988
34034
|
} catch {
|
|
33989
|
-
return
|
|
34035
|
+
return rejected(call, "invalid_arguments");
|
|
33990
34036
|
}
|
|
33991
34037
|
}
|
|
33992
34038
|
return {
|
|
@@ -33999,15 +34045,1301 @@ function createModelClientTarget(client) {
|
|
|
33999
34045
|
...calls.length ? { tool_calls: calls } : {}
|
|
34000
34046
|
},
|
|
34001
34047
|
tokens: result.tokens,
|
|
34048
|
+
...result.usageAvailable !== undefined ? { usageAvailable: result.usageAvailable } : {},
|
|
34002
34049
|
latencyMs: result.latencyMs,
|
|
34003
34050
|
finishReason: result.finishReason
|
|
34004
34051
|
};
|
|
34005
|
-
}, input.budgets.timeoutMs, signal)
|
|
34052
|
+
}, input.budgets.timeoutMs, signal).then((result) => controller.signal.aborted ? failure("error", signal?.aborted ? "aborted" : "timeout") : result).finally(() => {
|
|
34053
|
+
clearTimeout(timer);
|
|
34054
|
+
signal?.removeEventListener("abort", abort);
|
|
34055
|
+
});
|
|
34006
34056
|
}
|
|
34007
34057
|
};
|
|
34008
34058
|
}
|
|
34009
|
-
// src/
|
|
34059
|
+
// src/agent-workflow/environment.ts
|
|
34010
34060
|
var import_yaml3 = __toESM(require_dist(), 1);
|
|
34061
|
+
import { constants } from "node:fs";
|
|
34062
|
+
import { lstat, open, realpath } from "node:fs/promises";
|
|
34063
|
+
import { join as join3, resolve as resolve3 } from "node:path";
|
|
34064
|
+
class WorkflowEnvironmentInitializationError extends Error {
|
|
34065
|
+
cleanup;
|
|
34066
|
+
constructor(cleanup) {
|
|
34067
|
+
super("environment_initialization_failed");
|
|
34068
|
+
this.cleanup = cleanup;
|
|
34069
|
+
}
|
|
34070
|
+
}
|
|
34071
|
+
function isWorkflowState(value) {
|
|
34072
|
+
return isWorkflowJson(value) && value !== null && typeof value === "object" && !Array.isArray(value);
|
|
34073
|
+
}
|
|
34074
|
+
function workflowPathAllowed(workflow, tool, input) {
|
|
34075
|
+
if (tool !== "read_file" && tool !== "write_file")
|
|
34076
|
+
return true;
|
|
34077
|
+
if (!isWorkflowState(input) || typeof input.path !== "string" || !isWorkflowRelativePath(input.path))
|
|
34078
|
+
return false;
|
|
34079
|
+
const paths = workflow.environment.policy.paths;
|
|
34080
|
+
return !paths || (tool === "read_file" ? paths.read : paths.write).includes(input.path);
|
|
34081
|
+
}
|
|
34082
|
+
function workflowToolPermitted(workflow, tool) {
|
|
34083
|
+
const descriptor2 = getWorkflowTool(tool);
|
|
34084
|
+
if (!descriptor2 || !workflow.tools.includes(descriptor2.id))
|
|
34085
|
+
return false;
|
|
34086
|
+
if (descriptor2.authority.access === "none")
|
|
34087
|
+
return true;
|
|
34088
|
+
const grant = workflow.environment.policy.permissions[descriptor2.authority.resource];
|
|
34089
|
+
return grant === "write" || grant === "read" && descriptor2.authority.access === "read";
|
|
34090
|
+
}
|
|
34091
|
+
async function resolveWorkflowInitialState(workflow, fixtureRoot) {
|
|
34092
|
+
const initial = workflow.workflow.initial_state;
|
|
34093
|
+
if (typeof initial !== "string") {
|
|
34094
|
+
if (!isWorkflowState(initial))
|
|
34095
|
+
throw new Error("invalid_fixture");
|
|
34096
|
+
return structuredClone(initial);
|
|
34097
|
+
}
|
|
34098
|
+
if (!fixtureRoot || !isWorkflowRelativePath(initial) || !/\.(json|ya?ml)$/i.test(initial) || initial.split("/").some((part) => part.startsWith(".") || /(?:^|[._-])(?:secrets?|credentials?|tokens?|private|id_rsa|id_ed25519)(?:[._-]|$)/i.test(part)))
|
|
34099
|
+
throw new Error("invalid_fixture");
|
|
34100
|
+
const root = resolve3(fixtureRoot);
|
|
34101
|
+
const canonicalRoot = await realpath(root);
|
|
34102
|
+
let current = canonicalRoot;
|
|
34103
|
+
const ancestry = [];
|
|
34104
|
+
const parts = initial.split("/");
|
|
34105
|
+
for (const [index, part] of parts.entries()) {
|
|
34106
|
+
current = join3(current, part);
|
|
34107
|
+
const stat2 = await lstat(current);
|
|
34108
|
+
if (stat2.isSymbolicLink() || (index < parts.length - 1 ? !stat2.isDirectory() : !stat2.isFile()))
|
|
34109
|
+
throw new Error("invalid_fixture");
|
|
34110
|
+
ancestry.push({ path: current, ino: stat2.ino, dev: stat2.dev });
|
|
34111
|
+
}
|
|
34112
|
+
const file = await open(current, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
34113
|
+
try {
|
|
34114
|
+
const stat2 = await file.stat();
|
|
34115
|
+
const expected = ancestry[ancestry.length - 1];
|
|
34116
|
+
if (!stat2.isFile() || stat2.size > 1048576 || stat2.ino !== expected.ino || stat2.dev !== expected.dev)
|
|
34117
|
+
throw new Error("invalid_fixture");
|
|
34118
|
+
const buffer = Buffer.alloc(1048577);
|
|
34119
|
+
let length = 0;
|
|
34120
|
+
while (length < buffer.length) {
|
|
34121
|
+
const { bytesRead } = await file.read(buffer, length, buffer.length - length, null);
|
|
34122
|
+
if (!bytesRead)
|
|
34123
|
+
break;
|
|
34124
|
+
length += bytesRead;
|
|
34125
|
+
}
|
|
34126
|
+
if (length > 1048576)
|
|
34127
|
+
throw new Error("invalid_fixture");
|
|
34128
|
+
for (const entry of ancestry) {
|
|
34129
|
+
const after = await lstat(entry.path);
|
|
34130
|
+
if (after.isSymbolicLink() || after.ino !== entry.ino || after.dev !== entry.dev)
|
|
34131
|
+
throw new Error("invalid_fixture");
|
|
34132
|
+
}
|
|
34133
|
+
const document2 = import_yaml3.parseDocument(buffer.subarray(0, length).toString("utf8"), {
|
|
34134
|
+
uniqueKeys: true,
|
|
34135
|
+
customTags: []
|
|
34136
|
+
});
|
|
34137
|
+
if (document2.errors.length || document2.warnings.length)
|
|
34138
|
+
throw new Error("invalid_fixture");
|
|
34139
|
+
const value = document2.toJS({ maxAliasCount: 0 });
|
|
34140
|
+
if (!isWorkflowState(value))
|
|
34141
|
+
throw new Error("invalid_fixture");
|
|
34142
|
+
return value;
|
|
34143
|
+
} finally {
|
|
34144
|
+
await file.close();
|
|
34145
|
+
}
|
|
34146
|
+
}
|
|
34147
|
+
var createSimulatedWorkflowEnvironment = async ({
|
|
34148
|
+
workflow,
|
|
34149
|
+
initialState,
|
|
34150
|
+
signal
|
|
34151
|
+
}) => {
|
|
34152
|
+
if (workflow.environment.type !== "simulated" || signal.aborted || !isWorkflowState(initialState))
|
|
34153
|
+
throw new Error("environment_unavailable");
|
|
34154
|
+
const configuration = structuredClone(workflow);
|
|
34155
|
+
let state = structuredClone(initialState);
|
|
34156
|
+
let closed = false;
|
|
34157
|
+
return {
|
|
34158
|
+
type: "simulated",
|
|
34159
|
+
capabilities: {
|
|
34160
|
+
network: "denied",
|
|
34161
|
+
commands: "denied",
|
|
34162
|
+
externalSideEffects: "denied",
|
|
34163
|
+
isolation: "memory"
|
|
34164
|
+
},
|
|
34165
|
+
async execute(request, executionSignal) {
|
|
34166
|
+
if (closed || executionSignal.aborted)
|
|
34167
|
+
throw new Error("environment_unavailable");
|
|
34168
|
+
if (!workflowPathAllowed(configuration, request.tool, request.input))
|
|
34169
|
+
return {
|
|
34170
|
+
status: "denied",
|
|
34171
|
+
code: "permission_denied",
|
|
34172
|
+
evidence: {
|
|
34173
|
+
tool: getWorkflowTool(request.tool)?.id ?? "unknown",
|
|
34174
|
+
version: "1",
|
|
34175
|
+
status: "denied",
|
|
34176
|
+
code: "permission_denied"
|
|
34177
|
+
}
|
|
34178
|
+
};
|
|
34179
|
+
const result = executeSimulatedTool({
|
|
34180
|
+
...request,
|
|
34181
|
+
state,
|
|
34182
|
+
policy: configuration.environment.policy,
|
|
34183
|
+
declaredTools: configuration.tools
|
|
34184
|
+
});
|
|
34185
|
+
if (result.status === "succeeded")
|
|
34186
|
+
state = structuredClone(result.state);
|
|
34187
|
+
return result;
|
|
34188
|
+
},
|
|
34189
|
+
async snapshot(snapshotSignal) {
|
|
34190
|
+
if (closed || snapshotSignal.aborted)
|
|
34191
|
+
throw new Error("environment_unavailable");
|
|
34192
|
+
return structuredClone(state);
|
|
34193
|
+
},
|
|
34194
|
+
async close() {
|
|
34195
|
+
closed = true;
|
|
34196
|
+
state = {};
|
|
34197
|
+
return { status: "completed", artifacts: "discarded" };
|
|
34198
|
+
}
|
|
34199
|
+
};
|
|
34200
|
+
};
|
|
34201
|
+
// src/agent-workflow/sandbox.ts
|
|
34202
|
+
import { spawn } from "node:child_process";
|
|
34203
|
+
import { randomUUID } from "node:crypto";
|
|
34204
|
+
var WORKFLOW_SANDBOX_IMAGE = "oven/bun:1.3.10-alpine";
|
|
34205
|
+
var LIMIT = 1048576;
|
|
34206
|
+
var OWNER_LABEL = "artemiskit.workflow.owner";
|
|
34207
|
+
var FILE_PROGRAM = String.raw`
|
|
34208
|
+
const fs = require('node:fs');
|
|
34209
|
+
const root = '/workspace';
|
|
34210
|
+
const limit = 1048576;
|
|
34211
|
+
const safe = p => typeof p === 'string' && p.length <= 512 && /^[A-Za-z0-9_-][A-Za-z0-9_./-]*$/.test(p) && p.split('/').every(x => x && !['.','..','__proto__','prototype','constructor'].includes(x));
|
|
34212
|
+
function checked(p, create=false) {
|
|
34213
|
+
if (!safe(p)) throw 'invalid_input';
|
|
34214
|
+
const parts = p.split('/'); let current = root;
|
|
34215
|
+
for (let i=0;i<parts.length;i++) {
|
|
34216
|
+
current += '/' + parts[i];
|
|
34217
|
+
let stat;
|
|
34218
|
+
try { stat = fs.lstatSync(current); } catch(e) { if(e.code !== 'ENOENT') throw 'tool_error'; }
|
|
34219
|
+
if (stat && (stat.isSymbolicLink() || (i<parts.length-1 ? !stat.isDirectory() : !stat.isFile()))) throw 'invalid_input';
|
|
34220
|
+
if (!stat && i<parts.length-1) { if(!create) throw 'not_found'; fs.mkdirSync(current, {mode: 0o700}); }
|
|
34221
|
+
}
|
|
34222
|
+
return current;
|
|
34223
|
+
}
|
|
34224
|
+
function write(p, content) {
|
|
34225
|
+
if(typeof content !== 'string' || content.length>16384) throw 'invalid_input';
|
|
34226
|
+
const path=checked(p,true); const fd=fs.openSync(path, fs.constants.O_WRONLY|fs.constants.O_CREAT|fs.constants.O_TRUNC|fs.constants.O_NOFOLLOW,0o600);
|
|
34227
|
+
try { fs.writeFileSync(fd,content,'utf8'); } finally { fs.closeSync(fd); }
|
|
34228
|
+
}
|
|
34229
|
+
function read(p) {
|
|
34230
|
+
const path=checked(p); let fd;
|
|
34231
|
+
try { fd=fs.openSync(path,fs.constants.O_RDONLY|fs.constants.O_NOFOLLOW); } catch(e) { if(e.code==='ENOENT') throw 'not_found'; throw 'tool_error'; }
|
|
34232
|
+
try { const stat=fs.fstatSync(fd); if(!stat.isFile() || stat.size>65536) throw 'output_limit'; const text=fs.readFileSync(fd,'utf8'); if(text.length>16384) throw 'output_limit'; return text; } finally { fs.closeSync(fd); }
|
|
34233
|
+
}
|
|
34234
|
+
function snapshot() {
|
|
34235
|
+
const files={}; let count=0, bytes=0;
|
|
34236
|
+
function walk(dir, prefix, depth) {
|
|
34237
|
+
if(depth>16) throw 'output_limit';
|
|
34238
|
+
for(const name of fs.readdirSync(dir).sort()) {
|
|
34239
|
+
const p=prefix ? prefix+'/'+name : name;
|
|
34240
|
+
if(!safe(p) || ++count>1000) throw 'output_limit';
|
|
34241
|
+
const stat=fs.lstatSync(dir+'/'+name);
|
|
34242
|
+
if(stat.isSymbolicLink()) throw 'invalid_input';
|
|
34243
|
+
if(stat.isDirectory()) walk(dir+'/'+name,p,depth+1);
|
|
34244
|
+
else if(stat.isFile()) { const value=read(p); bytes+=Buffer.byteLength(value)+Buffer.byteLength(p); if(bytes>limit) throw 'output_limit'; files[p]=value; }
|
|
34245
|
+
else throw 'invalid_input';
|
|
34246
|
+
}
|
|
34247
|
+
}
|
|
34248
|
+
walk(root,'',0); return files;
|
|
34249
|
+
}
|
|
34250
|
+
try {
|
|
34251
|
+
const raw=await Bun.stdin.text(); if(Buffer.byteLength(raw)>limit) throw 'invalid_input';
|
|
34252
|
+
const request=JSON.parse(raw); let output;
|
|
34253
|
+
if(request.action==='init') { for(const [path,content] of Object.entries(request.files)) write(path,content); }
|
|
34254
|
+
else if(request.action==='write') { write(request.path,request.content); output={path:request.path,written:true}; }
|
|
34255
|
+
else if(request.action==='read') output={content:read(request.path)};
|
|
34256
|
+
else if(request.action!=='snapshot') throw 'invalid_input';
|
|
34257
|
+
const result=JSON.stringify({ok:true,files:snapshot(),...(output?{output}:{})}); if(Buffer.byteLength(result)>limit) throw 'output_limit'; process.stdout.write(result);
|
|
34258
|
+
} catch(error) { process.stdout.write(JSON.stringify({ok:false,code:['invalid_input','not_found','output_limit'].includes(error)?error:'tool_error'})); process.exitCode=1; }
|
|
34259
|
+
`;
|
|
34260
|
+
|
|
34261
|
+
class DockerFailure extends Error {
|
|
34262
|
+
code;
|
|
34263
|
+
uncertain;
|
|
34264
|
+
constructor(code, uncertain = false) {
|
|
34265
|
+
super(`sandbox_${code}`);
|
|
34266
|
+
this.code = code;
|
|
34267
|
+
this.uncertain = uncertain;
|
|
34268
|
+
}
|
|
34269
|
+
}
|
|
34270
|
+
function createDockerWorkflowEnvironmentFactory(options = {}) {
|
|
34271
|
+
const operationTimeoutMs = options.operationTimeoutMs ?? 5000;
|
|
34272
|
+
const cleanupTimeoutMs = options.cleanupTimeoutMs ?? 3000;
|
|
34273
|
+
if (Object.keys(options).some((key) => !["operationTimeoutMs", "cleanupTimeoutMs"].includes(key)) || !Number.isInteger(operationTimeoutMs) || operationTimeoutMs < 1 || operationTimeoutMs > 30000 || !Number.isInteger(cleanupTimeoutMs) || cleanupTimeoutMs < 1 || cleanupTimeoutMs > 1e4)
|
|
34274
|
+
throw new TypeError("Invalid Docker workflow environment options");
|
|
34275
|
+
return async ({ workflow, initialState, signal }) => {
|
|
34276
|
+
const parsed = AgentWorkflowSchema.safeParse(workflow);
|
|
34277
|
+
if (!parsed.success || parsed.data.environment.type !== "sandbox" || !isWorkflowState(initialState) || signal.aborted)
|
|
34278
|
+
throw new WorkflowEnvironmentInitializationError({
|
|
34279
|
+
status: "completed",
|
|
34280
|
+
artifacts: "discarded",
|
|
34281
|
+
pendingOperations: 0
|
|
34282
|
+
});
|
|
34283
|
+
const configuration = parsed.data;
|
|
34284
|
+
let state = structuredClone(initialState);
|
|
34285
|
+
const files = state.files ?? {};
|
|
34286
|
+
if (!isWorkflowState(files) || Object.entries(files).some(([path, content]) => !isWorkflowRelativePath(path) || typeof content !== "string" || content.length > 16384) || Object.keys(files).length > 1000)
|
|
34287
|
+
throw new WorkflowEnvironmentInitializationError({
|
|
34288
|
+
status: "completed",
|
|
34289
|
+
artifacts: "discarded",
|
|
34290
|
+
pendingOperations: 0
|
|
34291
|
+
});
|
|
34292
|
+
const owner = randomUUID();
|
|
34293
|
+
const name = `artemiskit-workflow-${owner}`;
|
|
34294
|
+
const children = new Set;
|
|
34295
|
+
let closed = false;
|
|
34296
|
+
let closing = false;
|
|
34297
|
+
let creationAttempted = false;
|
|
34298
|
+
let creationUncertain = false;
|
|
34299
|
+
let busy = false;
|
|
34300
|
+
const command = (args, input, commandSignal, timeoutMs = operationTimeoutMs) => {
|
|
34301
|
+
if (commandSignal.aborted)
|
|
34302
|
+
return Promise.reject(new DockerFailure("aborted"));
|
|
34303
|
+
if (Buffer.byteLength(input) > LIMIT)
|
|
34304
|
+
return Promise.reject(new DockerFailure("output_limit"));
|
|
34305
|
+
return new Promise((resolve4, reject) => {
|
|
34306
|
+
let child;
|
|
34307
|
+
try {
|
|
34308
|
+
child = spawn("docker", args, { stdio: ["pipe", "pipe", "pipe"] });
|
|
34309
|
+
} catch {
|
|
34310
|
+
reject(new DockerFailure("unavailable"));
|
|
34311
|
+
return;
|
|
34312
|
+
}
|
|
34313
|
+
children.add(child);
|
|
34314
|
+
const output = [];
|
|
34315
|
+
let size = 0;
|
|
34316
|
+
let failure2;
|
|
34317
|
+
let settled = false;
|
|
34318
|
+
const stop = (error2) => {
|
|
34319
|
+
failure2 ??= error2;
|
|
34320
|
+
child.kill("SIGKILL");
|
|
34321
|
+
};
|
|
34322
|
+
const abort = () => stop(new DockerFailure("aborted", true));
|
|
34323
|
+
const timer = setTimeout(() => stop(new DockerFailure("timeout", true)), timeoutMs);
|
|
34324
|
+
commandSignal.addEventListener("abort", abort, { once: true });
|
|
34325
|
+
child.stdout?.on("data", (chunk) => {
|
|
34326
|
+
size += chunk.length;
|
|
34327
|
+
if (size > LIMIT)
|
|
34328
|
+
stop(new DockerFailure("output_limit", true));
|
|
34329
|
+
else
|
|
34330
|
+
output.push(Buffer.from(chunk));
|
|
34331
|
+
});
|
|
34332
|
+
child.stderr?.on("data", (chunk) => {
|
|
34333
|
+
size += chunk.length;
|
|
34334
|
+
if (size > LIMIT)
|
|
34335
|
+
stop(new DockerFailure("output_limit", true));
|
|
34336
|
+
});
|
|
34337
|
+
child.stdin?.on("error", () => {});
|
|
34338
|
+
const finish = (code, error2) => {
|
|
34339
|
+
if (settled)
|
|
34340
|
+
return;
|
|
34341
|
+
settled = true;
|
|
34342
|
+
clearTimeout(timer);
|
|
34343
|
+
commandSignal.removeEventListener("abort", abort);
|
|
34344
|
+
children.delete(child);
|
|
34345
|
+
if (error2 || failure2)
|
|
34346
|
+
reject(error2 ?? failure2);
|
|
34347
|
+
else
|
|
34348
|
+
resolve4({ code: code ?? 1, stdout: Buffer.concat(output).toString("utf8") });
|
|
34349
|
+
};
|
|
34350
|
+
child.once("error", () => finish(null, new DockerFailure("unavailable")));
|
|
34351
|
+
child.once("close", (code) => finish(code));
|
|
34352
|
+
child.stdin?.end(input);
|
|
34353
|
+
if (commandSignal.aborted)
|
|
34354
|
+
abort();
|
|
34355
|
+
});
|
|
34356
|
+
};
|
|
34357
|
+
async function close(closeSignal) {
|
|
34358
|
+
if (closed)
|
|
34359
|
+
return { status: "completed", artifacts: "discarded" };
|
|
34360
|
+
closing = true;
|
|
34361
|
+
for (const child of children)
|
|
34362
|
+
child.kill("SIGKILL");
|
|
34363
|
+
const cleanupController = new AbortController;
|
|
34364
|
+
const onAbort = () => cleanupController.abort();
|
|
34365
|
+
closeSignal.addEventListener("abort", onAbort, { once: true });
|
|
34366
|
+
if (closeSignal.aborted)
|
|
34367
|
+
cleanupController.abort();
|
|
34368
|
+
const timer = setTimeout(onAbort, cleanupTimeoutMs);
|
|
34369
|
+
try {
|
|
34370
|
+
if (!creationAttempted) {
|
|
34371
|
+
closed = true;
|
|
34372
|
+
state = {};
|
|
34373
|
+
return { status: "completed", artifacts: "discarded" };
|
|
34374
|
+
}
|
|
34375
|
+
const inspected = await command(["inspect", "--format", `{{index .Config.Labels "${OWNER_LABEL}"}}`, name], "", cleanupController.signal, cleanupTimeoutMs);
|
|
34376
|
+
if (inspected.code === 0) {
|
|
34377
|
+
if (inspected.stdout.trim() !== owner)
|
|
34378
|
+
return { status: "unresolved", artifacts: "unknown" };
|
|
34379
|
+
const removed = await command(["rm", "--force", name], "", cleanupController.signal, cleanupTimeoutMs);
|
|
34380
|
+
if (removed.code !== 0)
|
|
34381
|
+
return { status: "unresolved", artifacts: "unknown" };
|
|
34382
|
+
creationUncertain = false;
|
|
34383
|
+
}
|
|
34384
|
+
const remaining = await command(["ps", "--all", "--filter", `name=^/${name}$`, "--format", "{{.Names}}"], "", cleanupController.signal, cleanupTimeoutMs);
|
|
34385
|
+
if (remaining.code !== 0 || remaining.stdout.trim() || creationUncertain || children.size > 0)
|
|
34386
|
+
return { status: "unresolved", artifacts: "unknown" };
|
|
34387
|
+
closed = true;
|
|
34388
|
+
state = {};
|
|
34389
|
+
return { status: "completed", artifacts: "discarded" };
|
|
34390
|
+
} catch {
|
|
34391
|
+
return { status: "unresolved", artifacts: "unknown" };
|
|
34392
|
+
} finally {
|
|
34393
|
+
clearTimeout(timer);
|
|
34394
|
+
closeSignal.removeEventListener("abort", onAbort);
|
|
34395
|
+
}
|
|
34396
|
+
}
|
|
34397
|
+
async function fileOperation(request, operationSignal) {
|
|
34398
|
+
const result = await command(["exec", "--interactive", name, "bun", "--eval", FILE_PROGRAM], JSON.stringify(request), operationSignal);
|
|
34399
|
+
let response;
|
|
34400
|
+
try {
|
|
34401
|
+
response = JSON.parse(result.stdout);
|
|
34402
|
+
} catch {
|
|
34403
|
+
throw new DockerFailure("unavailable");
|
|
34404
|
+
}
|
|
34405
|
+
if (!isWorkflowState(response))
|
|
34406
|
+
throw new DockerFailure("unavailable");
|
|
34407
|
+
if (response.ok === false && ["invalid_input", "not_found", "output_limit", "tool_error"].includes(String(response.code)))
|
|
34408
|
+
return {
|
|
34409
|
+
ok: false,
|
|
34410
|
+
code: response.code
|
|
34411
|
+
};
|
|
34412
|
+
if (result.code !== 0 || response.ok !== true || !isWorkflowState(response.files) || Object.entries(response.files).some(([path, content]) => !isWorkflowRelativePath(path) || typeof content !== "string" || content.length > 16384))
|
|
34413
|
+
throw new DockerFailure("unavailable");
|
|
34414
|
+
const next = { ...state, files: response.files };
|
|
34415
|
+
if (!isWorkflowState(next))
|
|
34416
|
+
throw new DockerFailure("output_limit");
|
|
34417
|
+
state = structuredClone(next);
|
|
34418
|
+
return { ok: true, output: response.output };
|
|
34419
|
+
}
|
|
34420
|
+
try {
|
|
34421
|
+
const image = await command(["image", "inspect", WORKFLOW_SANDBOX_IMAGE, "--format", "{{.Id}}"], "", signal);
|
|
34422
|
+
if (image.code !== 0 || !/^sha256:[a-f0-9]{64}\s*$/.test(image.stdout))
|
|
34423
|
+
throw new DockerFailure("unavailable");
|
|
34424
|
+
creationAttempted = true;
|
|
34425
|
+
let created;
|
|
34426
|
+
try {
|
|
34427
|
+
created = await command([
|
|
34428
|
+
"create",
|
|
34429
|
+
"--pull=never",
|
|
34430
|
+
"--name",
|
|
34431
|
+
name,
|
|
34432
|
+
"--label",
|
|
34433
|
+
"artemiskit.workflow=true",
|
|
34434
|
+
"--label",
|
|
34435
|
+
`${OWNER_LABEL}=${owner}`,
|
|
34436
|
+
"--network",
|
|
34437
|
+
"none",
|
|
34438
|
+
"--read-only",
|
|
34439
|
+
"--cap-drop",
|
|
34440
|
+
"ALL",
|
|
34441
|
+
"--security-opt",
|
|
34442
|
+
"no-new-privileges",
|
|
34443
|
+
"--memory",
|
|
34444
|
+
"128m",
|
|
34445
|
+
"--memory-swap",
|
|
34446
|
+
"128m",
|
|
34447
|
+
"--cpus",
|
|
34448
|
+
"0.5",
|
|
34449
|
+
"--pids-limit",
|
|
34450
|
+
"64",
|
|
34451
|
+
"--user",
|
|
34452
|
+
"1000:1000",
|
|
34453
|
+
"--tmpfs",
|
|
34454
|
+
"/workspace:rw,noexec,nosuid,nodev,size=16777216,uid=1000,gid=1000,mode=0700",
|
|
34455
|
+
"--tmpfs",
|
|
34456
|
+
"/tmp:rw,noexec,nosuid,nodev,size=8388608,uid=1000,gid=1000,mode=0700",
|
|
34457
|
+
"--workdir",
|
|
34458
|
+
"/workspace",
|
|
34459
|
+
"--entrypoint",
|
|
34460
|
+
"bun",
|
|
34461
|
+
image.stdout.trim(),
|
|
34462
|
+
"--eval",
|
|
34463
|
+
"setInterval(() => {}, 1000)"
|
|
34464
|
+
], "", signal);
|
|
34465
|
+
} catch (error2) {
|
|
34466
|
+
creationUncertain = error2 instanceof DockerFailure && error2.uncertain;
|
|
34467
|
+
throw error2;
|
|
34468
|
+
}
|
|
34469
|
+
if (created.code !== 0)
|
|
34470
|
+
throw new DockerFailure("unavailable");
|
|
34471
|
+
const started = await command(["start", name], "", signal);
|
|
34472
|
+
if (started.code !== 0)
|
|
34473
|
+
throw new DockerFailure("unavailable");
|
|
34474
|
+
const initialized2 = await fileOperation({ action: "init", files }, signal);
|
|
34475
|
+
if (!initialized2.ok)
|
|
34476
|
+
throw new DockerFailure("unavailable");
|
|
34477
|
+
} catch {
|
|
34478
|
+
const cleanup = await close(new AbortController().signal);
|
|
34479
|
+
throw new WorkflowEnvironmentInitializationError({
|
|
34480
|
+
...cleanup,
|
|
34481
|
+
pendingOperations: children.size + (creationUncertain ? 1 : 0)
|
|
34482
|
+
});
|
|
34483
|
+
}
|
|
34484
|
+
return {
|
|
34485
|
+
type: "sandbox",
|
|
34486
|
+
capabilities: {
|
|
34487
|
+
network: "denied",
|
|
34488
|
+
commands: "denied",
|
|
34489
|
+
externalSideEffects: "denied",
|
|
34490
|
+
isolation: "container"
|
|
34491
|
+
},
|
|
34492
|
+
async execute(request, operationSignal) {
|
|
34493
|
+
const tool = getWorkflowTool(request.tool)?.id ?? "unknown";
|
|
34494
|
+
const failure2 = (status, code) => ({
|
|
34495
|
+
status,
|
|
34496
|
+
code,
|
|
34497
|
+
evidence: { tool, version: "1", status, code }
|
|
34498
|
+
});
|
|
34499
|
+
if (closed || closing || busy || operationSignal.aborted || signal.aborted)
|
|
34500
|
+
return failure2("failed", "tool_error");
|
|
34501
|
+
const checked = executeSimulatedTool({
|
|
34502
|
+
tool: request.tool,
|
|
34503
|
+
input: request.input,
|
|
34504
|
+
state,
|
|
34505
|
+
policy: configuration.environment.policy,
|
|
34506
|
+
declaredTools: configuration.tools
|
|
34507
|
+
});
|
|
34508
|
+
if (checked.status === "invalid" && checked.code === "invalid_input")
|
|
34509
|
+
return checked;
|
|
34510
|
+
if (!workflowPathAllowed(configuration, request.tool, request.input))
|
|
34511
|
+
return failure2("denied", "permission_denied");
|
|
34512
|
+
if (checked.status !== "succeeded")
|
|
34513
|
+
return checked;
|
|
34514
|
+
busy = true;
|
|
34515
|
+
try {
|
|
34516
|
+
if (request.tool === "read_file" || request.tool === "write_file") {
|
|
34517
|
+
if (!isWorkflowState(request.input))
|
|
34518
|
+
return failure2("invalid", "invalid_input");
|
|
34519
|
+
const result = await fileOperation({ action: request.tool === "read_file" ? "read" : "write", ...request.input }, operationSignal);
|
|
34520
|
+
if (!result.ok)
|
|
34521
|
+
return failure2(result.code === "invalid_input" ? "invalid" : "failed", result.code);
|
|
34522
|
+
if (!isWorkflowJson(result.output))
|
|
34523
|
+
return failure2("failed", "tool_error");
|
|
34524
|
+
return {
|
|
34525
|
+
status: "succeeded",
|
|
34526
|
+
output: structuredClone(result.output),
|
|
34527
|
+
state: structuredClone(state),
|
|
34528
|
+
evidence: { tool, version: "1", status: "succeeded" }
|
|
34529
|
+
};
|
|
34530
|
+
}
|
|
34531
|
+
state = structuredClone(checked.state);
|
|
34532
|
+
return checked;
|
|
34533
|
+
} catch {
|
|
34534
|
+
return failure2("failed", "tool_error");
|
|
34535
|
+
} finally {
|
|
34536
|
+
busy = false;
|
|
34537
|
+
}
|
|
34538
|
+
},
|
|
34539
|
+
async snapshot(snapshotSignal) {
|
|
34540
|
+
if (closed || closing || busy || snapshotSignal.aborted)
|
|
34541
|
+
throw new DockerFailure("unavailable");
|
|
34542
|
+
busy = true;
|
|
34543
|
+
try {
|
|
34544
|
+
const result = await fileOperation({ action: "snapshot" }, snapshotSignal);
|
|
34545
|
+
if (!result.ok)
|
|
34546
|
+
throw new DockerFailure("unavailable");
|
|
34547
|
+
return structuredClone(state);
|
|
34548
|
+
} finally {
|
|
34549
|
+
busy = false;
|
|
34550
|
+
}
|
|
34551
|
+
},
|
|
34552
|
+
close
|
|
34553
|
+
};
|
|
34554
|
+
};
|
|
34555
|
+
}
|
|
34556
|
+
var createDockerWorkflowEnvironment = createDockerWorkflowEnvironmentFactory();
|
|
34557
|
+
// src/agent-workflow/session.ts
|
|
34558
|
+
var import_ajv5 = __toESM(require_ajv(), 1);
|
|
34559
|
+
import { createHash as createHash4, randomUUID as randomUUID2 } from "node:crypto";
|
|
34560
|
+
var hash = (value) => createHash4("sha256").update(value).digest("hex");
|
|
34561
|
+
function identity(value) {
|
|
34562
|
+
return {
|
|
34563
|
+
sha256: hash(value),
|
|
34564
|
+
.../^[A-Za-z][A-Za-z0-9._:/-]{0,127}$/.test(value) && !/(?:secret|token|password|credential|api.?key|^sk-|^npm_)/i.test(value) ? { display: value } : {}
|
|
34565
|
+
};
|
|
34566
|
+
}
|
|
34567
|
+
var zeroUsage = () => ({ prompt: 0, completion: 0, total: 0 });
|
|
34568
|
+
var usageSchema = exports_external.object({
|
|
34569
|
+
prompt: exports_external.number().int().nonnegative().safe(),
|
|
34570
|
+
completion: exports_external.number().int().nonnegative().safe(),
|
|
34571
|
+
total: exports_external.number().int().nonnegative().safe()
|
|
34572
|
+
}).strict();
|
|
34573
|
+
var callSchema = exports_external.object({
|
|
34574
|
+
id: exports_external.string().min(1).max(256),
|
|
34575
|
+
type: exports_external.literal("function"),
|
|
34576
|
+
function: exports_external.object({
|
|
34577
|
+
name: exports_external.string().regex(/^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$/),
|
|
34578
|
+
arguments: exports_external.string().max(1e6)
|
|
34579
|
+
}).strict()
|
|
34580
|
+
}).strict();
|
|
34581
|
+
var completedSchema = exports_external.object({
|
|
34582
|
+
status: exports_external.literal("completed"),
|
|
34583
|
+
id: exports_external.string().min(1).max(256),
|
|
34584
|
+
model: exports_external.string().min(1).max(256),
|
|
34585
|
+
message: exports_external.object({
|
|
34586
|
+
role: exports_external.literal("assistant"),
|
|
34587
|
+
content: exports_external.string().max(1e6),
|
|
34588
|
+
tool_calls: exports_external.array(callSchema).max(100).optional()
|
|
34589
|
+
}).strict(),
|
|
34590
|
+
tokens: usageSchema,
|
|
34591
|
+
usageAvailable: exports_external.boolean().optional(),
|
|
34592
|
+
latencyMs: exports_external.number().finite().nonnegative(),
|
|
34593
|
+
finishReason: exports_external.enum(["stop", "length", "tool_calls", "content_filter"]).optional()
|
|
34594
|
+
}).strict();
|
|
34595
|
+
var failureSchema = exports_external.object({
|
|
34596
|
+
status: exports_external.enum(["unsupported", "invalid", "error"]),
|
|
34597
|
+
tokens: usageSchema.optional(),
|
|
34598
|
+
usageAvailable: exports_external.boolean().optional(),
|
|
34599
|
+
rejectedCall: exports_external.object({
|
|
34600
|
+
requestedCallIdHash: exports_external.string().regex(/^[a-f0-9]{64}$/),
|
|
34601
|
+
tool: exports_external.string(),
|
|
34602
|
+
reason: exports_external.enum(["undeclared_tool", "invalid_arguments", "duplicate_id"])
|
|
34603
|
+
}).strict().optional(),
|
|
34604
|
+
code: exports_external.enum([
|
|
34605
|
+
"invalid_request",
|
|
34606
|
+
"tool_use_unsupported",
|
|
34607
|
+
"invalid_response",
|
|
34608
|
+
"target_error",
|
|
34609
|
+
"timeout",
|
|
34610
|
+
"aborted"
|
|
34611
|
+
])
|
|
34612
|
+
}).strict();
|
|
34613
|
+
var capabilitySchema = exports_external.object({
|
|
34614
|
+
status: exports_external.literal("available"),
|
|
34615
|
+
toolUse: exports_external.boolean(),
|
|
34616
|
+
transportCancellation: exports_external.boolean()
|
|
34617
|
+
}).strict();
|
|
34618
|
+
var cleanupSchema = exports_external.object({
|
|
34619
|
+
status: exports_external.enum(["completed", "unresolved"]),
|
|
34620
|
+
artifacts: exports_external.enum(["discarded", "retained", "unknown"])
|
|
34621
|
+
}).strict();
|
|
34622
|
+
var failureCodes = new Set([
|
|
34623
|
+
"undeclared_tool",
|
|
34624
|
+
"permission_denied",
|
|
34625
|
+
"invalid_input",
|
|
34626
|
+
"invalid_state",
|
|
34627
|
+
"not_found",
|
|
34628
|
+
"output_limit",
|
|
34629
|
+
"invalid_policy",
|
|
34630
|
+
"tool_error"
|
|
34631
|
+
]);
|
|
34632
|
+
function safeBoundary(value) {
|
|
34633
|
+
let nodes = 0;
|
|
34634
|
+
let bytes = 0;
|
|
34635
|
+
const parents = new Set;
|
|
34636
|
+
function check(item, depth) {
|
|
34637
|
+
if (++nodes > 1e4 || depth > 16)
|
|
34638
|
+
return false;
|
|
34639
|
+
if (item === undefined || item === null || typeof item === "boolean")
|
|
34640
|
+
return true;
|
|
34641
|
+
if (typeof item === "number")
|
|
34642
|
+
return Number.isFinite(item);
|
|
34643
|
+
if (typeof item === "string") {
|
|
34644
|
+
bytes += Buffer.byteLength(item);
|
|
34645
|
+
return bytes <= 1048576;
|
|
34646
|
+
}
|
|
34647
|
+
if (typeof item !== "object" || parents.has(item) || !Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null)
|
|
34648
|
+
return false;
|
|
34649
|
+
if (Object.getOwnPropertySymbols(item).length || Array.isArray(item) && item.length > 1e4)
|
|
34650
|
+
return false;
|
|
34651
|
+
parents.add(item);
|
|
34652
|
+
for (const key of Object.getOwnPropertyNames(item)) {
|
|
34653
|
+
if (Array.isArray(item) && key === "length")
|
|
34654
|
+
continue;
|
|
34655
|
+
bytes += Buffer.byteLength(key);
|
|
34656
|
+
const descriptor2 = Object.getOwnPropertyDescriptor(item, key);
|
|
34657
|
+
if (bytes > 1048576 || ["__proto__", "constructor", "prototype"].includes(key) || !descriptor2 || !("value" in descriptor2) || !descriptor2.enumerable || !check(descriptor2.value, depth + 1))
|
|
34658
|
+
return false;
|
|
34659
|
+
}
|
|
34660
|
+
parents.delete(item);
|
|
34661
|
+
return true;
|
|
34662
|
+
}
|
|
34663
|
+
try {
|
|
34664
|
+
return check(value, 0);
|
|
34665
|
+
} catch {
|
|
34666
|
+
return false;
|
|
34667
|
+
}
|
|
34668
|
+
}
|
|
34669
|
+
|
|
34670
|
+
class Stop extends Error {
|
|
34671
|
+
execution;
|
|
34672
|
+
reason;
|
|
34673
|
+
constructor(execution, reason) {
|
|
34674
|
+
super(reason);
|
|
34675
|
+
this.execution = execution;
|
|
34676
|
+
this.reason = reason;
|
|
34677
|
+
}
|
|
34678
|
+
}
|
|
34679
|
+
function createAgentWorkflowSession(options) {
|
|
34680
|
+
const controller = new AbortController;
|
|
34681
|
+
let sessionState = "idle";
|
|
34682
|
+
let promise;
|
|
34683
|
+
let workflow;
|
|
34684
|
+
try {
|
|
34685
|
+
workflow = AgentWorkflowSchema.parse(options.workflow);
|
|
34686
|
+
} catch {}
|
|
34687
|
+
const target = options.target;
|
|
34688
|
+
const retained = [];
|
|
34689
|
+
const listeners = new Set;
|
|
34690
|
+
let finished = false;
|
|
34691
|
+
let started = 0;
|
|
34692
|
+
let eventSequence = 0;
|
|
34693
|
+
const record = {
|
|
34694
|
+
schemaVersion: "1",
|
|
34695
|
+
engine: "native",
|
|
34696
|
+
execution: "invalid",
|
|
34697
|
+
reason: "invalid_workflow",
|
|
34698
|
+
policy: "passed",
|
|
34699
|
+
...workflow ? {
|
|
34700
|
+
configuration: {
|
|
34701
|
+
sha256: hash(JSON.stringify(workflow)),
|
|
34702
|
+
provider: identity(workflow.target.provider),
|
|
34703
|
+
model: identity(workflow.target.model),
|
|
34704
|
+
generation: {
|
|
34705
|
+
maxTokens: workflow.target.generation?.max_tokens ?? 1024,
|
|
34706
|
+
temperature: workflow.target.generation?.temperature ?? 0
|
|
34707
|
+
},
|
|
34708
|
+
limits: structuredClone(workflow.environment.policy.budgets)
|
|
34709
|
+
}
|
|
34710
|
+
} : {},
|
|
34711
|
+
taskVerification: "unavailable",
|
|
34712
|
+
environment: workflow?.environment.type ?? "unknown",
|
|
34713
|
+
capability: {
|
|
34714
|
+
advertised: null,
|
|
34715
|
+
transportCancellation: false,
|
|
34716
|
+
preflight: options.preflight || options.preflightOnly ? "failed" : "not_requested"
|
|
34717
|
+
},
|
|
34718
|
+
usage: {
|
|
34719
|
+
status: "unavailable",
|
|
34720
|
+
reported: zeroUsage(),
|
|
34721
|
+
missingRequests: 0,
|
|
34722
|
+
inFlightUnknown: false,
|
|
34723
|
+
preflight: zeroUsage()
|
|
34724
|
+
},
|
|
34725
|
+
budgets: {
|
|
34726
|
+
actions: 0,
|
|
34727
|
+
modelRequests: 0,
|
|
34728
|
+
toolCalls: 0,
|
|
34729
|
+
modelRequestAccounting: "target_invocations",
|
|
34730
|
+
transportAttempts: "unavailable",
|
|
34731
|
+
tokenOvershoot: 0,
|
|
34732
|
+
elapsedMs: 0
|
|
34733
|
+
},
|
|
34734
|
+
cleanup: { status: "completed", artifacts: "discarded", pendingOperations: 0 },
|
|
34735
|
+
artifacts: { state: "unavailable" },
|
|
34736
|
+
events: retained,
|
|
34737
|
+
droppedEvents: 0
|
|
34738
|
+
};
|
|
34739
|
+
const pending = new Set;
|
|
34740
|
+
const modelPending = new Set;
|
|
34741
|
+
let transcript = [];
|
|
34742
|
+
let state = null;
|
|
34743
|
+
let environment;
|
|
34744
|
+
let deadlineExpired = false;
|
|
34745
|
+
let phase = "execution";
|
|
34746
|
+
let measuredRequests = 0;
|
|
34747
|
+
const ids = new Set;
|
|
34748
|
+
const emit = (event) => {
|
|
34749
|
+
const value = {
|
|
34750
|
+
...event,
|
|
34751
|
+
sequence: ++eventSequence,
|
|
34752
|
+
elapsedMs: Math.max(0, Date.now() - started),
|
|
34753
|
+
phase
|
|
34754
|
+
};
|
|
34755
|
+
if (retained.length < 255 || value.type === "finished")
|
|
34756
|
+
retained.push(value);
|
|
34757
|
+
else
|
|
34758
|
+
record.droppedEvents++;
|
|
34759
|
+
try {
|
|
34760
|
+
options.onEvent?.(structuredClone(value));
|
|
34761
|
+
} catch {}
|
|
34762
|
+
for (const wake of listeners)
|
|
34763
|
+
wake();
|
|
34764
|
+
};
|
|
34765
|
+
const abort = () => {
|
|
34766
|
+
if (!finished) {
|
|
34767
|
+
controller.abort();
|
|
34768
|
+
if (sessionState === "running")
|
|
34769
|
+
sessionState = "cancelling";
|
|
34770
|
+
}
|
|
34771
|
+
};
|
|
34772
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
34773
|
+
if (options.signal?.aborted)
|
|
34774
|
+
abort();
|
|
34775
|
+
function active() {
|
|
34776
|
+
if (controller.signal.aborted)
|
|
34777
|
+
throw new Stop(deadlineExpired ? "timeout" : "cancelled", deadlineExpired ? "deadline" : "cancelled");
|
|
34778
|
+
if (workflow && Date.now() - started >= workflow.environment.policy.budgets.timeout_ms) {
|
|
34779
|
+
deadlineExpired = true;
|
|
34780
|
+
controller.abort();
|
|
34781
|
+
throw new Stop("timeout", "deadline");
|
|
34782
|
+
}
|
|
34783
|
+
}
|
|
34784
|
+
async function owned(operation, signal, isModel = false) {
|
|
34785
|
+
if (signal.aborted)
|
|
34786
|
+
throw new Stop(deadlineExpired ? "timeout" : "cancelled", deadlineExpired ? "deadline" : "cancelled");
|
|
34787
|
+
const work = Promise.resolve().then(() => {
|
|
34788
|
+
if (signal.aborted)
|
|
34789
|
+
throw new Stop("cancelled", "cancelled");
|
|
34790
|
+
return operation();
|
|
34791
|
+
});
|
|
34792
|
+
pending.add(work);
|
|
34793
|
+
if (isModel)
|
|
34794
|
+
modelPending.add(work);
|
|
34795
|
+
work.then(() => {
|
|
34796
|
+
pending.delete(work);
|
|
34797
|
+
modelPending.delete(work);
|
|
34798
|
+
}, () => {
|
|
34799
|
+
pending.delete(work);
|
|
34800
|
+
modelPending.delete(work);
|
|
34801
|
+
});
|
|
34802
|
+
return new Promise((resolve4, reject) => {
|
|
34803
|
+
const onAbort = () => {
|
|
34804
|
+
signal.removeEventListener("abort", onAbort);
|
|
34805
|
+
reject(new Stop(deadlineExpired ? "timeout" : "cancelled", deadlineExpired ? "deadline" : "cancelled"));
|
|
34806
|
+
};
|
|
34807
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
34808
|
+
work.then((value) => {
|
|
34809
|
+
signal.removeEventListener("abort", onAbort);
|
|
34810
|
+
resolve4(value);
|
|
34811
|
+
}, (error2) => {
|
|
34812
|
+
signal.removeEventListener("abort", onAbort);
|
|
34813
|
+
reject(error2);
|
|
34814
|
+
});
|
|
34815
|
+
});
|
|
34816
|
+
}
|
|
34817
|
+
function admit(kind) {
|
|
34818
|
+
active();
|
|
34819
|
+
if (!workflow)
|
|
34820
|
+
throw new Stop("invalid", "invalid_workflow");
|
|
34821
|
+
const budget = workflow.environment.policy.budgets;
|
|
34822
|
+
if (record.budgets.actions >= budget.max_actions)
|
|
34823
|
+
throw new Stop("budget_exceeded", "max_actions");
|
|
34824
|
+
if (kind === "model" && record.budgets.modelRequests >= (budget.max_model_requests ?? budget.max_actions))
|
|
34825
|
+
throw new Stop("budget_exceeded", "max_model_requests");
|
|
34826
|
+
if (kind === "tool" && record.budgets.toolCalls >= (budget.max_tool_calls ?? budget.max_actions))
|
|
34827
|
+
throw new Stop("budget_exceeded", "max_tool_calls");
|
|
34828
|
+
if (budget.max_tokens !== undefined) {
|
|
34829
|
+
if (record.usage.missingRequests)
|
|
34830
|
+
throw new Stop("budget_exceeded", "usage_unavailable");
|
|
34831
|
+
if (record.usage.reported.total >= budget.max_tokens)
|
|
34832
|
+
throw new Stop("budget_exceeded", "max_tokens");
|
|
34833
|
+
}
|
|
34834
|
+
record.budgets.actions++;
|
|
34835
|
+
if (kind === "model")
|
|
34836
|
+
record.budgets.modelRequests++;
|
|
34837
|
+
else
|
|
34838
|
+
record.budgets.toolCalls++;
|
|
34839
|
+
}
|
|
34840
|
+
const ajv3 = new import_ajv5.default({
|
|
34841
|
+
strict: true,
|
|
34842
|
+
allErrors: false,
|
|
34843
|
+
coerceTypes: false,
|
|
34844
|
+
useDefaults: false,
|
|
34845
|
+
removeAdditional: false
|
|
34846
|
+
});
|
|
34847
|
+
async function model(messages, tools2) {
|
|
34848
|
+
if (!workflow)
|
|
34849
|
+
throw new Stop("invalid", "invalid_workflow");
|
|
34850
|
+
const budget = workflow.environment.policy.budgets;
|
|
34851
|
+
const remainingTokens = budget.max_tokens === undefined ? 1e6 : Math.max(1, budget.max_tokens - record.usage.reported.total);
|
|
34852
|
+
const request = {
|
|
34853
|
+
messages: structuredClone(messages),
|
|
34854
|
+
tools: structuredClone(tools2),
|
|
34855
|
+
model: workflow.target.model,
|
|
34856
|
+
generation: {
|
|
34857
|
+
maxTokens: Math.min(workflow.target.generation?.max_tokens ?? 1024, remainingTokens),
|
|
34858
|
+
temperature: workflow.target.generation?.temperature ?? 0
|
|
34859
|
+
},
|
|
34860
|
+
budgets: {
|
|
34861
|
+
timeoutMs: Math.max(1, budget.timeout_ms - (Date.now() - started)),
|
|
34862
|
+
maxToolCalls: 100
|
|
34863
|
+
}
|
|
34864
|
+
};
|
|
34865
|
+
if (!safeBoundary(request) || !agentTurnRequestSchema.safeParse(request).success || !validWorkflowTranscript(messages))
|
|
34866
|
+
throw new Stop("invalid", "transcript_limit");
|
|
34867
|
+
admit("model");
|
|
34868
|
+
const operationId = `model-${record.budgets.modelRequests}`;
|
|
34869
|
+
emit({ type: "model_requested", operationId });
|
|
34870
|
+
let response;
|
|
34871
|
+
let modelCompleted = false;
|
|
34872
|
+
try {
|
|
34873
|
+
try {
|
|
34874
|
+
response = await owned(() => target.turn(request, controller.signal), controller.signal, true);
|
|
34875
|
+
} catch (error2) {
|
|
34876
|
+
if (error2 instanceof Stop)
|
|
34877
|
+
throw error2;
|
|
34878
|
+
throw new Stop("failed", "target_error");
|
|
34879
|
+
}
|
|
34880
|
+
if (!safeBoundary(response)) {
|
|
34881
|
+
record.usage.missingRequests++;
|
|
34882
|
+
throw new Stop("invalid", "invalid_response");
|
|
34883
|
+
}
|
|
34884
|
+
const failure2 = failureSchema.safeParse(response);
|
|
34885
|
+
if (failure2.success) {
|
|
34886
|
+
if (failure2.data.tokens && failure2.data.tokens.total === failure2.data.tokens.prompt + failure2.data.tokens.completion && failure2.data.usageAvailable !== false && (failure2.data.usageAvailable === true || failure2.data.tokens.total > 0)) {
|
|
34887
|
+
measuredRequests++;
|
|
34888
|
+
for (const key of ["prompt", "completion", "total"]) {
|
|
34889
|
+
record.usage.reported[key] += failure2.data.tokens[key];
|
|
34890
|
+
if (phase === "preflight")
|
|
34891
|
+
record.usage.preflight[key] += failure2.data.tokens[key];
|
|
34892
|
+
}
|
|
34893
|
+
} else
|
|
34894
|
+
record.usage.missingRequests++;
|
|
34895
|
+
if (failure2.data.rejectedCall) {
|
|
34896
|
+
const rejection = failure2.data.rejectedCall;
|
|
34897
|
+
const denied = rejection.reason === "undeclared_tool";
|
|
34898
|
+
if (denied)
|
|
34899
|
+
record.policy = "denied";
|
|
34900
|
+
admit("tool");
|
|
34901
|
+
const rejectedOperationId = `tool-${record.budgets.toolCalls}`;
|
|
34902
|
+
const tool = getWorkflowTool(rejection.tool)?.id ?? "unknown";
|
|
34903
|
+
emit({
|
|
34904
|
+
type: "tool_requested",
|
|
34905
|
+
operationId: rejectedOperationId,
|
|
34906
|
+
requestedCallIdHash: rejection.requestedCallIdHash,
|
|
34907
|
+
tool
|
|
34908
|
+
});
|
|
34909
|
+
emit({
|
|
34910
|
+
type: "tool_completed",
|
|
34911
|
+
operationId: rejectedOperationId,
|
|
34912
|
+
requestedCallIdHash: rejection.requestedCallIdHash,
|
|
34913
|
+
tool,
|
|
34914
|
+
status: denied ? "denied" : "invalid"
|
|
34915
|
+
});
|
|
34916
|
+
if (denied)
|
|
34917
|
+
throw new Stop("invalid", "policy_denied");
|
|
34918
|
+
}
|
|
34919
|
+
if (failure2.data.code === "timeout")
|
|
34920
|
+
throw new Stop("timeout", "deadline");
|
|
34921
|
+
if (failure2.data.code === "aborted")
|
|
34922
|
+
throw new Stop("cancelled", "cancelled");
|
|
34923
|
+
throw new Stop(failure2.data.status === "unsupported" ? "unsupported" : failure2.data.status === "invalid" ? "invalid" : "failed", failure2.data.code === "tool_use_unsupported" ? "tool_use_unsupported" : failure2.data.status === "invalid" ? "invalid_response" : "target_error");
|
|
34924
|
+
}
|
|
34925
|
+
const parsed = completedSchema.safeParse(response);
|
|
34926
|
+
if (!parsed.success || parsed.data.tokens.total !== parsed.data.tokens.prompt + parsed.data.tokens.completion) {
|
|
34927
|
+
record.usage.missingRequests++;
|
|
34928
|
+
throw new Stop("invalid", "invalid_response");
|
|
34929
|
+
}
|
|
34930
|
+
const value = parsed.data;
|
|
34931
|
+
const measured = value.usageAvailable !== false && (value.usageAvailable === true || value.tokens.total > 0);
|
|
34932
|
+
if (measured) {
|
|
34933
|
+
measuredRequests++;
|
|
34934
|
+
for (const key of ["prompt", "completion", "total"]) {
|
|
34935
|
+
record.usage.reported[key] += value.tokens[key];
|
|
34936
|
+
if (phase === "preflight")
|
|
34937
|
+
record.usage.preflight[key] += value.tokens[key];
|
|
34938
|
+
}
|
|
34939
|
+
} else
|
|
34940
|
+
record.usage.missingRequests++;
|
|
34941
|
+
record.capability.observedModelHash = hash(value.model);
|
|
34942
|
+
record.capability.observedModel = identity(value.model);
|
|
34943
|
+
const calls = value.message.tool_calls ?? [];
|
|
34944
|
+
if (value.finishReason === "tool_calls" && !calls.length || calls.length && value.finishReason && value.finishReason !== "tool_calls")
|
|
34945
|
+
throw new Stop("invalid", "invalid_response");
|
|
34946
|
+
for (const call of calls) {
|
|
34947
|
+
if (ids.has(call.id))
|
|
34948
|
+
throw new Stop("invalid", "invalid_response");
|
|
34949
|
+
ids.add(call.id);
|
|
34950
|
+
}
|
|
34951
|
+
modelCompleted = true;
|
|
34952
|
+
emit({ type: "model_completed", operationId, status: "completed" });
|
|
34953
|
+
if (budget.max_tokens !== undefined && !measured)
|
|
34954
|
+
throw new Stop("budget_exceeded", "usage_unavailable");
|
|
34955
|
+
if (budget.max_tokens !== undefined && record.usage.reported.total > budget.max_tokens) {
|
|
34956
|
+
record.budgets.tokenOvershoot = record.usage.reported.total - budget.max_tokens;
|
|
34957
|
+
throw new Stop("budget_exceeded", "max_tokens");
|
|
34958
|
+
}
|
|
34959
|
+
active();
|
|
34960
|
+
return value;
|
|
34961
|
+
} finally {
|
|
34962
|
+
if (!modelCompleted)
|
|
34963
|
+
emit({ type: "model_completed", operationId, status: "failed" });
|
|
34964
|
+
}
|
|
34965
|
+
}
|
|
34966
|
+
function originalArguments(call, tools2) {
|
|
34967
|
+
const definition2 = tools2.find((entry) => entry.function.name === call.function.name);
|
|
34968
|
+
if (!definition2) {
|
|
34969
|
+
record.policy = "denied";
|
|
34970
|
+
throw new Stop("invalid", "policy_denied");
|
|
34971
|
+
}
|
|
34972
|
+
let input;
|
|
34973
|
+
try {
|
|
34974
|
+
input = JSON.parse(call.function.arguments);
|
|
34975
|
+
} catch {
|
|
34976
|
+
throw new Stop("invalid", "invalid_response");
|
|
34977
|
+
}
|
|
34978
|
+
if (!isWorkflowState(input) || !ajv3.compile(definition2.function.parameters)(input))
|
|
34979
|
+
throw new Stop("invalid", "invalid_response");
|
|
34980
|
+
return input;
|
|
34981
|
+
}
|
|
34982
|
+
async function probe() {
|
|
34983
|
+
phase = "preflight";
|
|
34984
|
+
const nonce = randomUUID2();
|
|
34985
|
+
const tools2 = [
|
|
34986
|
+
{
|
|
34987
|
+
type: "function",
|
|
34988
|
+
function: {
|
|
34989
|
+
name: "artemis_probe",
|
|
34990
|
+
description: "Echo the exact supplied nonce to verify structured tool protocol.",
|
|
34991
|
+
parameters: {
|
|
34992
|
+
type: "object",
|
|
34993
|
+
properties: { nonce: { const: nonce } },
|
|
34994
|
+
required: ["nonce"],
|
|
34995
|
+
additionalProperties: false
|
|
34996
|
+
}
|
|
34997
|
+
}
|
|
34998
|
+
}
|
|
34999
|
+
];
|
|
35000
|
+
const messages = [
|
|
35001
|
+
{
|
|
35002
|
+
role: "user",
|
|
35003
|
+
content: `Call artemis_probe once with nonce ${nonce}. After receiving its result, reply with only that nonce.`
|
|
35004
|
+
}
|
|
35005
|
+
];
|
|
35006
|
+
const first = await model(messages, tools2);
|
|
35007
|
+
const calls = first.message.tool_calls ?? [];
|
|
35008
|
+
if (calls.length !== 1)
|
|
35009
|
+
throw new Stop("unsupported", "preflight_failed");
|
|
35010
|
+
admit("tool");
|
|
35011
|
+
const call = calls[0];
|
|
35012
|
+
originalArguments(call, tools2);
|
|
35013
|
+
const operationId = `tool-${record.budgets.toolCalls}`;
|
|
35014
|
+
const requestedCallIdHash = hash(call.id);
|
|
35015
|
+
emit({ type: "tool_requested", operationId, requestedCallIdHash, tool: "artemis_probe" });
|
|
35016
|
+
messages.push(first.message, {
|
|
35017
|
+
role: "tool",
|
|
35018
|
+
toolCallId: call.id,
|
|
35019
|
+
content: JSON.stringify({ nonce })
|
|
35020
|
+
});
|
|
35021
|
+
emit({
|
|
35022
|
+
type: "tool_completed",
|
|
35023
|
+
operationId,
|
|
35024
|
+
requestedCallIdHash,
|
|
35025
|
+
tool: "artemis_probe",
|
|
35026
|
+
status: "completed"
|
|
35027
|
+
});
|
|
35028
|
+
const second = await model(messages, tools2);
|
|
35029
|
+
if (second.message.tool_calls?.length || second.message.content !== nonce)
|
|
35030
|
+
throw new Stop("unsupported", "preflight_failed");
|
|
35031
|
+
record.capability.preflight = "passed";
|
|
35032
|
+
emit({ type: "preflight_completed", status: "completed" });
|
|
35033
|
+
phase = "execution";
|
|
35034
|
+
}
|
|
35035
|
+
async function executeTool(call, tools2) {
|
|
35036
|
+
if (!workflow || !environment)
|
|
35037
|
+
throw new Stop("failed", "environment_unavailable");
|
|
35038
|
+
admit("tool");
|
|
35039
|
+
const operationId = `tool-${record.budgets.toolCalls}`;
|
|
35040
|
+
const requestedCallIdHash = hash(call.id);
|
|
35041
|
+
const known = getWorkflowTool(call.function.name);
|
|
35042
|
+
emit({
|
|
35043
|
+
type: "tool_requested",
|
|
35044
|
+
operationId,
|
|
35045
|
+
requestedCallIdHash,
|
|
35046
|
+
tool: known?.id ?? "unknown"
|
|
35047
|
+
});
|
|
35048
|
+
let toolCompleted = false;
|
|
35049
|
+
try {
|
|
35050
|
+
const input = originalArguments(call, tools2);
|
|
35051
|
+
if (!workflowToolPermitted(workflow, call.function.name) || !workflowPathAllowed(workflow, call.function.name, input)) {
|
|
35052
|
+
record.policy = "denied";
|
|
35053
|
+
toolCompleted = true;
|
|
35054
|
+
emit({
|
|
35055
|
+
type: "tool_completed",
|
|
35056
|
+
operationId,
|
|
35057
|
+
requestedCallIdHash,
|
|
35058
|
+
tool: known?.id ?? "unknown",
|
|
35059
|
+
status: "denied"
|
|
35060
|
+
});
|
|
35061
|
+
throw new Stop("invalid", "policy_denied");
|
|
35062
|
+
}
|
|
35063
|
+
let value;
|
|
35064
|
+
try {
|
|
35065
|
+
value = await owned(() => environment ? environment.execute({ tool: call.function.name, input: structuredClone(input) }, controller.signal) : Promise.reject(), controller.signal);
|
|
35066
|
+
} catch (error2) {
|
|
35067
|
+
if (error2 instanceof Stop)
|
|
35068
|
+
throw error2;
|
|
35069
|
+
throw new Stop("failed", "tool_failed");
|
|
35070
|
+
}
|
|
35071
|
+
if (!isWorkflowState(value) || !known)
|
|
35072
|
+
throw new Stop("invalid", "invalid_environment");
|
|
35073
|
+
const status = value.status;
|
|
35074
|
+
if (!isWorkflowState(value.evidence) || value.evidence.tool !== known.id || value.evidence.version !== "1" || value.evidence.status !== status || Object.keys(value).some((key) => !(status === "succeeded" ? ["status", "output", "state", "evidence"] : ["status", "code", "evidence"]).includes(key)))
|
|
35075
|
+
throw new Stop("invalid", "invalid_environment");
|
|
35076
|
+
if (status === "succeeded") {
|
|
35077
|
+
if (!isWorkflowState(value.state) || !isWorkflowJson(value.output) || !ajv3.compile(known.outputSchema)(value.output))
|
|
35078
|
+
throw new Stop("invalid", "invalid_environment");
|
|
35079
|
+
state = structuredClone(value.state);
|
|
35080
|
+
transcript.push({
|
|
35081
|
+
role: "tool",
|
|
35082
|
+
toolCallId: call.id,
|
|
35083
|
+
content: JSON.stringify(value.output)
|
|
35084
|
+
});
|
|
35085
|
+
toolCompleted = true;
|
|
35086
|
+
emit({
|
|
35087
|
+
type: "tool_completed",
|
|
35088
|
+
operationId,
|
|
35089
|
+
requestedCallIdHash,
|
|
35090
|
+
tool: known.id,
|
|
35091
|
+
status: "completed"
|
|
35092
|
+
});
|
|
35093
|
+
} else if ((status === "denied" || status === "invalid" || status === "failed") && typeof value.code === "string" && failureCodes.has(value.code)) {
|
|
35094
|
+
if (status === "denied")
|
|
35095
|
+
record.policy = "denied";
|
|
35096
|
+
transcript.push({
|
|
35097
|
+
role: "tool",
|
|
35098
|
+
toolCallId: call.id,
|
|
35099
|
+
content: JSON.stringify({ status, code: value.code })
|
|
35100
|
+
});
|
|
35101
|
+
toolCompleted = true;
|
|
35102
|
+
emit({ type: "tool_completed", operationId, requestedCallIdHash, tool: known.id, status });
|
|
35103
|
+
throw new Stop(status === "denied" ? "invalid" : "failed", status === "denied" ? "policy_denied" : "tool_failed");
|
|
35104
|
+
} else
|
|
35105
|
+
throw new Stop("invalid", "invalid_environment");
|
|
35106
|
+
active();
|
|
35107
|
+
} finally {
|
|
35108
|
+
if (!toolCompleted)
|
|
35109
|
+
emit({
|
|
35110
|
+
type: "tool_completed",
|
|
35111
|
+
operationId,
|
|
35112
|
+
requestedCallIdHash,
|
|
35113
|
+
tool: known?.id ?? "unknown",
|
|
35114
|
+
status: record.policy === "denied" ? "denied" : "failed"
|
|
35115
|
+
});
|
|
35116
|
+
}
|
|
35117
|
+
}
|
|
35118
|
+
async function run() {
|
|
35119
|
+
sessionState = controller.signal.aborted ? "cancelling" : "running";
|
|
35120
|
+
started = Date.now();
|
|
35121
|
+
const timeout = workflow?.environment.policy.budgets.timeout_ms ?? 1;
|
|
35122
|
+
const timer = setTimeout(() => {
|
|
35123
|
+
deadlineExpired = true;
|
|
35124
|
+
abort();
|
|
35125
|
+
}, timeout);
|
|
35126
|
+
const cleanupMs = options.cleanupTimeoutMs ?? (workflow?.environment.type === "sandbox" ? 6000 : 1000);
|
|
35127
|
+
try {
|
|
35128
|
+
if (!workflow)
|
|
35129
|
+
throw new Stop("invalid", "invalid_workflow");
|
|
35130
|
+
if (!Number.isInteger(cleanupMs) || cleanupMs < 1 || cleanupMs > 1e4 || !target || typeof target.turn !== "function" || typeof target.capabilities !== "function")
|
|
35131
|
+
throw new Stop("invalid", "invalid_options");
|
|
35132
|
+
active();
|
|
35133
|
+
emit({ type: "started" });
|
|
35134
|
+
const initialState = options.preflightOnly ? {} : await owned(() => resolveWorkflowInitialState(workflow, options.fixtureRoot), controller.signal).catch((error2) => {
|
|
35135
|
+
if (error2 instanceof Stop)
|
|
35136
|
+
throw error2;
|
|
35137
|
+
throw new Stop("invalid", "invalid_fixture");
|
|
35138
|
+
});
|
|
35139
|
+
active();
|
|
35140
|
+
const advertised = await owned(() => target.capabilities({ timeoutMs: Math.max(1, timeout - (Date.now() - started)) }, controller.signal), controller.signal);
|
|
35141
|
+
if (!safeBoundary(advertised))
|
|
35142
|
+
throw new Stop("invalid", "invalid_response");
|
|
35143
|
+
const capability = capabilitySchema.safeParse(advertised);
|
|
35144
|
+
if (!capability.success)
|
|
35145
|
+
throw new Stop("unsupported", "target_unavailable");
|
|
35146
|
+
record.capability.advertised = capability.data.toolUse;
|
|
35147
|
+
record.capability.transportCancellation = capability.data.transportCancellation;
|
|
35148
|
+
if (!capability.data.toolUse)
|
|
35149
|
+
throw new Stop("unsupported", "tool_use_unsupported");
|
|
35150
|
+
if (options.preflight || options.preflightOnly)
|
|
35151
|
+
await probe();
|
|
35152
|
+
if (!options.preflightOnly) {
|
|
35153
|
+
active();
|
|
35154
|
+
const factory2 = options.environmentFactory ?? (workflow.environment.type === "simulated" ? createSimulatedWorkflowEnvironment : createDockerWorkflowEnvironment);
|
|
35155
|
+
environment = await owned(async () => {
|
|
35156
|
+
const created = await factory2({
|
|
35157
|
+
workflow: structuredClone(workflow),
|
|
35158
|
+
initialState: structuredClone(initialState),
|
|
35159
|
+
signal: controller.signal
|
|
35160
|
+
});
|
|
35161
|
+
environment = created;
|
|
35162
|
+
if (finished && created && typeof created.close === "function") {
|
|
35163
|
+
const lateController = new AbortController;
|
|
35164
|
+
const lateTimer = setTimeout(() => lateController.abort(), Math.min(1000, cleanupMs));
|
|
35165
|
+
try {
|
|
35166
|
+
await owned(() => created.close(lateController.signal), lateController.signal);
|
|
35167
|
+
} catch {} finally {
|
|
35168
|
+
clearTimeout(lateTimer);
|
|
35169
|
+
}
|
|
35170
|
+
}
|
|
35171
|
+
return created;
|
|
35172
|
+
}, controller.signal);
|
|
35173
|
+
if (!environment || environment.type !== workflow.environment.type || typeof environment.execute !== "function" || typeof environment.snapshot !== "function" || typeof environment.close !== "function" || environment.capabilities?.network !== "denied" || environment.capabilities.commands !== "denied" || environment.capabilities.externalSideEffects !== "denied" || environment.capabilities.isolation !== (workflow.environment.type === "simulated" ? "memory" : "container"))
|
|
35174
|
+
throw new Stop("invalid", "invalid_environment");
|
|
35175
|
+
state = structuredClone(initialState);
|
|
35176
|
+
transcript = [{ role: "system", content: workflow.workflow.system_instructions }];
|
|
35177
|
+
const tools2 = workflow.tools.map((id) => {
|
|
35178
|
+
const descriptor2 = getWorkflowTool(id);
|
|
35179
|
+
if (!descriptor2)
|
|
35180
|
+
throw new Stop("invalid", "invalid_workflow");
|
|
35181
|
+
return {
|
|
35182
|
+
type: "function",
|
|
35183
|
+
function: {
|
|
35184
|
+
name: id,
|
|
35185
|
+
description: descriptor2.description,
|
|
35186
|
+
parameters: descriptor2.inputSchema
|
|
35187
|
+
}
|
|
35188
|
+
};
|
|
35189
|
+
});
|
|
35190
|
+
for (const turn of workflow.workflow.turns) {
|
|
35191
|
+
transcript.push(structuredClone(turn));
|
|
35192
|
+
while (true) {
|
|
35193
|
+
const answer = await model(transcript, tools2);
|
|
35194
|
+
transcript.push(answer.message);
|
|
35195
|
+
const calls = answer.message.tool_calls ?? [];
|
|
35196
|
+
if (!calls.length)
|
|
35197
|
+
break;
|
|
35198
|
+
for (const call of calls)
|
|
35199
|
+
await executeTool(call, tools2);
|
|
35200
|
+
}
|
|
35201
|
+
}
|
|
35202
|
+
}
|
|
35203
|
+
record.execution = "completed";
|
|
35204
|
+
record.reason = "finished";
|
|
35205
|
+
} catch (error2) {
|
|
35206
|
+
if (error2 instanceof WorkflowEnvironmentInitializationError) {
|
|
35207
|
+
const detail = error2.cleanup;
|
|
35208
|
+
const checked = safeBoundary(detail) ? cleanupSchema.extend({ pendingOperations: exports_external.number().int().nonnegative().safe() }).safeParse(detail) : null;
|
|
35209
|
+
record.cleanup = checked?.success ? checked.data : { status: "unresolved", artifacts: "unknown", pendingOperations: 1 };
|
|
35210
|
+
}
|
|
35211
|
+
const stop = error2 instanceof WorkflowEnvironmentInitializationError ? new Stop("failed", "environment_unavailable") : error2 instanceof Stop ? error2 : new Stop("failed", "target_error");
|
|
35212
|
+
record.execution = stop.execution;
|
|
35213
|
+
record.reason = stop.reason;
|
|
35214
|
+
} finally {
|
|
35215
|
+
clearTimeout(timer);
|
|
35216
|
+
controller.abort();
|
|
35217
|
+
const cleanup = new AbortController;
|
|
35218
|
+
const drainController = new AbortController;
|
|
35219
|
+
const drainBudget = Math.max(1, Math.floor((Number.isInteger(cleanupMs) && cleanupMs >= 1 && cleanupMs <= 1e4 ? cleanupMs : 1000) / 3));
|
|
35220
|
+
const drainTimer = setTimeout(() => drainController.abort(), drainBudget);
|
|
35221
|
+
let adapterPending = 0;
|
|
35222
|
+
try {
|
|
35223
|
+
const drain = target?.drain;
|
|
35224
|
+
if (typeof drain === "function") {
|
|
35225
|
+
const value = await owned(() => drain.call(target, { timeoutMs: drainBudget }), drainController.signal);
|
|
35226
|
+
if (value && Number.isSafeInteger(value.pendingOperations) && value.pendingOperations >= 0)
|
|
35227
|
+
adapterPending = value.pendingOperations;
|
|
35228
|
+
else
|
|
35229
|
+
adapterPending = 1;
|
|
35230
|
+
}
|
|
35231
|
+
const callbacks = [...pending];
|
|
35232
|
+
if (callbacks.length)
|
|
35233
|
+
await owned(() => Promise.allSettled(callbacks), drainController.signal);
|
|
35234
|
+
} catch {
|
|
35235
|
+
adapterPending = typeof target?.drain === "function" ? Math.max(1, adapterPending) : adapterPending;
|
|
35236
|
+
}
|
|
35237
|
+
clearTimeout(drainTimer);
|
|
35238
|
+
record.usage.inFlightUnknown = modelPending.size > 0 || adapterPending > 0;
|
|
35239
|
+
const pendingBeforeClose = pending.size;
|
|
35240
|
+
if (environment && typeof environment.close === "function") {
|
|
35241
|
+
const snapshotController = new AbortController;
|
|
35242
|
+
const snapshotTimer = setTimeout(() => snapshotController.abort(), drainBudget);
|
|
35243
|
+
if (!pendingBeforeClose && typeof environment.snapshot === "function" && !cleanup.signal.aborted) {
|
|
35244
|
+
try {
|
|
35245
|
+
const snapshot = await owned(() => environment.snapshot(snapshotController.signal), snapshotController.signal);
|
|
35246
|
+
if (isWorkflowState(snapshot))
|
|
35247
|
+
state = structuredClone(snapshot);
|
|
35248
|
+
else
|
|
35249
|
+
state = null;
|
|
35250
|
+
} catch {
|
|
35251
|
+
state = null;
|
|
35252
|
+
}
|
|
35253
|
+
} else
|
|
35254
|
+
state = null;
|
|
35255
|
+
clearTimeout(snapshotTimer);
|
|
35256
|
+
const closeTimer = setTimeout(() => cleanup.abort(), drainBudget);
|
|
35257
|
+
try {
|
|
35258
|
+
const result = await owned(() => environment.close(cleanup.signal), cleanup.signal);
|
|
35259
|
+
const checked = safeBoundary(result) ? cleanupSchema.safeParse(result) : null;
|
|
35260
|
+
if (checked?.success)
|
|
35261
|
+
record.cleanup = { ...checked.data, pendingOperations: 0 };
|
|
35262
|
+
else
|
|
35263
|
+
record.cleanup = { status: "unresolved", artifacts: "unknown", pendingOperations: 0 };
|
|
35264
|
+
} catch {
|
|
35265
|
+
record.cleanup = { status: "unresolved", artifacts: "unknown", pendingOperations: 0 };
|
|
35266
|
+
} finally {
|
|
35267
|
+
clearTimeout(closeTimer);
|
|
35268
|
+
}
|
|
35269
|
+
}
|
|
35270
|
+
record.cleanup.pendingOperations = Math.max(record.cleanup.pendingOperations, pending.size, adapterPending);
|
|
35271
|
+
if (record.cleanup.pendingOperations || pendingBeforeClose || cleanup.signal.aborted) {
|
|
35272
|
+
record.cleanup.status = "unresolved";
|
|
35273
|
+
record.cleanup.artifacts = "unknown";
|
|
35274
|
+
}
|
|
35275
|
+
if (record.cleanup.status === "unresolved")
|
|
35276
|
+
state = null;
|
|
35277
|
+
options.signal?.removeEventListener("abort", abort);
|
|
35278
|
+
record.usage.missingRequests = record.budgets.modelRequests - measuredRequests;
|
|
35279
|
+
const tokenLimit = workflow?.environment.policy.budgets.max_tokens;
|
|
35280
|
+
record.budgets.tokenOvershoot = tokenLimit === undefined ? 0 : Math.max(0, record.usage.reported.total - tokenLimit);
|
|
35281
|
+
record.usage.status = measuredRequests ? record.usage.missingRequests || record.usage.inFlightUnknown ? "partial" : "reported" : "unavailable";
|
|
35282
|
+
if (state) {
|
|
35283
|
+
const files = isWorkflowState(state.files) ? Object.entries(state.files).filter((entry) => typeof entry[1] === "string").sort(([a2], [b2]) => a2.localeCompare(b2)) : [];
|
|
35284
|
+
record.artifacts = {
|
|
35285
|
+
state: "available",
|
|
35286
|
+
stateSha256: hash(JSON.stringify(state)),
|
|
35287
|
+
files: files.slice(0, 100).map(([path, content]) => ({
|
|
35288
|
+
pathSha256: hash(path),
|
|
35289
|
+
contentSha256: hash(content),
|
|
35290
|
+
bytes: Buffer.byteLength(content)
|
|
35291
|
+
})),
|
|
35292
|
+
omittedFiles: Math.max(0, files.length - 100)
|
|
35293
|
+
};
|
|
35294
|
+
}
|
|
35295
|
+
record.budgets.elapsedMs = Math.max(0, Date.now() - started);
|
|
35296
|
+
emit({ type: "finished", status: record.execution === "completed" ? "completed" : "failed" });
|
|
35297
|
+
finished = true;
|
|
35298
|
+
sessionState = "completed";
|
|
35299
|
+
for (const wake of listeners)
|
|
35300
|
+
wake();
|
|
35301
|
+
}
|
|
35302
|
+
return {
|
|
35303
|
+
record: structuredClone(record),
|
|
35304
|
+
state: state ? structuredClone(state) : null,
|
|
35305
|
+
transcript: structuredClone(transcript)
|
|
35306
|
+
};
|
|
35307
|
+
}
|
|
35308
|
+
return {
|
|
35309
|
+
get state() {
|
|
35310
|
+
return sessionState;
|
|
35311
|
+
},
|
|
35312
|
+
run() {
|
|
35313
|
+
if (!promise) {
|
|
35314
|
+
sessionState = controller.signal.aborted ? "cancelling" : "running";
|
|
35315
|
+
promise = Promise.resolve().then(run);
|
|
35316
|
+
}
|
|
35317
|
+
return promise;
|
|
35318
|
+
},
|
|
35319
|
+
cancel: abort,
|
|
35320
|
+
async* events() {
|
|
35321
|
+
let index = 0;
|
|
35322
|
+
while (true) {
|
|
35323
|
+
while (index < retained.length)
|
|
35324
|
+
yield structuredClone(retained[index++]);
|
|
35325
|
+
if (finished)
|
|
35326
|
+
return;
|
|
35327
|
+
await new Promise((resolve4) => {
|
|
35328
|
+
const wake = () => {
|
|
35329
|
+
listeners.delete(wake);
|
|
35330
|
+
resolve4();
|
|
35331
|
+
};
|
|
35332
|
+
listeners.add(wake);
|
|
35333
|
+
});
|
|
35334
|
+
}
|
|
35335
|
+
}
|
|
35336
|
+
};
|
|
35337
|
+
}
|
|
35338
|
+
function runAgentWorkflow(options) {
|
|
35339
|
+
return createAgentWorkflowSession(options).run();
|
|
35340
|
+
}
|
|
35341
|
+
// src/validator/validator.ts
|
|
35342
|
+
var import_yaml4 = __toESM(require_dist(), 1);
|
|
34011
35343
|
import { readFileSync } from "node:fs";
|
|
34012
35344
|
class ScenarioValidator {
|
|
34013
35345
|
_options;
|
|
@@ -34035,12 +35367,12 @@ class ScenarioValidator {
|
|
|
34035
35367
|
}
|
|
34036
35368
|
let parsed;
|
|
34037
35369
|
try {
|
|
34038
|
-
parsed =
|
|
35370
|
+
parsed = import_yaml4.default.parse(content, {
|
|
34039
35371
|
prettyErrors: true,
|
|
34040
35372
|
strict: true
|
|
34041
35373
|
});
|
|
34042
35374
|
} catch (err) {
|
|
34043
|
-
if (err instanceof
|
|
35375
|
+
if (err instanceof import_yaml4.default.YAMLError) {
|
|
34044
35376
|
const linePos = err.linePos?.[0];
|
|
34045
35377
|
errors3.push({
|
|
34046
35378
|
line: linePos?.line || 1,
|
|
@@ -34253,14 +35585,19 @@ class ScenarioValidator {
|
|
|
34253
35585
|
}
|
|
34254
35586
|
export {
|
|
34255
35587
|
wrapError,
|
|
35588
|
+
workflowToolPermitted,
|
|
35589
|
+
workflowPathAllowed,
|
|
34256
35590
|
validateToolArguments,
|
|
34257
35591
|
validateScenario,
|
|
34258
35592
|
validateAgentWorkflow,
|
|
35593
|
+
validWorkflowTranscript,
|
|
34259
35594
|
substituteVariables,
|
|
34260
35595
|
substituteString,
|
|
34261
35596
|
scoreAgentOutcome,
|
|
34262
35597
|
runScenarios,
|
|
34263
35598
|
runScenario,
|
|
35599
|
+
runAgentWorkflow,
|
|
35600
|
+
resolveWorkflowInitialState,
|
|
34264
35601
|
resolveScenarioPaths,
|
|
34265
35602
|
resolvePatterns,
|
|
34266
35603
|
registerEvaluator,
|
|
@@ -34279,6 +35616,7 @@ export {
|
|
|
34279
35616
|
listKnownModels,
|
|
34280
35617
|
listEvaluators,
|
|
34281
35618
|
listAdapters,
|
|
35619
|
+
isWorkflowState,
|
|
34282
35620
|
isWorkflowRelativePath,
|
|
34283
35621
|
isWorkflowJson,
|
|
34284
35622
|
isStressManifest,
|
|
@@ -34302,19 +35640,26 @@ export {
|
|
|
34302
35640
|
createWorkloadIdentity,
|
|
34303
35641
|
createStorageFromEnv,
|
|
34304
35642
|
createStorageAdapter,
|
|
35643
|
+
createSimulatedWorkflowEnvironment,
|
|
34305
35644
|
createRunManifest,
|
|
34306
35645
|
createRedactionOptions,
|
|
34307
35646
|
createNoOpRedactor,
|
|
34308
35647
|
createModelClientTarget,
|
|
34309
35648
|
createExecutionProvenance,
|
|
35649
|
+
createDockerWorkflowEnvironmentFactory,
|
|
35650
|
+
createDockerWorkflowEnvironment,
|
|
34310
35651
|
createDefaultRedactor,
|
|
35652
|
+
createAgentWorkflowSession,
|
|
34311
35653
|
createAdapter,
|
|
34312
35654
|
assessComparisonEligibility,
|
|
34313
35655
|
assertRunManifestIntegrity,
|
|
35656
|
+
agentTurnRequestSchema,
|
|
34314
35657
|
adapterRegistry,
|
|
34315
35658
|
actionBudgetExceeded,
|
|
34316
35659
|
WorkflowPolicySchema,
|
|
35660
|
+
WorkflowEnvironmentInitializationError,
|
|
34317
35661
|
WORKFLOW_TOOL_IDS,
|
|
35662
|
+
WORKFLOW_SANDBOX_IMAGE,
|
|
34318
35663
|
VariablesSchema,
|
|
34319
35664
|
ToolTraceEvaluator,
|
|
34320
35665
|
TestCaseSchema,
|