@emseepea/testing 0.23.1 → 0.23.3
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 +31 -4
- package/package.json +1 -1
- package/semantic/provider.mjs +158 -15
- package/semantic/test.mjs +24 -4
package/README.md
CHANGED
|
@@ -109,12 +109,39 @@ EMSEEPEA_CODEX_MODEL=gpt-5.5 emseepea-test --provider codex-local eval
|
|
|
109
109
|
|
|
110
110
|
`codex-local` uses the existing Codex CLI sign-in without copying it.
|
|
111
111
|
`codex-ci` requires `OPENAI_API_KEY` from the CI secret store. Both providers
|
|
112
|
-
run `codex exec --json`, resume the actual
|
|
113
|
-
user configuration, and pre-approve only
|
|
114
|
-
server.
|
|
112
|
+
support Codex CLI 0.162.0 or later, run `codex exec --json`, resume the actual
|
|
113
|
+
Codex session for follow-ups, ignore user configuration, and pre-approve only
|
|
114
|
+
the discovered tools from the test server.
|
|
115
115
|
|
|
116
116
|
An isolated command policy denies shell launches and spawned shell environments
|
|
117
|
-
inherit no provider credentials.
|
|
117
|
+
inherit no provider credentials. The runner also disables Codex account apps,
|
|
118
|
+
plugins, remote plugins, multi-agent execution, web search, shell tools, and
|
|
119
|
+
unified execution for each one-shot and resumed native invocation.
|
|
120
|
+
|
|
121
|
+
The invocation configuration sets the default account-app state to disabled,
|
|
122
|
+
omits account-app instructions, and disables Codex app, plugin, and tool
|
|
123
|
+
suggestion features.
|
|
124
|
+
|
|
125
|
+
Native-client events are authoritative for the tools that were actually used:
|
|
126
|
+
a turn is accepted only when every application MCP call belongs to the
|
|
127
|
+
configured test server and its expected tools.
|
|
128
|
+
|
|
129
|
+
Codex may make its two built-in resource-discovery calls through the CLI's
|
|
130
|
+
`codex` server. The runner accepts them only when their native results prove the
|
|
131
|
+
resource and template inventories are empty, and excludes them from application
|
|
132
|
+
assertions and raw evidence.
|
|
133
|
+
|
|
134
|
+
Any non-empty, unrecognised, account-app, plugin, or other foreign call is
|
|
135
|
+
rejected without storing raw account metadata.
|
|
136
|
+
|
|
137
|
+
Failed Codex runs retain only a fixed failure category: authentication,
|
|
138
|
+
configuration, host storage, permission or sandbox, provider, transport, or
|
|
139
|
+
unknown. The runner inspects at most 32 KiB of standard error to derive that
|
|
140
|
+
category and does not store the standard error.
|
|
141
|
+
|
|
142
|
+
Rejected native items retain only capped counts from a fixed item-type list.
|
|
143
|
+
Commands, arguments, results, paths, server and tool names, and unknown
|
|
144
|
+
item-type strings are excluded.
|
|
118
145
|
|
|
119
146
|
Codex CLI evidence proves that CLI journey. It does not prove native ChatGPT
|
|
120
147
|
connection, attachment, retrieval, or MCP App rendering behavior.
|
package/package.json
CHANGED
package/semantic/provider.mjs
CHANGED
|
@@ -6,6 +6,11 @@ import { scriptedElicitations } from "./elicitation.mjs";
|
|
|
6
6
|
|
|
7
7
|
const claudeModel = "claude-sonnet-4-6";
|
|
8
8
|
const mcpServerName = "emseepea_eval";
|
|
9
|
+
const minimumCodexCliVersion = "0.162.0";
|
|
10
|
+
const codexEmptyDiscoveryTools = new Map([
|
|
11
|
+
["list_mcp_resources", "resources"],
|
|
12
|
+
["list_mcp_resource_templates", "resourceTemplates"],
|
|
13
|
+
]);
|
|
9
14
|
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10
15
|
const providerErrorMessages = new Map([
|
|
11
16
|
["error_during_execution", "Model command failed during execution"],
|
|
@@ -13,6 +18,17 @@ const providerErrorMessages = new Map([
|
|
|
13
18
|
["error_max_turns", "Model command exceeded its turn limit"],
|
|
14
19
|
]);
|
|
15
20
|
const authenticationFailure = /not logged in|failed to authenticate|oauth session expired/i;
|
|
21
|
+
const codexFailureCategories = new Set([
|
|
22
|
+
"authentication",
|
|
23
|
+
"configuration",
|
|
24
|
+
"host-storage",
|
|
25
|
+
"permission-or-sandbox",
|
|
26
|
+
"provider",
|
|
27
|
+
"transport",
|
|
28
|
+
"unknown",
|
|
29
|
+
]);
|
|
30
|
+
const stderrLimit = 32 * 1024;
|
|
31
|
+
const forbiddenItemLimit = 64;
|
|
16
32
|
const hasAuthenticationFailure = (events) => events.some((event) => (
|
|
17
33
|
(Array.isArray(event.message?.content)
|
|
18
34
|
&& event.message.content.some(({ type, text }) => type === "text" && /not logged in/i.test(text ?? "")))
|
|
@@ -28,6 +44,9 @@ export async function modelVersion(provider = "claude-local") {
|
|
|
28
44
|
const result = await runProcess(command, ["--version"], { env: modelEnvironment({}) });
|
|
29
45
|
const version = result.stdout.trim().match(/(?:codex-cli\s+)?(\d+\.\d+\.\d+)\b/)?.[1];
|
|
30
46
|
if (result.code !== 0 || !version) throw new Error(`Could not verify ${codex ? "Codex" : "Claude"} CLI version`);
|
|
47
|
+
if (codex && compareVersions(version, minimumCodexCliVersion) < 0) {
|
|
48
|
+
throw new Error(`Codex CLI ${version} is unsupported; use ${minimumCodexCliVersion} or later`);
|
|
49
|
+
}
|
|
31
50
|
return version;
|
|
32
51
|
}
|
|
33
52
|
|
|
@@ -42,11 +61,16 @@ export function providerSettings(provider) {
|
|
|
42
61
|
return provider.startsWith("codex-")
|
|
43
62
|
? {
|
|
44
63
|
approvalPolicy: "never",
|
|
64
|
+
appInstructions: false,
|
|
65
|
+
apps: false,
|
|
45
66
|
loginShell: false,
|
|
46
67
|
multiAgent: false,
|
|
68
|
+
plugins: false,
|
|
69
|
+
remotePlugin: false,
|
|
47
70
|
sandbox: "read-only",
|
|
48
71
|
shell: "forbidden",
|
|
49
72
|
shellEnvironment: "empty",
|
|
73
|
+
toolSuggest: false,
|
|
50
74
|
userConfig: "ignored",
|
|
51
75
|
webSearch: false,
|
|
52
76
|
}
|
|
@@ -388,14 +412,17 @@ export async function runModel(provider, prompt, directory, signal) {
|
|
|
388
412
|
if (execution.outputLimitExceeded) throw new Error("Model command output exceeded its limit");
|
|
389
413
|
if (execution.errorCode === "ABORT_ERR") throw new Error("Model command was cancelled");
|
|
390
414
|
if (execution.errorCode) throw new Error("Model command could not start");
|
|
391
|
-
if (execution.code !== 0 && !execution.stdout)
|
|
415
|
+
if (execution.code !== 0 && !execution.stdout) {
|
|
416
|
+
throw codexProcessError(execution.code, execution.providerFailureCategory);
|
|
417
|
+
}
|
|
392
418
|
return provider.startsWith("codex-")
|
|
393
419
|
? parseCodexEvents(execution.stdout, [], undefined, execution.code,
|
|
394
|
-
providerModel(provider), invocation.providerSecrets)
|
|
420
|
+
providerModel(provider), invocation.providerSecrets, execution.providerFailureCategory)
|
|
395
421
|
: parseClaudeEvents(execution.stdout, execution.code);
|
|
396
422
|
}
|
|
397
423
|
|
|
398
|
-
export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, processExitCode = 0,
|
|
424
|
+
export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, processExitCode = 0,
|
|
425
|
+
selectedModel = "test-model", secrets = [], providerFailureCategory) {
|
|
399
426
|
let events;
|
|
400
427
|
try {
|
|
401
428
|
events = typeof stdout === "string"
|
|
@@ -412,11 +439,13 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
412
439
|
const authentication = /(?:not logged in|authentication|unauthorized|invalid api key|oauth)/i;
|
|
413
440
|
const errorText = events.flatMap((event) => [event.message, event.error?.message, event.item?.message])
|
|
414
441
|
.filter((value) => typeof value === "string").join("\n");
|
|
415
|
-
if (authentication.test(errorText))
|
|
442
|
+
if (authentication.test(errorText)) {
|
|
443
|
+
throw codexEvidenceError("Model command is not signed in", "authentication");
|
|
444
|
+
}
|
|
416
445
|
const failedTool = events.some(({ item }) => item?.type === "mcp_tool_call"
|
|
417
446
|
&& (item.status === "failed" || item.result?.isError === true || item.result?.is_error === true));
|
|
418
447
|
if (failedTool) throw new Error("Model command reported a failed MCP tool call");
|
|
419
|
-
if (processExitCode !== 0) throw
|
|
448
|
+
if (processExitCode !== 0) throw codexProcessError(processExitCode, providerFailureCategory);
|
|
420
449
|
if (events.some(({ type }) => type === "turn.failed" || type === "error")) {
|
|
421
450
|
throw new Error("Model command reported an error");
|
|
422
451
|
}
|
|
@@ -435,12 +464,18 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
435
464
|
const advertised = new Set(advertisedTools.map(({ name }) => name));
|
|
436
465
|
const completed = events.filter(({ type }) => type === "item.completed").map(({ item }) => item);
|
|
437
466
|
const forbidden = completed.filter((item) => !["agent_message", "reasoning", "mcp_tool_call"].includes(item?.type));
|
|
438
|
-
if (forbidden.length) throw
|
|
467
|
+
if (forbidden.length) throw forbiddenItemError(forbidden);
|
|
439
468
|
const toolItems = completed.filter(({ type }) => type === "mcp_tool_call");
|
|
440
|
-
const
|
|
469
|
+
const discoveryItems = toolItems.filter(({ server, tool }) =>
|
|
470
|
+
server === "codex" && codexEmptyDiscoveryTools.has(tool));
|
|
471
|
+
if (discoveryItems.some((item) => !emptyCodexDiscovery(item))) {
|
|
472
|
+
throw forbiddenItemError(discoveryItems.filter((item) => !emptyCodexDiscovery(item)));
|
|
473
|
+
}
|
|
474
|
+
const applicationToolItems = toolItems.filter((item) => !discoveryItems.includes(item));
|
|
475
|
+
const calls = applicationToolItems.map((item) => {
|
|
441
476
|
if (item.server !== mcpServerName || !advertised.has(item.tool)
|
|
442
477
|
|| !item.arguments || typeof item.arguments !== "object" || Array.isArray(item.arguments)) {
|
|
443
|
-
throw
|
|
478
|
+
throw forbiddenItemError([item]);
|
|
444
479
|
}
|
|
445
480
|
if (!item.result || typeof item.result !== "object" || item.status === "failed"
|
|
446
481
|
|| item.result.isError === true || item.result.is_error === true) {
|
|
@@ -451,7 +486,7 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
451
486
|
const messages = completed.filter(({ type }) => type === "agent_message");
|
|
452
487
|
const answer = messages.at(-1)?.text;
|
|
453
488
|
if (typeof answer !== "string") throw new Error("Model command returned no answer");
|
|
454
|
-
const toolResults =
|
|
489
|
+
const toolResults = applicationToolItems.map(({ result }) => ({ content: result.content ?? result, isError: false }));
|
|
455
490
|
return {
|
|
456
491
|
answer,
|
|
457
492
|
calls,
|
|
@@ -471,6 +506,25 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
471
506
|
};
|
|
472
507
|
}
|
|
473
508
|
|
|
509
|
+
function emptyCodexDiscovery(item) {
|
|
510
|
+
if (item.status !== "completed" || !item.arguments || typeof item.arguments !== "object"
|
|
511
|
+
|| Array.isArray(item.arguments) || Object.keys(item.arguments).length !== 0
|
|
512
|
+
|| !item.result || typeof item.result !== "object" || Array.isArray(item.result)
|
|
513
|
+
|| item.result.structured_content !== null || Object.keys(item.result).length !== 2
|
|
514
|
+
|| !Array.isArray(item.result.content) || item.result.content.length !== 1) return false;
|
|
515
|
+
const [content] = item.result.content;
|
|
516
|
+
if (!content || content.type !== "text" || typeof content.text !== "string"
|
|
517
|
+
|| Object.keys(content).length !== 2) return false;
|
|
518
|
+
try {
|
|
519
|
+
const parsed = JSON.parse(content.text);
|
|
520
|
+
const key = codexEmptyDiscoveryTools.get(item.tool);
|
|
521
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
522
|
+
&& Object.keys(parsed).length === 1 && Array.isArray(parsed[key]) && parsed[key].length === 0;
|
|
523
|
+
} catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
474
528
|
function startCodexConversation(provider, directory, url, tools, authToken, context, signal, serverSecrets) {
|
|
475
529
|
let threadId;
|
|
476
530
|
let closed = false;
|
|
@@ -492,7 +546,8 @@ function startCodexConversation(provider, directory, url, tools, authToken, cont
|
|
|
492
546
|
if (execution.errorCode === "ABORT_ERR") throw new Error("Model command was cancelled");
|
|
493
547
|
if (execution.errorCode) throw new Error("Model conversation could not start");
|
|
494
548
|
const turn = parseCodexEvents(execution.stdout, tools, threadId, execution.code,
|
|
495
|
-
providerModel(provider), [...invocation.providerSecrets, authToken, ...serverSecrets]
|
|
549
|
+
providerModel(provider), [...invocation.providerSecrets, authToken, ...serverSecrets],
|
|
550
|
+
execution.providerFailureCategory);
|
|
496
551
|
threadId = turn.threadId;
|
|
497
552
|
return { ...turn, elicitations: [] };
|
|
498
553
|
},
|
|
@@ -528,8 +583,14 @@ function codexInvocation(provider, prompt, directory, conversation) {
|
|
|
528
583
|
"-c", 'web_search="disabled"',
|
|
529
584
|
"-c", 'shell_environment_policy.inherit="none"',
|
|
530
585
|
"-c", "allow_login_shell=false",
|
|
586
|
+
"-c", "apps._default.enabled=false",
|
|
587
|
+
"-c", "include_apps_instructions=false",
|
|
588
|
+
"-c", "features.apps=false",
|
|
531
589
|
"-c", "features.multi_agent=false",
|
|
590
|
+
"-c", "features.plugins=false",
|
|
591
|
+
"-c", "features.remote_plugin=false",
|
|
532
592
|
"-c", "features.shell_tool=false",
|
|
593
|
+
"-c", "features.tool_suggest=false",
|
|
533
594
|
"-c", "features.unified_exec=false",
|
|
534
595
|
];
|
|
535
596
|
if (conversation?.context) args.push("-c", `developer_instructions=${JSON.stringify(conversation.context)}`);
|
|
@@ -559,8 +620,10 @@ function runProcess(command, args, options) {
|
|
|
559
620
|
return new Promise((resolve) => {
|
|
560
621
|
const child = spawn(command, args, { stdio: ["ignore", "pipe", "pipe"], ...options });
|
|
561
622
|
let stdout = "";
|
|
623
|
+
let stderr = Buffer.alloc(0);
|
|
562
624
|
let timedOut = false;
|
|
563
625
|
let outputLimitExceeded = false;
|
|
626
|
+
let settled = false;
|
|
564
627
|
const timer = setTimeout(() => { timedOut = true; child.kill("SIGKILL"); }, 180_000);
|
|
565
628
|
timer.unref();
|
|
566
629
|
child.stdout.on("data", (chunk) => {
|
|
@@ -570,18 +633,98 @@ function runProcess(command, args, options) {
|
|
|
570
633
|
child.kill("SIGKILL");
|
|
571
634
|
}
|
|
572
635
|
});
|
|
573
|
-
child.stderr.
|
|
574
|
-
|
|
636
|
+
child.stderr.on("data", (chunk) => {
|
|
637
|
+
if (stderr.length >= stderrLimit) return;
|
|
638
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
639
|
+
stderr = Buffer.concat([stderr, bytes.subarray(0, stderrLimit - stderr.length)]);
|
|
640
|
+
});
|
|
641
|
+
const finish = (result) => {
|
|
642
|
+
if (settled) return;
|
|
643
|
+
settled = true;
|
|
575
644
|
clearTimeout(timer);
|
|
576
|
-
|
|
645
|
+
const providerFailureCategory = classifyCodexFailure(stderr.toString("utf8"));
|
|
646
|
+
stderr = Buffer.alloc(0);
|
|
647
|
+
resolve({ ...result, providerFailureCategory });
|
|
648
|
+
};
|
|
649
|
+
child.once("error", (error) => {
|
|
650
|
+
finish({ code: 1, errorCode: error.code, outputLimitExceeded, stdout, timedOut });
|
|
577
651
|
});
|
|
578
652
|
child.once("close", (code) => {
|
|
579
|
-
|
|
580
|
-
resolve({ code: code ?? 1, outputLimitExceeded, stdout, timedOut });
|
|
653
|
+
finish({ code: code ?? 1, outputLimitExceeded, stdout, timedOut });
|
|
581
654
|
});
|
|
582
655
|
});
|
|
583
656
|
}
|
|
584
657
|
|
|
658
|
+
function classifyCodexFailure(stderr) {
|
|
659
|
+
if (/no space left|disk (?:is )?full|storage (?:capacity|quota)|\benospc\b/i.test(stderr)) {
|
|
660
|
+
return "host-storage";
|
|
661
|
+
}
|
|
662
|
+
if (/not logged in|authenticat|unauthorized|invalid api key|oauth/i.test(stderr)) {
|
|
663
|
+
return "authentication";
|
|
664
|
+
}
|
|
665
|
+
if (/permission denied|operation not permitted|sandbox|\beacces\b|\beperm\b/i.test(stderr)) {
|
|
666
|
+
return "permission-or-sandbox";
|
|
667
|
+
}
|
|
668
|
+
if (/configuration|config(?:uration)? error|unknown (?:feature|field|key)|invalid (?:config|value)/i.test(stderr)) {
|
|
669
|
+
return "configuration";
|
|
670
|
+
}
|
|
671
|
+
if (/connection (?:refused|reset)|network|transport|timed? out|\btls\b|certificate|\bdns\b|\beconn/i.test(stderr)) {
|
|
672
|
+
return "transport";
|
|
673
|
+
}
|
|
674
|
+
if (/provider (?:error|failure|unavailable)|model (?:is )?(?:unavailable|overloaded)|rate limit|service unavailable/i.test(stderr)) {
|
|
675
|
+
return "provider";
|
|
676
|
+
}
|
|
677
|
+
return "unknown";
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function compareVersions(left, right) {
|
|
681
|
+
const a = left.split(".").map(Number);
|
|
682
|
+
const b = right.split(".").map(Number);
|
|
683
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
684
|
+
const difference = (a[index] ?? 0) - (b[index] ?? 0);
|
|
685
|
+
if (difference !== 0) return difference;
|
|
686
|
+
}
|
|
687
|
+
return 0;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function codexEvidenceError(message, category) {
|
|
691
|
+
return Object.assign(new Error(message), {
|
|
692
|
+
providerFailureCategory: codexFailureCategories.has(category) ? category : "unknown",
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
function codexProcessError(code, category) {
|
|
697
|
+
return codexEvidenceError(`Model command exited ${code}`, category);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function forbiddenItemError(items) {
|
|
701
|
+
const counts = {
|
|
702
|
+
commandExecution: 0,
|
|
703
|
+
computerUse: 0,
|
|
704
|
+
fileChange: 0,
|
|
705
|
+
mcpToolCall: 0,
|
|
706
|
+
other: 0,
|
|
707
|
+
webSearch: 0,
|
|
708
|
+
};
|
|
709
|
+
for (const item of items.slice(0, forbiddenItemLimit)) {
|
|
710
|
+
const key = new Map([
|
|
711
|
+
["command_execution", "commandExecution"],
|
|
712
|
+
["computer_tool", "computerUse"],
|
|
713
|
+
["file_change", "fileChange"],
|
|
714
|
+
["mcp_tool_call", "mcpToolCall"],
|
|
715
|
+
["web_search", "webSearch"],
|
|
716
|
+
]).get(item?.type) ?? "other";
|
|
717
|
+
counts[key] += 1;
|
|
718
|
+
}
|
|
719
|
+
return Object.assign(new Error("Model command used a forbidden tool"), {
|
|
720
|
+
forbiddenItemEvidence: Object.freeze({
|
|
721
|
+
counts: Object.freeze(counts),
|
|
722
|
+
inspectedCount: Math.min(items.length, forbiddenItemLimit),
|
|
723
|
+
truncated: items.length > forbiddenItemLimit,
|
|
724
|
+
}),
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
585
728
|
function modelEnvironment(extra) {
|
|
586
729
|
return Object.fromEntries(Object.entries({
|
|
587
730
|
CI: "true",
|
package/semantic/test.mjs
CHANGED
|
@@ -178,7 +178,7 @@ export async function createConversation(testContext, options) {
|
|
|
178
178
|
return;
|
|
179
179
|
}
|
|
180
180
|
const trial = state.trials[index];
|
|
181
|
-
trial.record
|
|
181
|
+
recordModelFailure(trial.record, result.reason);
|
|
182
182
|
if (Array.isArray(result.reason?.elicitations)) trial.record.elicitations = result.reason.elicitations;
|
|
183
183
|
if (Array.isArray(result.reason?.attemptedToolCalls)) {
|
|
184
184
|
trial.record.attemptedToolCalls = result.reason.attemptedToolCalls;
|
|
@@ -508,9 +508,11 @@ async function judgeTrialMeaning(trial, trialIndex, expected) {
|
|
|
508
508
|
record.verdict = verdict;
|
|
509
509
|
outcomes.push({ failed: !verdict.pass, record });
|
|
510
510
|
} catch (error) {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
511
|
+
if (error instanceof SyntaxError || error.message === "Judge returned an invalid verdict") {
|
|
512
|
+
record.error = "invalid judge verdict";
|
|
513
|
+
} else {
|
|
514
|
+
recordModelFailure(record, error);
|
|
515
|
+
}
|
|
514
516
|
outcomes.push({ failed: true, record });
|
|
515
517
|
}
|
|
516
518
|
}
|
|
@@ -570,6 +572,24 @@ function safeModelFailure(error) {
|
|
|
570
572
|
return "model invocation failed";
|
|
571
573
|
}
|
|
572
574
|
|
|
575
|
+
function recordModelFailure(record, error) {
|
|
576
|
+
record.error = safeModelFailure(error);
|
|
577
|
+
const category = error?.providerFailureCategory;
|
|
578
|
+
if ([
|
|
579
|
+
"authentication",
|
|
580
|
+
"configuration",
|
|
581
|
+
"host-storage",
|
|
582
|
+
"permission-or-sandbox",
|
|
583
|
+
"provider",
|
|
584
|
+
"transport",
|
|
585
|
+
"unknown",
|
|
586
|
+
].includes(category)) record.providerFailureCategory = category;
|
|
587
|
+
const evidence = error?.forbiddenItemEvidence;
|
|
588
|
+
if (evidence && typeof evidence === "object") {
|
|
589
|
+
record.forbiddenItemEvidence = structuredClone(evidence);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
573
593
|
async function isolatedModel(provider, prompt, prefix, signal) {
|
|
574
594
|
const directory = await mkdtemp(join(tmpdir(), prefix));
|
|
575
595
|
try {
|