bopodev-agent-sdk 0.1.25 → 0.1.27
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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-typecheck.log +1 -1
- package/README.md +27 -0
- package/dist/adapters/codex/src/server/quota.d.ts +2 -0
- package/dist/agent-sdk/src/adapters.d.ts +12 -1
- package/dist/agent-sdk/src/provider-failures/anthropic-api.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/claude-code.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/codex.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/common.d.ts +7 -0
- package/dist/agent-sdk/src/provider-failures/cursor.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/gemini-cli.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/http.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/index.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/openai-api.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/opencode.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/shell.d.ts +5 -0
- package/dist/agent-sdk/src/provider-failures/types.d.ts +20 -0
- package/dist/agent-sdk/src/quota.d.ts +4 -0
- package/dist/agent-sdk/src/runtime-core.d.ts +1 -1
- package/dist/agent-sdk/src/runtime-http.d.ts +4 -2
- package/dist/agent-sdk/src/runtime-parsers.d.ts +1 -1
- package/dist/agent-sdk/src/runtime.d.ts +13 -0
- package/dist/agent-sdk/src/types.d.ts +19 -1
- package/dist/contracts/src/index.d.ts +426 -11
- package/package.json +2 -2
- package/src/adapters.ts +477 -58
- package/src/provider-failures/anthropic-api.ts +20 -0
- package/src/provider-failures/claude-code.ts +20 -0
- package/src/provider-failures/codex.ts +23 -0
- package/src/provider-failures/common.ts +86 -0
- package/src/provider-failures/cursor.ts +20 -0
- package/src/provider-failures/gemini-cli.ts +20 -0
- package/src/provider-failures/http.ts +12 -0
- package/src/provider-failures/index.ts +54 -0
- package/src/provider-failures/openai-api.ts +20 -0
- package/src/provider-failures/opencode.ts +20 -0
- package/src/provider-failures/shell.ts +12 -0
- package/src/provider-failures/types.ts +28 -0
- package/src/runtime-core.ts +7 -1
- package/src/runtime-http.ts +51 -6
- package/src/runtime-parsers.ts +1 -0
- package/src/runtime.ts +299 -1
- package/src/types.ts +20 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeAnthropicFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
if (!normalized) return detail;
|
|
7
|
+
const lower = normalized.toLowerCase();
|
|
8
|
+
if (/model/.test(lower) && /(not supported|does not exist|not found|unavailable|unsupported)/.test(lower)) {
|
|
9
|
+
return "Selected Anthropic model is unavailable for this account. Choose a supported model.";
|
|
10
|
+
}
|
|
11
|
+
if (/(auth|unauthorized|api key|invalid_api_key|permission denied)/.test(lower)) {
|
|
12
|
+
return "Authentication failed for provider runtime. Verify credentials and account access.";
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const anthropicApiFailureResolver = {
|
|
18
|
+
normalize: normalizeAnthropicFailureDetail,
|
|
19
|
+
classify: createClassifier(normalizeAnthropicFailureDetail)
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeClaudeFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
if (!normalized) return detail;
|
|
7
|
+
const lower = normalized.toLowerCase();
|
|
8
|
+
if (/model/.test(lower) && /(not supported|does not exist|not found|unavailable|unsupported)/.test(lower)) {
|
|
9
|
+
return "Selected Anthropic model is unavailable for this account. Choose a supported model.";
|
|
10
|
+
}
|
|
11
|
+
if (/(auth|unauthorized|api key|invalid_api_key|permission denied|not logged in|claude login)/.test(lower)) {
|
|
12
|
+
return "Authentication failed for provider runtime. Verify credentials and account access.";
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const claudeCodeFailureResolver = {
|
|
18
|
+
normalize: normalizeClaudeFailureDetail,
|
|
19
|
+
classify: createClassifier(normalizeClaudeFailureDetail)
|
|
20
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeCodexFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
if (!normalized) return detail;
|
|
7
|
+
const lower = normalized.toLowerCase();
|
|
8
|
+
if (/not supported/.test(lower) && lower.includes("chatgpt account") && lower.includes("model")) {
|
|
9
|
+
return "Codex model not supported for this ChatGPT account. Select a supported Codex model.";
|
|
10
|
+
}
|
|
11
|
+
if (/model/.test(lower) && /(not supported|does not exist|not found|unavailable|unsupported)/.test(lower)) {
|
|
12
|
+
return "Selected model is unavailable for this account. Choose a supported model.";
|
|
13
|
+
}
|
|
14
|
+
if (/(auth|unauthorized|api key|invalid_api_key|permission denied)/.test(lower)) {
|
|
15
|
+
return "Authentication failed for provider runtime. Verify credentials and account access.";
|
|
16
|
+
}
|
|
17
|
+
return normalized;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const codexFailureResolver = {
|
|
21
|
+
normalize: normalizeCodexFailureDetail,
|
|
22
|
+
classify: createClassifier(normalizeCodexFailureDetail)
|
|
23
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { containsUsageLimitHardStopFailure } from "../runtime-core";
|
|
2
|
+
import type {
|
|
3
|
+
ProviderFailureClassification,
|
|
4
|
+
ProviderFailureClassifier,
|
|
5
|
+
ProviderFailureInput,
|
|
6
|
+
ProviderFailureNormalizer
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
export function normalizeWhitespace(value: string) {
|
|
10
|
+
return value.replace(/\s+/g, " ").trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function hasModelUnsupportedSignal(haystack: string) {
|
|
14
|
+
return (
|
|
15
|
+
/model/.test(haystack) &&
|
|
16
|
+
/(not supported|unsupported|does not exist|not found|unavailable|invalid model|model_not_found)/.test(haystack)
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hasAuthFailureSignal(haystack: string) {
|
|
21
|
+
return /(not logged in|login required|requires login|authentication required|authentication failed|unauthorized|invalid[_\s-]?api[_\s-]?key|api key)/.test(
|
|
22
|
+
haystack
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function hasUnknownSessionSignal(haystack: string) {
|
|
27
|
+
return /unknown session|session .* not found|could not resume|missing rollout path for thread|conversation .* not found|thread .* not found/.test(
|
|
28
|
+
haystack
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function classifyFromSignals(
|
|
33
|
+
normalize: ProviderFailureNormalizer,
|
|
34
|
+
input: ProviderFailureInput
|
|
35
|
+
): ProviderFailureClassification {
|
|
36
|
+
const detail = normalize(input.detail);
|
|
37
|
+
const combined = `${detail}\n${input.stderr ?? ""}\n${input.stdout ?? ""}`.toLowerCase();
|
|
38
|
+
if (containsUsageLimitHardStopFailure(combined)) {
|
|
39
|
+
const blockerCode =
|
|
40
|
+
combined.includes("insufficient_quota") ||
|
|
41
|
+
combined.includes("billing_hard_limit_reached") ||
|
|
42
|
+
combined.includes("out of funds")
|
|
43
|
+
? "provider_out_of_funds"
|
|
44
|
+
: "provider_quota_exhausted";
|
|
45
|
+
return {
|
|
46
|
+
detail,
|
|
47
|
+
blockerCode,
|
|
48
|
+
retryable: false,
|
|
49
|
+
providerUsageLimited: true
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (hasModelUnsupportedSignal(combined)) {
|
|
53
|
+
return {
|
|
54
|
+
detail,
|
|
55
|
+
blockerCode: "model_not_supported",
|
|
56
|
+
retryable: false,
|
|
57
|
+
providerUsageLimited: false
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (hasAuthFailureSignal(combined)) {
|
|
61
|
+
return {
|
|
62
|
+
detail,
|
|
63
|
+
blockerCode: "auth_required",
|
|
64
|
+
retryable: false,
|
|
65
|
+
providerUsageLimited: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (hasUnknownSessionSignal(combined)) {
|
|
69
|
+
return {
|
|
70
|
+
detail: "Saved provider session is no longer available. Retry with a fresh session.",
|
|
71
|
+
blockerCode: "unknown_session",
|
|
72
|
+
retryable: true,
|
|
73
|
+
providerUsageLimited: false
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
detail,
|
|
78
|
+
blockerCode: input.failureType?.trim() || "runtime_failed",
|
|
79
|
+
retryable: input.failureType !== "auth" && input.failureType !== "bad_response",
|
|
80
|
+
providerUsageLimited: false
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createClassifier(normalize: ProviderFailureNormalizer): ProviderFailureClassifier {
|
|
85
|
+
return (input) => classifyFromSignals(normalize, input);
|
|
86
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeCursorFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
if (!normalized) return detail;
|
|
7
|
+
const lower = normalized.toLowerCase();
|
|
8
|
+
if (/model/.test(lower) && /(not supported|does not exist|not found|unavailable|unsupported)/.test(lower)) {
|
|
9
|
+
return "Selected model is unavailable for this account. Choose a supported model.";
|
|
10
|
+
}
|
|
11
|
+
if (/(auth|unauthorized|api key|invalid_api_key|permission denied)/.test(lower)) {
|
|
12
|
+
return "Authentication failed for provider runtime. Verify credentials and account access.";
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const cursorFailureResolver = {
|
|
18
|
+
normalize: normalizeCursorFailureDetail,
|
|
19
|
+
classify: createClassifier(normalizeCursorFailureDetail)
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeGeminiFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
if (!normalized) return detail;
|
|
7
|
+
const lower = normalized.toLowerCase();
|
|
8
|
+
if (/model/.test(lower) && /(not supported|does not exist|not found|unavailable|unsupported)/.test(lower)) {
|
|
9
|
+
return "Selected model is unavailable for this account. Choose a supported model.";
|
|
10
|
+
}
|
|
11
|
+
if (/(auth|unauthorized|api key|permission denied)/.test(lower)) {
|
|
12
|
+
return "Gemini authentication failed. Verify Gemini credentials and account permissions.";
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const geminiCliFailureResolver = {
|
|
18
|
+
normalize: normalizeGeminiFailureDetail,
|
|
19
|
+
classify: createClassifier(normalizeGeminiFailureDetail)
|
|
20
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeHttpFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
return normalized || detail;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const httpFailureResolver = {
|
|
10
|
+
normalize: normalizeHttpFailureDetail,
|
|
11
|
+
classify: createClassifier(normalizeHttpFailureDetail)
|
|
12
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AgentProviderType } from "../types";
|
|
2
|
+
import { anthropicApiFailureResolver } from "./anthropic-api";
|
|
3
|
+
import { claudeCodeFailureResolver } from "./claude-code";
|
|
4
|
+
import { codexFailureResolver } from "./codex";
|
|
5
|
+
import { cursorFailureResolver } from "./cursor";
|
|
6
|
+
import { geminiCliFailureResolver } from "./gemini-cli";
|
|
7
|
+
import { httpFailureResolver } from "./http";
|
|
8
|
+
import { openAiApiFailureResolver } from "./openai-api";
|
|
9
|
+
import { opencodeFailureResolver } from "./opencode";
|
|
10
|
+
import { shellFailureResolver } from "./shell";
|
|
11
|
+
import type {
|
|
12
|
+
ProviderFailureClassification,
|
|
13
|
+
ProviderFailureInput,
|
|
14
|
+
ProviderFailureResolverMap
|
|
15
|
+
} from "./types";
|
|
16
|
+
|
|
17
|
+
const providerFailureResolvers: ProviderFailureResolverMap = {
|
|
18
|
+
codex: codexFailureResolver,
|
|
19
|
+
claude_code: claudeCodeFailureResolver,
|
|
20
|
+
openai_api: openAiApiFailureResolver,
|
|
21
|
+
anthropic_api: anthropicApiFailureResolver,
|
|
22
|
+
cursor: cursorFailureResolver,
|
|
23
|
+
opencode: opencodeFailureResolver,
|
|
24
|
+
gemini_cli: geminiCliFailureResolver,
|
|
25
|
+
shell: shellFailureResolver,
|
|
26
|
+
http: httpFailureResolver
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function normalizeProviderFailureDetail(provider: AgentProviderType, detail: string) {
|
|
30
|
+
const resolver = providerFailureResolvers[provider];
|
|
31
|
+
if (!resolver) {
|
|
32
|
+
return detail.replace(/\s+/g, " ").trim() || detail;
|
|
33
|
+
}
|
|
34
|
+
return resolver.normalize(detail);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function classifyProviderFailure(
|
|
38
|
+
provider: AgentProviderType,
|
|
39
|
+
input: ProviderFailureInput
|
|
40
|
+
): ProviderFailureClassification {
|
|
41
|
+
const resolver = providerFailureResolvers[provider];
|
|
42
|
+
if (!resolver) {
|
|
43
|
+
const normalized = input.detail.replace(/\s+/g, " ").trim() || input.detail;
|
|
44
|
+
return {
|
|
45
|
+
detail: normalized,
|
|
46
|
+
blockerCode: input.failureType?.trim() || "runtime_failed",
|
|
47
|
+
retryable: input.failureType !== "auth" && input.failureType !== "bad_response",
|
|
48
|
+
providerUsageLimited: false
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return resolver.classify(input);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export type { ProviderFailureClassification, ProviderFailureInput } from "./types";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeOpenAiFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
if (!normalized) return detail;
|
|
7
|
+
const lower = normalized.toLowerCase();
|
|
8
|
+
if (/model/.test(lower) && /(not supported|does not exist|not found|unavailable|unsupported)/.test(lower)) {
|
|
9
|
+
return "Selected model is unavailable for this account. Choose a supported model.";
|
|
10
|
+
}
|
|
11
|
+
if (/(auth|unauthorized|api key|invalid_api_key|permission denied)/.test(lower)) {
|
|
12
|
+
return "Authentication failed for provider runtime. Verify credentials and account access.";
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const openAiApiFailureResolver = {
|
|
18
|
+
normalize: normalizeOpenAiFailureDetail,
|
|
19
|
+
classify: createClassifier(normalizeOpenAiFailureDetail)
|
|
20
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeOpenCodeFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
if (!normalized) return detail;
|
|
7
|
+
const lower = normalized.toLowerCase();
|
|
8
|
+
if (/model/.test(lower) && /(not supported|does not exist|not found|unavailable|unsupported)/.test(lower)) {
|
|
9
|
+
return "Selected model is unavailable for this account. Choose a supported model.";
|
|
10
|
+
}
|
|
11
|
+
if (/(auth|unauthorized|api key|invalid_api_key|permission denied)/.test(lower)) {
|
|
12
|
+
return "Authentication failed for provider runtime. Verify credentials and account access.";
|
|
13
|
+
}
|
|
14
|
+
return normalized;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const opencodeFailureResolver = {
|
|
18
|
+
normalize: normalizeOpenCodeFailureDetail,
|
|
19
|
+
classify: createClassifier(normalizeOpenCodeFailureDetail)
|
|
20
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createClassifier, normalizeWhitespace } from "./common";
|
|
2
|
+
import type { ProviderFailureNormalizer } from "./types";
|
|
3
|
+
|
|
4
|
+
const normalizeShellFailureDetail: ProviderFailureNormalizer = (detail) => {
|
|
5
|
+
const normalized = normalizeWhitespace(detail);
|
|
6
|
+
return normalized || detail;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const shellFailureResolver = {
|
|
10
|
+
normalize: normalizeShellFailureDetail,
|
|
11
|
+
classify: createClassifier(normalizeShellFailureDetail)
|
|
12
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { AgentProviderType } from "../types";
|
|
2
|
+
|
|
3
|
+
export type ProviderFailureInput = {
|
|
4
|
+
detail: string;
|
|
5
|
+
stderr?: string;
|
|
6
|
+
stdout?: string;
|
|
7
|
+
failureType?: string | null;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type ProviderFailureClassification = {
|
|
11
|
+
detail: string;
|
|
12
|
+
blockerCode: string;
|
|
13
|
+
retryable: boolean;
|
|
14
|
+
providerUsageLimited: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ProviderFailureClassifier = (
|
|
18
|
+
input: ProviderFailureInput
|
|
19
|
+
) => ProviderFailureClassification;
|
|
20
|
+
|
|
21
|
+
export type ProviderFailureNormalizer = (detail: string) => string;
|
|
22
|
+
|
|
23
|
+
export type ProviderFailureResolver = {
|
|
24
|
+
normalize: ProviderFailureNormalizer;
|
|
25
|
+
classify: ProviderFailureClassifier;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ProviderFailureResolverMap = Partial<Record<AgentProviderType, ProviderFailureResolver>>;
|
package/src/runtime-core.ts
CHANGED
|
@@ -4,4 +4,10 @@ export type {
|
|
|
4
4
|
RuntimeExecutionOutput,
|
|
5
5
|
RuntimeTranscriptEvent
|
|
6
6
|
} from "./runtime";
|
|
7
|
-
export {
|
|
7
|
+
export {
|
|
8
|
+
checkRuntimeCommandHealth,
|
|
9
|
+
containsUsageLimitHardStopFailure,
|
|
10
|
+
containsRateLimitFailure,
|
|
11
|
+
executeAgentRuntime,
|
|
12
|
+
executePromptRuntime
|
|
13
|
+
} from "./runtime";
|
package/src/runtime-http.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { AgentRuntimeConfig } from "./types";
|
|
2
|
+
import type { AgentFinalRunOutput } from "bopodev-contracts";
|
|
3
|
+
import { containsUsageLimitHardStopFailure, parseAgentFinalRunOutput } from "./runtime";
|
|
2
4
|
|
|
3
5
|
export type DirectApiProvider = "openai_api" | "anthropic_api";
|
|
4
6
|
|
|
@@ -10,10 +12,19 @@ export type DirectApiExecutionOutput = {
|
|
|
10
12
|
elapsedMs: number;
|
|
11
13
|
statusCode: number;
|
|
12
14
|
summary?: string;
|
|
15
|
+
finalRunOutput?: AgentFinalRunOutput;
|
|
13
16
|
tokenInput?: number;
|
|
14
17
|
tokenOutput?: number;
|
|
15
18
|
usdCost?: number;
|
|
16
|
-
failureType?:
|
|
19
|
+
failureType?:
|
|
20
|
+
| "auth"
|
|
21
|
+
| "rate_limit"
|
|
22
|
+
| "out_of_funds"
|
|
23
|
+
| "quota_exhausted"
|
|
24
|
+
| "timeout"
|
|
25
|
+
| "network"
|
|
26
|
+
| "bad_response"
|
|
27
|
+
| "http_error";
|
|
17
28
|
error?: string;
|
|
18
29
|
responsePreview?: string;
|
|
19
30
|
attemptCount: number;
|
|
@@ -21,7 +32,15 @@ export type DirectApiExecutionOutput = {
|
|
|
21
32
|
attempt: number;
|
|
22
33
|
statusCode: number;
|
|
23
34
|
elapsedMs: number;
|
|
24
|
-
failureType?:
|
|
35
|
+
failureType?:
|
|
36
|
+
| "auth"
|
|
37
|
+
| "rate_limit"
|
|
38
|
+
| "out_of_funds"
|
|
39
|
+
| "quota_exhausted"
|
|
40
|
+
| "timeout"
|
|
41
|
+
| "network"
|
|
42
|
+
| "bad_response"
|
|
43
|
+
| "http_error";
|
|
25
44
|
error?: string;
|
|
26
45
|
}>;
|
|
27
46
|
};
|
|
@@ -133,8 +152,9 @@ export async function executeDirectApiRuntime(
|
|
|
133
152
|
const preview = toPreview(text);
|
|
134
153
|
const parsed = tryParseJson(text);
|
|
135
154
|
if (!response.ok) {
|
|
136
|
-
const failureType = classifyHttpFailure(response.status);
|
|
137
155
|
const error = extractErrorMessage(provider, parsed, text) || `HTTP ${response.status}`;
|
|
156
|
+
const providerUsageLimited = containsUsageLimitHardStopFailure(`${error}\n${preview}\n${text}`);
|
|
157
|
+
const failureType = classifyHttpFailure(response.status, providerUsageLimited, error);
|
|
138
158
|
attempts.push({
|
|
139
159
|
attempt,
|
|
140
160
|
statusCode: response.status,
|
|
@@ -149,7 +169,7 @@ export async function executeDirectApiRuntime(
|
|
|
149
169
|
error,
|
|
150
170
|
responsePreview: preview
|
|
151
171
|
};
|
|
152
|
-
if (!isRetryableFailure(failureType, response.status) || attempt >= maxAttempts) {
|
|
172
|
+
if (providerUsageLimited || !isRetryableFailure(failureType, response.status) || attempt >= maxAttempts) {
|
|
153
173
|
break;
|
|
154
174
|
}
|
|
155
175
|
await sleep(retryBackoffMs * attempt);
|
|
@@ -175,6 +195,7 @@ export async function executeDirectApiRuntime(
|
|
|
175
195
|
break;
|
|
176
196
|
}
|
|
177
197
|
const summary = provider === "openai_api" ? extractOpenAiSummary(parsed) : extractAnthropicSummary(parsed);
|
|
198
|
+
const finalRunOutput = parseAgentFinalRunOutput(summary);
|
|
178
199
|
const usage = provider === "openai_api" ? extractOpenAiUsage(parsed) : extractAnthropicUsage(parsed);
|
|
179
200
|
attempts.push({
|
|
180
201
|
attempt,
|
|
@@ -189,6 +210,7 @@ export async function executeDirectApiRuntime(
|
|
|
189
210
|
elapsedMs,
|
|
190
211
|
statusCode: response.status,
|
|
191
212
|
summary,
|
|
213
|
+
finalRunOutput: finalRunOutput.output,
|
|
192
214
|
tokenInput: usage.tokenInput,
|
|
193
215
|
tokenOutput: usage.tokenOutput,
|
|
194
216
|
usdCost: usage.usdCost ?? 0,
|
|
@@ -349,14 +371,22 @@ function extractAnthropicUsage(parsed: Record<string, unknown>) {
|
|
|
349
371
|
function extractErrorMessage(provider: DirectApiProvider, parsed: Record<string, unknown> | null, fallback: string) {
|
|
350
372
|
const fallbackMessage = toPreview(fallback, 320);
|
|
351
373
|
if (!parsed) return fallbackMessage;
|
|
374
|
+
if (typeof parsed.detail === "string" && parsed.detail.trim()) {
|
|
375
|
+
return parsed.detail.trim();
|
|
376
|
+
}
|
|
377
|
+
if (typeof parsed.reason === "string" && parsed.reason.trim()) {
|
|
378
|
+
return parsed.reason.trim();
|
|
379
|
+
}
|
|
352
380
|
const error = parsed.error;
|
|
353
381
|
if (typeof error === "string" && error.trim()) return error.trim();
|
|
354
382
|
if (error && typeof error === "object") {
|
|
355
383
|
const errorRecord = error as Record<string, unknown>;
|
|
356
384
|
const candidates = [
|
|
357
385
|
errorRecord.message,
|
|
386
|
+
errorRecord.detail,
|
|
358
387
|
errorRecord.error?.toString(),
|
|
359
|
-
(errorRecord.details as string | undefined) ?? undefined
|
|
388
|
+
(errorRecord.details as string | undefined) ?? undefined,
|
|
389
|
+
errorRecord.reason
|
|
360
390
|
];
|
|
361
391
|
for (const candidate of candidates) {
|
|
362
392
|
if (typeof candidate === "string" && candidate.trim()) return candidate.trim();
|
|
@@ -368,8 +398,23 @@ function extractErrorMessage(provider: DirectApiProvider, parsed: Record<string,
|
|
|
368
398
|
return fallbackMessage;
|
|
369
399
|
}
|
|
370
400
|
|
|
371
|
-
function classifyHttpFailure(
|
|
401
|
+
function classifyHttpFailure(
|
|
402
|
+
statusCode: number,
|
|
403
|
+
providerUsageLimited: boolean,
|
|
404
|
+
detail: string
|
|
405
|
+
): "auth" | "rate_limit" | "out_of_funds" | "quota_exhausted" | "http_error" {
|
|
372
406
|
if (statusCode === 401 || statusCode === 403) return "auth";
|
|
407
|
+
if (providerUsageLimited) {
|
|
408
|
+
const normalized = detail.toLowerCase();
|
|
409
|
+
if (
|
|
410
|
+
normalized.includes("insufficient_quota") ||
|
|
411
|
+
normalized.includes("billing_hard_limit_reached") ||
|
|
412
|
+
normalized.includes("out of funds")
|
|
413
|
+
) {
|
|
414
|
+
return "out_of_funds";
|
|
415
|
+
}
|
|
416
|
+
return "quota_exhausted";
|
|
417
|
+
}
|
|
373
418
|
if (statusCode === 429) return "rate_limit";
|
|
374
419
|
return "http_error";
|
|
375
420
|
}
|