@emseepea/testing 0.26.15 → 0.26.16
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/package.json +2 -2
- package/semantic/provider.mjs +62 -18
- package/semantic/test.mjs +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emseepea/testing",
|
|
3
|
-
"version": "0.26.
|
|
3
|
+
"version": "0.26.16",
|
|
4
4
|
"description": "MCP integration and semantic testing helpers",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"test:llm:built": "emseepea-test eval"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@emseepea/server": "0.26.
|
|
60
|
+
"@emseepea/server": "0.26.1",
|
|
61
61
|
"@modelcontextprotocol/client": "2.3.1",
|
|
62
62
|
"@modelcontextprotocol/server": "2.3.1",
|
|
63
63
|
"@modelcontextprotocol/core": "2.3.1"
|
package/semantic/provider.mjs
CHANGED
|
@@ -25,15 +25,28 @@ const providerErrorMessages = new Map([
|
|
|
25
25
|
["error_max_turns", "Model command exceeded its turn limit"],
|
|
26
26
|
]);
|
|
27
27
|
const authenticationFailure = /not logged in|failed to authenticate|oauth session expired/i;
|
|
28
|
-
const
|
|
28
|
+
const providerFailureCategories = new Set([
|
|
29
29
|
"authentication",
|
|
30
|
+
"billing",
|
|
30
31
|
"configuration",
|
|
31
32
|
"host-storage",
|
|
32
33
|
"permission-or-sandbox",
|
|
33
34
|
"provider",
|
|
35
|
+
"rate-limit",
|
|
34
36
|
"transport",
|
|
35
37
|
"unknown",
|
|
36
38
|
]);
|
|
39
|
+
const claudeAssistantFailureCategories = new Map([
|
|
40
|
+
["authentication_failed", "authentication"],
|
|
41
|
+
["oauth_org_not_allowed", "authentication"],
|
|
42
|
+
["account_on_hold", "provider"],
|
|
43
|
+
["billing_error", "billing"],
|
|
44
|
+
["rate_limit", "rate-limit"],
|
|
45
|
+
["overloaded", "provider"],
|
|
46
|
+
["invalid_request", "configuration"],
|
|
47
|
+
["model_not_found", "provider"],
|
|
48
|
+
["server_error", "provider"],
|
|
49
|
+
]);
|
|
37
50
|
const stderrLimit = 32 * 1024;
|
|
38
51
|
const forbiddenItemLimit = 64;
|
|
39
52
|
const hasAuthenticationFailure = (events) => events.some((event) => (
|
|
@@ -81,7 +94,7 @@ export function providerSettings(provider) {
|
|
|
81
94
|
: { effort: "low", maxTurns: null, judgeMaxTurns: 4, permissionMode: "dontAsk", userSettings: false };
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
export function parseClaudeEvents(stdout, processExitCode = 0) {
|
|
97
|
+
export function parseClaudeEvents(stdout, processExitCode = 0, failureCategory) {
|
|
85
98
|
let events;
|
|
86
99
|
try {
|
|
87
100
|
events = stdout.split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
|
|
@@ -93,11 +106,15 @@ export function parseClaudeEvents(stdout, processExitCode = 0) {
|
|
|
93
106
|
const toolUses = events.flatMap(({ message }) => (
|
|
94
107
|
Array.isArray(message?.content) ? message.content.filter(({ type }) => type === "tool_use") : []
|
|
95
108
|
));
|
|
96
|
-
if (hasAuthenticationFailure(events))
|
|
97
|
-
|
|
109
|
+
if (hasAuthenticationFailure(events)) {
|
|
110
|
+
throw providerEvidenceError("Model command is not signed in", "authentication");
|
|
111
|
+
}
|
|
112
|
+
if (processExitCode !== 0) {
|
|
113
|
+
throw providerProcessError(processExitCode, claudeFailureCategory(events, result, failureCategory));
|
|
114
|
+
}
|
|
98
115
|
if (!result) throw new Error("Model command omitted its result event");
|
|
99
116
|
if (result.is_error) {
|
|
100
|
-
throw
|
|
117
|
+
throw claudeResultError(events, result);
|
|
101
118
|
}
|
|
102
119
|
if (typeof answer !== "string") throw new Error("Model command returned a non-text answer");
|
|
103
120
|
if (toolUses.length > 0) {
|
|
@@ -172,10 +189,12 @@ export function parseNativeClaudeEvents(stdout, advertisedTools, requireInit = f
|
|
|
172
189
|
responseSha256: hash(response.content),
|
|
173
190
|
};
|
|
174
191
|
});
|
|
175
|
-
if (hasAuthenticationFailure(events))
|
|
192
|
+
if (hasAuthenticationFailure(events)) {
|
|
193
|
+
throw providerEvidenceError("Model command is not signed in", "authentication");
|
|
194
|
+
}
|
|
176
195
|
if (!result) throw new Error("Model command omitted its result event");
|
|
177
196
|
if (result.is_error) {
|
|
178
|
-
throw
|
|
197
|
+
throw claudeResultError(events, result);
|
|
179
198
|
}
|
|
180
199
|
if (typeof result.result !== "string") throw new Error("Model command returned a non-text answer");
|
|
181
200
|
if ((result.permission_denials?.length ?? 0) > 0) {
|
|
@@ -425,12 +444,12 @@ export async function runModel(provider, prompt, directory, signal) {
|
|
|
425
444
|
if (execution.errorCode === "ABORT_ERR") throw new Error("Model command was cancelled");
|
|
426
445
|
if (execution.errorCode) throw new Error("Model command could not start");
|
|
427
446
|
if (execution.code !== 0 && !execution.stdout) {
|
|
428
|
-
throw
|
|
447
|
+
throw providerProcessError(execution.code, execution.providerFailureCategory);
|
|
429
448
|
}
|
|
430
449
|
return provider.startsWith("codex-")
|
|
431
450
|
? parseCodexEvents(execution.stdout, [], undefined, execution.code,
|
|
432
451
|
providerModel(provider), invocation.providerSecrets, execution.providerFailureCategory)
|
|
433
|
-
: parseClaudeEvents(execution.stdout, execution.code);
|
|
452
|
+
: parseClaudeEvents(execution.stdout, execution.code, execution.providerFailureCategory);
|
|
434
453
|
}
|
|
435
454
|
|
|
436
455
|
export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, processExitCode = 0,
|
|
@@ -452,9 +471,9 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
452
471
|
const errorText = events.flatMap((event) => [event.message, event.error?.message, event.item?.message])
|
|
453
472
|
.filter((value) => typeof value === "string").join("\n");
|
|
454
473
|
if (authentication.test(errorText)) {
|
|
455
|
-
throw
|
|
474
|
+
throw providerEvidenceError("Model command is not signed in", "authentication");
|
|
456
475
|
}
|
|
457
|
-
if (processExitCode !== 0) throw
|
|
476
|
+
if (processExitCode !== 0) throw providerProcessError(processExitCode, providerFailureCategory);
|
|
458
477
|
if (events.some(({ type }) => type === "turn.failed" || type === "error")) {
|
|
459
478
|
throw new Error("Model command reported an error");
|
|
460
479
|
}
|
|
@@ -684,7 +703,7 @@ function runProcess(command, args, options) {
|
|
|
684
703
|
if (settled) return;
|
|
685
704
|
settled = true;
|
|
686
705
|
clearTimeout(timer);
|
|
687
|
-
const providerFailureCategory =
|
|
706
|
+
const providerFailureCategory = classifyProviderFailure(stderr.toString("utf8"));
|
|
688
707
|
stderr = Buffer.alloc(0);
|
|
689
708
|
resolve({ ...result, providerFailureCategory });
|
|
690
709
|
};
|
|
@@ -697,7 +716,7 @@ function runProcess(command, args, options) {
|
|
|
697
716
|
});
|
|
698
717
|
}
|
|
699
718
|
|
|
700
|
-
function
|
|
719
|
+
function classifyProviderFailure(stderr) {
|
|
701
720
|
if (/no space left|disk (?:is )?full|storage (?:capacity|quota)|\benospc\b/i.test(stderr)) {
|
|
702
721
|
return "host-storage";
|
|
703
722
|
}
|
|
@@ -713,20 +732,45 @@ function classifyCodexFailure(stderr) {
|
|
|
713
732
|
if (/connection (?:refused|reset)|network|transport|timed? out|\btls\b|certificate|\bdns\b|\beconn/i.test(stderr)) {
|
|
714
733
|
return "transport";
|
|
715
734
|
}
|
|
716
|
-
if (/provider (?:error|failure|unavailable)|model (?:is )?(?:unavailable|overloaded)|rate limit|service unavailable/i.test(stderr)) {
|
|
735
|
+
if (/provider (?:error|failure|unavailable)|model (?:is )?(?:unavailable|overloaded)|rate limit|service unavailable|(?:hit|reached) your limit/i.test(stderr)) {
|
|
717
736
|
return "provider";
|
|
718
737
|
}
|
|
719
738
|
return "unknown";
|
|
720
739
|
}
|
|
721
740
|
|
|
722
|
-
function
|
|
741
|
+
function claudeFailureCategory(events, result, fallback) {
|
|
742
|
+
const end = result ? events.lastIndexOf(result) : events.length;
|
|
743
|
+
const assistantFailure = events.findLast((event, index) => (
|
|
744
|
+
index < end
|
|
745
|
+
&& event.type === "assistant"
|
|
746
|
+
&& (event.parent_tool_use_id ?? null) === null
|
|
747
|
+
&& (event.agent_id ?? null) === null
|
|
748
|
+
&& claudeAssistantFailureCategories.has(event.error)
|
|
749
|
+
));
|
|
750
|
+
if (assistantFailure) return claudeAssistantFailureCategories.get(assistantFailure.error);
|
|
751
|
+
if (!result?.is_error) return fallback;
|
|
752
|
+
const errors = Array.isArray(result.errors) ? result.errors : [];
|
|
753
|
+
for (const detail of [result.result, ...errors]) {
|
|
754
|
+
if (typeof detail !== "string") continue;
|
|
755
|
+
const category = classifyProviderFailure(detail);
|
|
756
|
+
if (category !== "unknown") return category;
|
|
757
|
+
}
|
|
758
|
+
return fallback;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function claudeResultError(events, result) {
|
|
762
|
+
const message = providerErrorMessages.get(result.subtype) ?? "Model command reported an error";
|
|
763
|
+
return providerEvidenceError(message, claudeFailureCategory(events, result));
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function providerEvidenceError(message, category) {
|
|
723
767
|
return Object.assign(new Error(message), {
|
|
724
|
-
providerFailureCategory:
|
|
768
|
+
providerFailureCategory: providerFailureCategories.has(category) ? category : "unknown",
|
|
725
769
|
});
|
|
726
770
|
}
|
|
727
771
|
|
|
728
|
-
function
|
|
729
|
-
return
|
|
772
|
+
function providerProcessError(code, category) {
|
|
773
|
+
return providerEvidenceError(`Model command exited ${code}`, category);
|
|
730
774
|
}
|
|
731
775
|
|
|
732
776
|
function forbiddenItemError(items) {
|
package/semantic/test.mjs
CHANGED
|
@@ -606,10 +606,12 @@ function recordModelFailure(record, error) {
|
|
|
606
606
|
const category = error?.providerFailureCategory;
|
|
607
607
|
if ([
|
|
608
608
|
"authentication",
|
|
609
|
+
"billing",
|
|
609
610
|
"configuration",
|
|
610
611
|
"host-storage",
|
|
611
612
|
"permission-or-sandbox",
|
|
612
613
|
"provider",
|
|
614
|
+
"rate-limit",
|
|
613
615
|
"transport",
|
|
614
616
|
"unknown",
|
|
615
617
|
].includes(category)) record.providerFailureCategory = category;
|