@koda-sl/baker-bridge 0.147.0-dev.ad499429a → 0.148.0-dev.a87640a51

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.
@@ -260,6 +260,24 @@ function memoryInFlightTurns(initial) {
260
260
  },
261
261
  };
262
262
  }
263
+ // The CLI's own report of a failed model request, written as an assistant
264
+ // message — the shape production stores (`model: "<synthetic>"`, `error`).
265
+ function apiErrorMessage(uuid, text, error) {
266
+ return {
267
+ type: "assistant",
268
+ uuid,
269
+ session_id: "session_api_error",
270
+ parent_tool_use_id: null,
271
+ error,
272
+ is_api_error_message: true,
273
+ message: {
274
+ role: "assistant",
275
+ model: "<synthetic>",
276
+ content: [{ type: "text", text }],
277
+ stop_reason: "stop_sequence",
278
+ },
279
+ };
280
+ }
263
281
  function resultMessage(costUsd, sessionId, modelUsage) {
264
282
  return {
265
283
  type: "result",
@@ -2061,7 +2079,7 @@ describe("BridgeJobExecutor", () => {
2061
2079
  expect(savedBoundaries).toEqual([]);
2062
2080
  expect(clearCalls).toContainEqual({ threadId: "thread_result_error", reason: "result_error" });
2063
2081
  });
2064
- it("preserves provider status details when an SDK result error omits errors", async () => {
2082
+ it("reports a provider's server error in product language, not its status line", async () => {
2065
2083
  const clearCalls = [];
2066
2084
  const executor = createExecutor({
2067
2085
  clearCalls,
@@ -2087,10 +2105,69 @@ describe("BridgeJobExecutor", () => {
2087
2105
  kind: "errored",
2088
2106
  costUsd: 0.17,
2089
2107
  model: "claude-test",
2090
- errors: ["Claude Code returned an error result: API Error: 500 Internal Server Error; apiErrorStatus 500"],
2108
+ errors: [expect.stringContaining("on our side")],
2091
2109
  });
2092
2110
  expect(clearCalls).toContainEqual({ threadId: "thread_provider_result_error", reason: "result_error" });
2093
2111
  });
2112
+ it("never shows the marketer a model provider's billing error, as a reply or as the turn's error", async () => {
2113
+ const providerError = "API Error: 402 Insufficient credits. Add more using https://openrouter.ai/settings/credits";
2114
+ const relay = recordingRelay();
2115
+ const executor = createExecutor({
2116
+ relay,
2117
+ queryResults: [
2118
+ scriptedMessages(apiErrorMessage("assistant_402", providerError, "unknown"), {
2119
+ type: "result",
2120
+ subtype: "success",
2121
+ is_error: true,
2122
+ result: providerError,
2123
+ api_error_status: 402,
2124
+ total_cost_usd: 0,
2125
+ session_id: "session_402",
2126
+ }),
2127
+ ],
2128
+ });
2129
+ const outcome = await executor.execute({ threadId: "thread_402", turnId: "turn_402", prompt: "Build the creatives", history: history([]) }, activeControls());
2130
+ const shown = JSON.stringify([relay.assistantEvents, outcome.kind === "errored" ? outcome.errors : []]);
2131
+ expect(outcome.kind).toBe("errored");
2132
+ expect(shown).not.toMatch(/API Error|openrouter|402/i);
2133
+ });
2134
+ it("tells the marketer how to fix an image the provider could not read, not to send the same message again", async () => {
2135
+ const imageError = 'API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Could not process image"}}';
2136
+ const executor = createExecutor({
2137
+ queryResults: [
2138
+ scriptedMessages({
2139
+ type: "result",
2140
+ subtype: "success",
2141
+ is_error: true,
2142
+ result: imageError,
2143
+ api_error_status: 400,
2144
+ total_cost_usd: 0,
2145
+ session_id: "session_bad_image",
2146
+ }),
2147
+ ],
2148
+ });
2149
+ const outcome = await executor.execute({ threadId: "thread_bad_image", turnId: "turn_bad_image", prompt: "Use this photo", history: history([]) }, activeControls());
2150
+ expect(outcome.kind === "errored" ? outcome.errors : []).toEqual([
2151
+ expect.stringContaining("extension lies about its format"),
2152
+ ]);
2153
+ });
2154
+ it("keeps a server error the turn recovered from out of the chat, and in the turn events", async () => {
2155
+ const providerError = "API Error: Server error mid-response. The response above may be incomplete.";
2156
+ const relay = recordingRelay();
2157
+ const turnEvents = [];
2158
+ const executor = createExecutor({
2159
+ relay,
2160
+ turnEvents,
2161
+ queryResults: [
2162
+ scriptedMessages(apiErrorMessage("assistant_server_error", providerError, "server_error"), assistantMessage("assistant_recovered", "Your three headlines are ready.", "session_recovered"), resultMessage(0.3, "session_recovered")),
2163
+ ],
2164
+ });
2165
+ const outcome = await executor.execute({ threadId: "thread_recovered", turnId: "turn_recovered", prompt: "Write headlines", history: history([]) }, activeControls());
2166
+ expect(outcome.kind).toBe("completed");
2167
+ expect(JSON.stringify(relay.assistantEvents)).not.toContain("API Error");
2168
+ expect(JSON.stringify(relay.assistantEvents)).toContain("Your three headlines are ready.");
2169
+ expect(turnEvents).toContainEqual(expect.objectContaining({ event: "api_error", detail: { error: "server_error", text: providerError } }));
2170
+ });
2094
2171
  it("closes SDK queries on error and inactive exits but not on natural clean completion", async () => {
2095
2172
  const naturalCloseCalls = [];
2096
2173
  const naturalExecutor = createExecutor({