@koda-sl/baker-bridge 0.149.0-dev.3d350da58 → 0.149.0-dev.7e6763861

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.
Files changed (60) hide show
  1. package/README.md +21 -29
  2. package/dist/hono/agent-session.d.ts +0 -1
  3. package/dist/hono/agent-session.d.ts.map +1 -1
  4. package/dist/hono/agent-session.js +0 -7
  5. package/dist/hono/agent-session.js.map +1 -1
  6. package/dist/hono/agent-session.test.js +1 -7
  7. package/dist/hono/agent-session.test.js.map +1 -1
  8. package/dist/hono/agent.d.ts.map +1 -1
  9. package/dist/hono/agent.js +2 -15
  10. package/dist/hono/agent.js.map +1 -1
  11. package/dist/hono/bridge-job.d.ts +7 -19
  12. package/dist/hono/bridge-job.d.ts.map +1 -1
  13. package/dist/hono/bridge-job.js +113 -27
  14. package/dist/hono/bridge-job.js.map +1 -1
  15. package/dist/hono/bridge-job.test.js +153 -76
  16. package/dist/hono/bridge-job.test.js.map +1 -1
  17. package/dist/hono/served-model.d.ts +23 -0
  18. package/dist/hono/served-model.d.ts.map +1 -0
  19. package/dist/hono/served-model.js +82 -0
  20. package/dist/hono/served-model.js.map +1 -0
  21. package/dist/hono/served-model.test.d.ts +2 -0
  22. package/dist/hono/served-model.test.d.ts.map +1 -0
  23. package/dist/hono/served-model.test.js +29 -0
  24. package/dist/hono/served-model.test.js.map +1 -0
  25. package/dist/hono/session-continuity.d.ts +24 -0
  26. package/dist/hono/session-continuity.d.ts.map +1 -1
  27. package/dist/hono/session-continuity.integration.test.js +200 -5
  28. package/dist/hono/session-continuity.integration.test.js.map +1 -1
  29. package/dist/hono/session-continuity.js +94 -7
  30. package/dist/hono/session-continuity.js.map +1 -1
  31. package/dist/hono/session-store.d.ts +2 -0
  32. package/dist/hono/session-store.d.ts.map +1 -1
  33. package/dist/hono/session-store.js +6 -0
  34. package/dist/hono/session-store.js.map +1 -1
  35. package/dist/hono/session-usage-baseline.d.ts +1 -0
  36. package/dist/hono/session-usage-baseline.d.ts.map +1 -1
  37. package/dist/hono/session-usage-baseline.js +17 -4
  38. package/dist/hono/session-usage-baseline.js.map +1 -1
  39. package/dist/hono/turn-events.d.ts +1 -1
  40. package/dist/hono/turn-events.d.ts.map +1 -1
  41. package/dist/hono/turn-events.js.map +1 -1
  42. package/dist/hono/types.d.ts +0 -6
  43. package/dist/hono/types.d.ts.map +1 -1
  44. package/package.json +1 -1
  45. package/dist/hono/cli-sync.d.ts +0 -8
  46. package/dist/hono/cli-sync.d.ts.map +0 -1
  47. package/dist/hono/cli-sync.js +0 -93
  48. package/dist/hono/cli-sync.js.map +0 -1
  49. package/dist/hono/cli-sync.test.d.ts +0 -2
  50. package/dist/hono/cli-sync.test.d.ts.map +0 -1
  51. package/dist/hono/cli-sync.test.js +0 -81
  52. package/dist/hono/cli-sync.test.js.map +0 -1
  53. package/dist/hono/main-sync.d.ts +0 -19
  54. package/dist/hono/main-sync.d.ts.map +0 -1
  55. package/dist/hono/main-sync.js +0 -147
  56. package/dist/hono/main-sync.js.map +0 -1
  57. package/dist/hono/main-sync.test.d.ts +0 -2
  58. package/dist/hono/main-sync.test.d.ts.map +0 -1
  59. package/dist/hono/main-sync.test.js +0 -124
  60. package/dist/hono/main-sync.test.js.map +0 -1
@@ -23,6 +23,7 @@ import { CONNECTION_INPUT_TOOL_NAME } from "./connection-input.js";
23
23
  import { CREDENTIAL_INPUT_TOOL_NAME } from "./credential-input.js";
24
24
  import { FLOW_INPUT_TOOL_NAME } from "./flow-input.js";
25
25
  import { bridgeRuntimeFiles } from "./runtime-files.js";
26
+ import { alignServedModels } from "./served-model.js";
26
27
  import { INTERRUPTED_RESUME_PROMPT } from "./session-continuity.js";
27
28
  import { TAG_INPUT_TOOL_NAME } from "./tag-input.js";
28
29
  function history(entries) {
@@ -238,11 +239,14 @@ function createExecutor(options) {
238
239
  : {}),
239
240
  now: options.now,
240
241
  readUsageBaseline: async (sessionId) => options.usageBaselines?.[sessionId],
242
+ alignServedModels: async (sessionId) => {
243
+ if (options.claudeConfigDir) {
244
+ await alignServedModels(sessionId, options.claudeConfigDir);
245
+ }
246
+ },
241
247
  stalledCliExitMs: 0,
242
248
  prepareAttachments: options.prepareAttachments,
243
249
  ...(options.inFlightTurns ? { inFlightTurns: options.inFlightTurns } : {}),
244
- ...(options.catchUpWithMain ? { catchUpWithMain: options.catchUpWithMain } : {}),
245
- ...(options.keepCliInStep ? { keepCliInStep: options.keepCliInStep } : {}),
246
250
  });
247
251
  }
248
252
  function memoryInFlightTurns(initial) {
@@ -262,6 +266,24 @@ function memoryInFlightTurns(initial) {
262
266
  },
263
267
  };
264
268
  }
269
+ // The CLI's own report of a failed model request, written as an assistant
270
+ // message — the shape production stores (`model: "<synthetic>"`, `error`).
271
+ function apiErrorMessage(uuid, text, error) {
272
+ return {
273
+ type: "assistant",
274
+ uuid,
275
+ session_id: "session_api_error",
276
+ parent_tool_use_id: null,
277
+ error,
278
+ is_api_error_message: true,
279
+ message: {
280
+ role: "assistant",
281
+ model: "<synthetic>",
282
+ content: [{ type: "text", text }],
283
+ stop_reason: "stop_sequence",
284
+ },
285
+ };
286
+ }
265
287
  function resultMessage(costUsd, sessionId, modelUsage) {
266
288
  return {
267
289
  type: "result",
@@ -939,6 +961,40 @@ describe("BridgeJobExecutor", () => {
939
961
  ],
940
962
  });
941
963
  });
964
+ // OpenRouter answers under its own slug. A CLI resuming a transcript that names
965
+ // the slug drops every earlier thinking block from its first request, which
966
+ // breaks the prompt cache right after the system prompt (BAKER-331).
967
+ it("hands the CLI a transcript that names the model the conversation asked for", async () => {
968
+ const configDir = await mkdtemp(join(tmpdir(), "bridge-served-model-"));
969
+ try {
970
+ const transcriptPath = join(configDir, "projects", "-home-user-repo", "session_resume.jsonl");
971
+ await mkdir(join(configDir, "projects", "-home-user-repo"), { recursive: true });
972
+ await writeFile(transcriptPath, `${JSON.stringify({
973
+ type: "assistant",
974
+ requestedModel: "claude-opus-5-5",
975
+ message: { id: "msg_1", model: "anthropic/claude-opus-5.5", content: [{ type: "thinking", thinking: "t" }] },
976
+ })}\n`);
977
+ const resumedFrom = [];
978
+ const executor = createExecutor({
979
+ loadedBoundary: cleanBoundary("session_resume"),
980
+ claudeConfigDir: configDir,
981
+ queryResults: [
982
+ {
983
+ async *[Symbol.asyncIterator]() {
984
+ await Promise.resolve();
985
+ resumedFrom.push(readFileSync(transcriptPath, "utf8"));
986
+ yield resultMessage(0.2, "session_next");
987
+ },
988
+ },
989
+ ],
990
+ });
991
+ await executor.execute({ threadId: "thread_resume", turnId: "turn_resume", prompt: "Continue", history: history([]) }, activeControls());
992
+ expect(JSON.parse(resumedFrom[0] ?? "{}").message.model).toBe("claude-opus-5-5");
993
+ }
994
+ finally {
995
+ await rm(configDir, { recursive: true, force: true });
996
+ }
997
+ });
942
998
  it("publishes without the conversation, and without summarizing it first", async () => {
943
999
  const queryInputs = [];
944
1000
  const savedBoundaries = [];
@@ -1652,78 +1708,6 @@ describe("BridgeJobExecutor", () => {
1652
1708
  expect(queryInputs[0]?.prompt).toBe("/publish");
1653
1709
  expect(queryInputs[0]?.reminder).toBeUndefined();
1654
1710
  });
1655
- it("tells the agent which files another chat's publish just brought into its workspace", async () => {
1656
- const queryInputs = [];
1657
- const executor = createExecutor({
1658
- queryInputs,
1659
- catchUpWithMain: async () => ({ inWorkspace: true, changed: ["src/pages/home/hero.md"] }),
1660
- });
1661
- await executor.execute({
1662
- threadId: "thread_caught_up",
1663
- turnId: "turn_caught_up",
1664
- prompt: "Cambia el subtítulo del hero a «Sin entrada, paga en 24 meses».",
1665
- history: history([
1666
- { role: "user", text: "¿Qué pone ahora el hero de la home?", createdAt: 1 },
1667
- {
1668
- role: "assistant",
1669
- text: "El titular dice «Paneles solares para tu casa» y el subtítulo «Instala hoy y paga en 12 meses».",
1670
- createdAt: 2,
1671
- },
1672
- ]),
1673
- }, activeControls());
1674
- expect(queryInputs[0]?.prompt).toContain("Sin entrada, paga en 24 meses");
1675
- expect(queryInputs[0]?.reminder).toContain("Read again any of them you work on");
1676
- expect(queryInputs[0]?.reminder).toContain("- src/pages/home/hero.md");
1677
- });
1678
- it("leaves the workspace of a finished Session alone while it is reviewed", async () => {
1679
- let caughtUp = false;
1680
- const executor = createExecutor({
1681
- catchUpWithMain: () => {
1682
- caughtUp = true;
1683
- return Promise.resolve(undefined);
1684
- },
1685
- });
1686
- await executor.execute({
1687
- threadId: "thread_review_sync",
1688
- turnId: "turn_review_sync",
1689
- prompt: "review",
1690
- history: history([]),
1691
- review: { threadIds: [], chatFacts: { chatId: "chat_1" }, outputRepair: true },
1692
- }, activeControls());
1693
- expect(caughtUp).toBe(false);
1694
- });
1695
- it("brings the CLI to the release Convex sent before the turn starts", async () => {
1696
- const queryInputs = [];
1697
- const requested = [];
1698
- const executor = createExecutor({
1699
- queryInputs,
1700
- keepCliInStep: (wanted) => {
1701
- requested.push({ wanted, turnStarted: queryInputs.length > 0 });
1702
- return Promise.resolve();
1703
- },
1704
- });
1705
- await executor.execute({ threadId: "thread_cli", turnId: "turn_cli", prompt: "Hola", cliVersion: "0.412.0", history: history([]) }, activeControls());
1706
- expect(requested).toEqual([{ wanted: "0.412.0", turnStarted: false }]);
1707
- expect(queryInputs).toHaveLength(1);
1708
- });
1709
- it("keeps the Session's CLI while it is reviewed", async () => {
1710
- const wanted = [];
1711
- const executor = createExecutor({
1712
- keepCliInStep: (version) => {
1713
- wanted.push(version);
1714
- return Promise.resolve();
1715
- },
1716
- });
1717
- await executor.execute({
1718
- threadId: "thread_review_cli",
1719
- turnId: "turn_review_cli",
1720
- prompt: "review",
1721
- cliVersion: "0.412.0",
1722
- history: history([]),
1723
- review: { threadIds: [], chatFacts: { chatId: "chat_1" }, outputRepair: true },
1724
- }, activeControls());
1725
- expect(wanted).toEqual([]);
1726
- });
1727
1711
  it("keeps the viewing-reference context beside the message when a stale session falls back to rehydration", async () => {
1728
1712
  const queryInputs = [];
1729
1713
  const executor = createExecutor({
@@ -1855,6 +1839,23 @@ describe("BridgeJobExecutor", () => {
1855
1839
  expect(clearCalls).toEqual([]);
1856
1840
  expect(relay.contextSources.map((entry) => entry.source)).toEqual(["interrupted_resume", "local_resume"]);
1857
1841
  });
1842
+ it("keeps the chat's conversation when an interrupted publish that cannot be resumed is run again", async () => {
1843
+ const executor = createExecutor({
1844
+ loadedBoundary: cleanBoundary("session_clean"),
1845
+ inFlightTurns: memoryInFlightTurns({
1846
+ version: 1,
1847
+ turnId: "turn_publish",
1848
+ sessionId: "session_never_written",
1849
+ updatedAtMs: 1,
1850
+ }),
1851
+ queryResults: [
1852
+ scriptedError(new Error("No conversation found with session ID: session_never_written")),
1853
+ scriptedMessages(assistantMessage("assistant_published", "Published", "session_publish_only"), resultMessage(0.2, "session_publish_only")),
1854
+ ],
1855
+ });
1856
+ const outcome = await executor.execute({ threadId: "thread_publish", turnId: "turn_publish", prompt: "/publish" }, activeControls());
1857
+ expect(outcome).toMatchObject({ kind: "completed", cleanBoundary: undefined });
1858
+ });
1858
1859
  it("keeps the running turn's session on record until the turn ends", async () => {
1859
1860
  const recorded = [];
1860
1861
  const inFlightTurns = memoryInFlightTurns();
@@ -2135,7 +2136,7 @@ describe("BridgeJobExecutor", () => {
2135
2136
  expect(savedBoundaries).toEqual([]);
2136
2137
  expect(clearCalls).toContainEqual({ threadId: "thread_result_error", reason: "result_error" });
2137
2138
  });
2138
- it("preserves provider status details when an SDK result error omits errors", async () => {
2139
+ it("reports a provider's server error in product language, not its status line", async () => {
2139
2140
  const clearCalls = [];
2140
2141
  const executor = createExecutor({
2141
2142
  clearCalls,
@@ -2161,10 +2162,86 @@ describe("BridgeJobExecutor", () => {
2161
2162
  kind: "errored",
2162
2163
  costUsd: 0.17,
2163
2164
  model: "claude-test",
2164
- errors: ["Claude Code returned an error result: API Error: 500 Internal Server Error; apiErrorStatus 500"],
2165
+ errors: [expect.stringContaining("on our side")],
2165
2166
  });
2166
2167
  expect(clearCalls).toContainEqual({ threadId: "thread_provider_result_error", reason: "result_error" });
2167
2168
  });
2169
+ it("never shows the marketer a model provider's billing error, as a reply or as the turn's error", async () => {
2170
+ const providerError = "API Error: 402 Insufficient credits. Add more using https://openrouter.ai/settings/credits";
2171
+ const relay = recordingRelay();
2172
+ const executor = createExecutor({
2173
+ relay,
2174
+ queryResults: [
2175
+ scriptedMessages(apiErrorMessage("assistant_402", providerError, "unknown"), {
2176
+ type: "result",
2177
+ subtype: "success",
2178
+ is_error: true,
2179
+ result: providerError,
2180
+ api_error_status: 402,
2181
+ total_cost_usd: 0,
2182
+ session_id: "session_402",
2183
+ }),
2184
+ ],
2185
+ });
2186
+ const outcome = await executor.execute({ threadId: "thread_402", turnId: "turn_402", prompt: "Build the creatives", history: history([]) }, activeControls());
2187
+ const shown = JSON.stringify([relay.assistantEvents, outcome.kind === "errored" ? outcome.errors : []]);
2188
+ expect(outcome.kind).toBe("errored");
2189
+ expect(shown).not.toMatch(/API Error|openrouter|402/i);
2190
+ });
2191
+ it("never shows the marketer the provider refusing Baker's own model key", async () => {
2192
+ const executor = createExecutor({
2193
+ queryResults: [
2194
+ scriptedMessages({
2195
+ type: "result",
2196
+ subtype: "success",
2197
+ is_error: true,
2198
+ result: 'API Error: 401 {"error":{"message":"User not found.","code":401}}',
2199
+ api_error_status: 401,
2200
+ total_cost_usd: 0,
2201
+ session_id: "session_revoked_key",
2202
+ }),
2203
+ ],
2204
+ });
2205
+ const outcome = await executor.execute({ threadId: "thread_revoked_key", turnId: "turn_revoked_key", prompt: "Write headlines", history: history([]) }, activeControls());
2206
+ expect(outcome.kind === "errored" ? outcome.errors : []).toEqual([expect.stringContaining("on our side")]);
2207
+ });
2208
+ it("tells the marketer how to fix an image the provider could not read, not to send the same message again", async () => {
2209
+ const imageError = 'API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Could not process image"}}';
2210
+ const executor = createExecutor({
2211
+ queryResults: [
2212
+ scriptedMessages({
2213
+ type: "result",
2214
+ subtype: "success",
2215
+ is_error: true,
2216
+ result: imageError,
2217
+ api_error_status: 400,
2218
+ total_cost_usd: 0,
2219
+ session_id: "session_bad_image",
2220
+ }),
2221
+ ],
2222
+ });
2223
+ const outcome = await executor.execute({ threadId: "thread_bad_image", turnId: "turn_bad_image", prompt: "Use this photo", history: history([]) }, activeControls());
2224
+ expect(outcome.kind === "errored" ? outcome.errors : []).toEqual([
2225
+ expect.stringContaining("extension lies about its format"),
2226
+ ]);
2227
+ });
2228
+ it("keeps a server error the turn recovered from out of the chat, and in the turn events", async () => {
2229
+ const providerError = "API Error: Server error mid-response. The response above may be incomplete.";
2230
+ const relay = recordingRelay();
2231
+ const turnEvents = [];
2232
+ const executor = createExecutor({
2233
+ relay,
2234
+ turnEvents,
2235
+ queryResults: [
2236
+ scriptedMessages(apiErrorMessage("assistant_server_error", providerError, "server_error"), assistantMessage("assistant_recovered", "Your three headlines are ready.", "session_recovered"), resultMessage(0.3, "session_recovered")),
2237
+ ],
2238
+ });
2239
+ const outcome = await executor.execute({ threadId: "thread_recovered", turnId: "turn_recovered", prompt: "Write headlines", history: history([]) }, activeControls());
2240
+ expect(outcome.kind).toBe("completed");
2241
+ expect(JSON.stringify(relay.assistantEvents)).not.toContain("API Error");
2242
+ expect(JSON.stringify(relay.assistantEvents)).toContain("Your three headlines are ready.");
2243
+ expect(turnEvents).toContainEqual(expect.objectContaining({ event: "api_error", detail: { error: "server_error", text: providerError } }));
2244
+ });
2168
2245
  it("closes SDK queries on error and inactive exits but not on natural clean completion", async () => {
2169
2246
  const naturalCloseCalls = [];
2170
2247
  const naturalExecutor = createExecutor({