@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.
- package/README.md +21 -29
- package/dist/hono/agent-session.d.ts +0 -1
- package/dist/hono/agent-session.d.ts.map +1 -1
- package/dist/hono/agent-session.js +0 -7
- package/dist/hono/agent-session.js.map +1 -1
- package/dist/hono/agent-session.test.js +1 -7
- package/dist/hono/agent-session.test.js.map +1 -1
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +2 -15
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +7 -19
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +113 -27
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +153 -76
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/served-model.d.ts +23 -0
- package/dist/hono/served-model.d.ts.map +1 -0
- package/dist/hono/served-model.js +82 -0
- package/dist/hono/served-model.js.map +1 -0
- package/dist/hono/served-model.test.d.ts +2 -0
- package/dist/hono/served-model.test.d.ts.map +1 -0
- package/dist/hono/served-model.test.js +29 -0
- package/dist/hono/served-model.test.js.map +1 -0
- package/dist/hono/session-continuity.d.ts +24 -0
- package/dist/hono/session-continuity.d.ts.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +200 -5
- package/dist/hono/session-continuity.integration.test.js.map +1 -1
- package/dist/hono/session-continuity.js +94 -7
- package/dist/hono/session-continuity.js.map +1 -1
- package/dist/hono/session-store.d.ts +2 -0
- package/dist/hono/session-store.d.ts.map +1 -1
- package/dist/hono/session-store.js +6 -0
- package/dist/hono/session-store.js.map +1 -1
- package/dist/hono/session-usage-baseline.d.ts +1 -0
- package/dist/hono/session-usage-baseline.d.ts.map +1 -1
- package/dist/hono/session-usage-baseline.js +17 -4
- package/dist/hono/session-usage-baseline.js.map +1 -1
- package/dist/hono/turn-events.d.ts +1 -1
- package/dist/hono/turn-events.d.ts.map +1 -1
- package/dist/hono/turn-events.js.map +1 -1
- package/dist/hono/types.d.ts +0 -6
- package/dist/hono/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/hono/cli-sync.d.ts +0 -8
- package/dist/hono/cli-sync.d.ts.map +0 -1
- package/dist/hono/cli-sync.js +0 -93
- package/dist/hono/cli-sync.js.map +0 -1
- package/dist/hono/cli-sync.test.d.ts +0 -2
- package/dist/hono/cli-sync.test.d.ts.map +0 -1
- package/dist/hono/cli-sync.test.js +0 -81
- package/dist/hono/cli-sync.test.js.map +0 -1
- package/dist/hono/main-sync.d.ts +0 -19
- package/dist/hono/main-sync.d.ts.map +0 -1
- package/dist/hono/main-sync.js +0 -147
- package/dist/hono/main-sync.js.map +0 -1
- package/dist/hono/main-sync.test.d.ts +0 -2
- package/dist/hono/main-sync.test.d.ts.map +0 -1
- package/dist/hono/main-sync.test.js +0 -124
- 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("
|
|
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: ["
|
|
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({
|