mirror of https://github.com/openclaw/openclaw.git
fix: propagate active billing model across lifecycle errors
This commit is contained in:
parent
611a1fec24
commit
be8136878c
|
|
@ -518,6 +518,59 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("uses the active erroring model in billing failover errors", async () => {
|
||||
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-agent-"));
|
||||
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-workspace-"));
|
||||
try {
|
||||
await writeAuthStore(agentDir);
|
||||
runEmbeddedAttemptMock.mockResolvedValueOnce(
|
||||
makeAttempt({
|
||||
assistantTexts: [],
|
||||
lastAssistant: buildAssistant({
|
||||
stopReason: "error",
|
||||
errorMessage: "insufficient credits",
|
||||
provider: "openai",
|
||||
model: "mock-rotated",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
let thrown: unknown;
|
||||
try {
|
||||
await runEmbeddedPiAgent({
|
||||
sessionId: "session:test",
|
||||
sessionKey: "agent:test:billing-failover-active-model",
|
||||
sessionFile: path.join(workspaceDir, "session.jsonl"),
|
||||
workspaceDir,
|
||||
agentDir,
|
||||
config: makeConfig({ fallbacks: ["openai/mock-2"] }),
|
||||
prompt: "hello",
|
||||
provider: "openai",
|
||||
model: "mock-1",
|
||||
authProfileId: "openai:p1",
|
||||
authProfileIdSource: "user",
|
||||
timeoutMs: 5_000,
|
||||
runId: "run:billing-failover-active-model",
|
||||
});
|
||||
} catch (err) {
|
||||
thrown = err;
|
||||
}
|
||||
|
||||
expect(thrown).toMatchObject({
|
||||
name: "FailoverError",
|
||||
reason: "billing",
|
||||
provider: "openai",
|
||||
model: "mock-rotated",
|
||||
});
|
||||
expect(thrown).toBeInstanceOf(Error);
|
||||
expect((thrown as Error).message).toContain("openai (mock-rotated) returned a billing error");
|
||||
expect(runEmbeddedAttemptMock).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("skips profiles in cooldown when rotating after failure", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ export function handleAgentEnd(ctx: EmbeddedPiSubscribeContext) {
|
|||
const friendlyError = formatAssistantErrorText(lastAssistant, {
|
||||
cfg: ctx.params.config,
|
||||
sessionKey: ctx.params.sessionKey,
|
||||
provider: lastAssistant.provider,
|
||||
model: lastAssistant.model,
|
||||
});
|
||||
emitAgentEvent({
|
||||
runId: ctx.params.runId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createStubSessionHarness } from "./pi-embedded-subscribe.e2e-harness.js";
|
||||
import { subscribeEmbeddedPiSession } from "./pi-embedded-subscribe.js";
|
||||
|
||||
describe("subscribeEmbeddedPiSession lifecycle billing errors", () => {
|
||||
it("includes provider and model context in lifecycle billing errors", () => {
|
||||
const { session, emit } = createStubSessionHarness();
|
||||
const onAgentEvent = vi.fn();
|
||||
|
||||
subscribeEmbeddedPiSession({
|
||||
session,
|
||||
runId: "run-billing-error",
|
||||
onAgentEvent,
|
||||
sessionKey: "test-session",
|
||||
});
|
||||
|
||||
const assistantMessage = {
|
||||
role: "assistant",
|
||||
stopReason: "error",
|
||||
errorMessage: "insufficient credits",
|
||||
provider: "Anthropic",
|
||||
model: "claude-3-5-sonnet",
|
||||
} as AssistantMessage;
|
||||
|
||||
emit({ type: "message_update", message: assistantMessage });
|
||||
emit({ type: "agent_end" });
|
||||
|
||||
const lifecycleError = onAgentEvent.mock.calls.find(
|
||||
(call) => call[0]?.stream === "lifecycle" && call[0]?.data?.phase === "error",
|
||||
);
|
||||
expect(lifecycleError).toBeDefined();
|
||||
expect(lifecycleError?.[0]?.data?.error).toContain("Anthropic (claude-3-5-sonnet)");
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue