Files
tutti/packages/agent/gui/shared/agentEnv/agentErrorPresentation.spec.ts
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

194 lines
6.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
classifyFailedAgentMessage,
classifyRecoverableAgentMessage,
resolveAgentErrorPresentation
} from "./agentErrorPresentation";
describe("classifyFailedAgentMessage", () => {
it("recovers auth from a plain Claude 401 message", () => {
expect(
classifyFailedAgentMessage(
"Failed to authenticate. API Error: 401 Invalid authentication credentials"
)
).toBe("auth_required");
});
it("recovers cli/version/network codes from text", () => {
expect(classifyFailedAgentMessage("spawn codex ENOENT")).toBe(
"cli_not_found"
);
expect(
classifyFailedAgentMessage("codex-acp requires a newer version of codex")
).toBe("cli_version_unsupported");
expect(
classifyFailedAgentMessage("getaddrinfo ENOTFOUND api.anthropic.com")
).toBe("network_error");
expect(
classifyFailedAgentMessage(
'API Error: 522 {"title":"Error 522: Connection timed out"}'
)
).toBe("request_timed_out");
});
it("returns null for unclassified failures so they stay plain", () => {
expect(classifyFailedAgentMessage("request timed out")).toBeNull();
expect(classifyFailedAgentMessage("here is your answer")).toBeNull();
expect(classifyFailedAgentMessage(null)).toBeNull();
});
it("classifies account failures before authentication wrappers", () => {
expect(
classifyFailedAgentMessage(
"Kimi models rejected OAuth credentials: We're unable to verify your membership benefits"
)
).toBe("subscription_required");
expect(
classifyFailedAgentMessage(
"Kimi models rejected OAuth credentials: 402 Payment Required"
)
).toBe("insufficient_credits");
expect(
classifyFailedAgentMessage(
"Kimi request rejected OAuth credentials: reached your usage limit"
)
).toBe("quota_or_rate_limit");
});
it("recovers plan, balance, and rate-limit copy into stable account buckets", () => {
expect(classifyFailedAgentMessage("rate limit exceeded")).toBe(
"quota_or_rate_limit"
);
expect(classifyFailedAgentMessage("Key limit exceeded")).toBe(
"quota_or_rate_limit"
);
expect(classifyFailedAgentMessage("Upgrade your plan to continue")).toBe(
"quota_or_rate_limit"
);
expect(classifyFailedAgentMessage("Add a payment method to continue")).toBe(
"quota_or_rate_limit"
);
expect(
classifyFailedAgentMessage("Membership expired, please renew your plan")
).toBe("subscription_required");
expect(classifyFailedAgentMessage("Account balance is insufficient")).toBe(
"insufficient_credits"
);
});
});
describe("classifyRecoverableAgentMessage", () => {
it("recovers the standalone Claude Code login notice even when SDK marks it completed", () => {
expect(
classifyRecoverableAgentMessage({
body: "Not logged in · Please run /login",
statusKind: "completed"
})
).toBe("auth_required");
});
it("does not reinterpret other completed messages", () => {
expect(
classifyRecoverableAgentMessage({
body: "Authentication is configured and the request completed.",
statusKind: "completed"
})
).toBeNull();
});
it("does not reinterpret a normal Claude answer that discusses the notice", () => {
expect(
classifyRecoverableAgentMessage({
body: 'The message "Not logged in · Please run /login" means authentication is required.',
statusKind: "completed"
})
).toBeNull();
});
});
describe("resolveAgentErrorPresentation", () => {
it("routes env-fixable failures to the matching wizard step", () => {
const expectations: Record<string, { focus: string; actionKey: string }> = {
auth_required: {
focus: "auth",
actionKey: "agentHost.agentGui.visibleErrorActionRelogin"
},
cli_not_found: {
focus: "install",
actionKey: "agentHost.agentGui.visibleErrorActionInstall"
},
cli_version_unsupported: {
focus: "upgrade",
actionKey: "agentHost.agentGui.visibleErrorActionUpgrade"
},
network_error: {
focus: "network",
actionKey: "agentHost.agentGui.visibleErrorActionCheckNetwork"
},
runtime_unavailable: {
focus: "detect",
actionKey: "agentHost.agentGui.visibleErrorActionDetect"
},
provider_empty_response: {
focus: "detect",
actionKey: "agentHost.agentGui.visibleErrorActionDetect"
}
};
for (const [code, expected] of Object.entries(expectations)) {
const presentation = resolveAgentErrorPresentation(code);
expect(presentation, code).not.toBeNull();
expect(presentation?.focus, code).toBe(expected.focus);
expect(presentation?.actionKey, code).toBe(expected.actionKey);
expect(presentation?.messageKey, code).toBeTruthy();
}
});
it("shows accurate copy but NO wizard CTA for transient/server-side failures", () => {
for (const code of [
"request_timed_out",
"provider_config_timeout",
"provider_stream_disconnected",
"provider_concurrency_limit",
"insufficient_credits",
"model_not_allowed",
"plugin_unavailable",
"quota_or_rate_limit",
"session_interrupted",
"subscription_required"
]) {
const presentation = resolveAgentErrorPresentation(code);
expect(presentation, code).not.toBeNull();
expect(presentation?.focus, code).toBeNull();
expect(presentation?.actionKey, code).toBeNull();
expect(presentation?.messageKey, code).toBeTruthy();
}
});
it("keeps insufficient-credit product semantics out of AgentGUI", () => {
expect(resolveAgentErrorPresentation("insufficient_credits")).toEqual({
messageKey: "agentHost.agentGui.visibleErrorInsufficientCreditsUnknown",
focus: null,
actionKey: null
});
});
it("offers a self-detect escape hatch for ambiguous hard failures", () => {
for (const code of ["process_exited", "provider_error", "unknown"]) {
const presentation = resolveAgentErrorPresentation(code);
expect(presentation?.focus, code).toBe("detect");
expect(presentation?.actionKey, code).toBe(
"agentHost.agentGui.visibleErrorActionDetect"
);
// Generic codes keep the caller's phase-aware title.
expect(presentation?.messageKey, code).toBeNull();
}
});
it("returns null for unknown/empty codes so the caller renders a plain card", () => {
expect(resolveAgentErrorPresentation("WHATEVER")).toBeNull();
expect(resolveAgentErrorPresentation(null)).toBeNull();
expect(resolveAgentErrorPresentation(undefined)).toBeNull();
expect(resolveAgentErrorPresentation("")).toBeNull();
});
});