@koda-sl/baker-bridge 0.152.0-dev.1a19b1876 → 0.153.0-dev.ad2740332
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 +41 -7
- 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 +7 -9
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +17 -1
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +158 -20
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +250 -2
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/convex.d.ts +2 -1
- package/dist/hono/convex.d.ts.map +1 -1
- package/dist/hono/convex.js +4 -0
- package/dist/hono/convex.js.map +1 -1
- package/dist/hono/main-sync.d.ts +7 -0
- package/dist/hono/main-sync.d.ts.map +1 -1
- package/dist/hono/main-sync.js +57 -5
- package/dist/hono/main-sync.js.map +1 -1
- package/dist/hono/main-sync.test.js +41 -1
- package/dist/hono/main-sync.test.js.map +1 -1
- package/dist/hono/relay.d.ts +10 -1
- package/dist/hono/relay.d.ts.map +1 -1
- package/dist/hono/relay.js +4 -0
- package/dist/hono/relay.js.map +1 -1
- package/dist/hono/relay.test.js +11 -0
- package/dist/hono/relay.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 +30 -0
- package/dist/hono/session-continuity.d.ts.map +1 -1
- package/dist/hono/session-continuity.integration.test.js +201 -5
- package/dist/hono/session-continuity.integration.test.js.map +1 -1
- package/dist/hono/session-continuity.js +99 -8
- 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/review/digest.d.ts +35 -0
- package/dist/review/digest.d.ts.map +1 -1
- package/dist/review/digest.js +171 -27
- package/dist/review/digest.js.map +1 -1
- package/dist/review/digest.test.js +130 -1
- package/dist/review/digest.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -69,6 +69,7 @@ function recordingRelay() {
|
|
|
69
69
|
const compactionUpdates = [];
|
|
70
70
|
const reviewDigests = [];
|
|
71
71
|
const retractedEvents = [];
|
|
72
|
+
const toolOutcomeBatches = [];
|
|
72
73
|
const contextUsageUpdates = [];
|
|
73
74
|
return {
|
|
74
75
|
assistantEvents,
|
|
@@ -78,6 +79,7 @@ function recordingRelay() {
|
|
|
78
79
|
contextUsageUpdates,
|
|
79
80
|
reviewDigests,
|
|
80
81
|
retractedEvents,
|
|
82
|
+
toolOutcomeBatches,
|
|
81
83
|
startPreview: () => undefined,
|
|
82
84
|
previewText: () => undefined,
|
|
83
85
|
previewTools: () => undefined,
|
|
@@ -99,6 +101,10 @@ function recordingRelay() {
|
|
|
99
101
|
retractedEvents.push({ target, eventIds });
|
|
100
102
|
return Promise.resolve();
|
|
101
103
|
},
|
|
104
|
+
toolOutcomes: (_target, outcomes) => {
|
|
105
|
+
toolOutcomeBatches.push(outcomes);
|
|
106
|
+
return Promise.resolve();
|
|
107
|
+
},
|
|
102
108
|
complete: () => Promise.resolve(),
|
|
103
109
|
inputRequest: () => Promise.resolve(true),
|
|
104
110
|
inputResolved: () => Promise.resolve(),
|
|
@@ -243,6 +249,7 @@ function createExecutor(options) {
|
|
|
243
249
|
...(options.inFlightTurns ? { inFlightTurns: options.inFlightTurns } : {}),
|
|
244
250
|
...(options.catchUpWithMain ? { catchUpWithMain: options.catchUpWithMain } : {}),
|
|
245
251
|
...(options.keepCliInStep ? { keepCliInStep: options.keepCliInStep } : {}),
|
|
252
|
+
...(options.holdsNoWork ? { holdsNoWork: options.holdsNoWork } : {}),
|
|
246
253
|
});
|
|
247
254
|
}
|
|
248
255
|
function memoryInFlightTurns(initial) {
|
|
@@ -262,6 +269,24 @@ function memoryInFlightTurns(initial) {
|
|
|
262
269
|
},
|
|
263
270
|
};
|
|
264
271
|
}
|
|
272
|
+
// The CLI's own report of a failed model request, written as an assistant
|
|
273
|
+
// message — the shape production stores (`model: "<synthetic>"`, `error`).
|
|
274
|
+
function apiErrorMessage(uuid, text, error) {
|
|
275
|
+
return {
|
|
276
|
+
type: "assistant",
|
|
277
|
+
uuid,
|
|
278
|
+
session_id: "session_api_error",
|
|
279
|
+
parent_tool_use_id: null,
|
|
280
|
+
error,
|
|
281
|
+
is_api_error_message: true,
|
|
282
|
+
message: {
|
|
283
|
+
role: "assistant",
|
|
284
|
+
model: "<synthetic>",
|
|
285
|
+
content: [{ type: "text", text }],
|
|
286
|
+
stop_reason: "stop_sequence",
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
}
|
|
265
290
|
function resultMessage(costUsd, sessionId, modelUsage) {
|
|
266
291
|
return {
|
|
267
292
|
type: "result",
|
|
@@ -939,6 +964,40 @@ describe("BridgeJobExecutor", () => {
|
|
|
939
964
|
],
|
|
940
965
|
});
|
|
941
966
|
});
|
|
967
|
+
// OpenRouter answers under its own slug. A CLI resuming a transcript that names
|
|
968
|
+
// the slug drops every earlier thinking block from its first request, which
|
|
969
|
+
// breaks the prompt cache right after the system prompt (BAKER-331).
|
|
970
|
+
it("hands the CLI a transcript that names the model the conversation asked for", async () => {
|
|
971
|
+
const configDir = await mkdtemp(join(tmpdir(), "bridge-served-model-"));
|
|
972
|
+
try {
|
|
973
|
+
const transcriptPath = join(configDir, "projects", "-home-user-repo", "session_resume.jsonl");
|
|
974
|
+
await mkdir(join(configDir, "projects", "-home-user-repo"), { recursive: true });
|
|
975
|
+
await writeFile(transcriptPath, `${JSON.stringify({
|
|
976
|
+
type: "assistant",
|
|
977
|
+
requestedModel: "claude-opus-5-5",
|
|
978
|
+
message: { id: "msg_1", model: "anthropic/claude-opus-5.5", content: [{ type: "thinking", thinking: "t" }] },
|
|
979
|
+
})}\n`);
|
|
980
|
+
vi.stubEnv("CLAUDE_CONFIG_DIR", configDir);
|
|
981
|
+
const resumedFrom = [];
|
|
982
|
+
const executor = createExecutor({
|
|
983
|
+
loadedBoundary: cleanBoundary("session_resume"),
|
|
984
|
+
queryResults: [
|
|
985
|
+
{
|
|
986
|
+
async *[Symbol.asyncIterator]() {
|
|
987
|
+
await Promise.resolve();
|
|
988
|
+
resumedFrom.push(readFileSync(transcriptPath, "utf8"));
|
|
989
|
+
yield resultMessage(0.2, "session_next");
|
|
990
|
+
},
|
|
991
|
+
},
|
|
992
|
+
],
|
|
993
|
+
});
|
|
994
|
+
await executor.execute({ threadId: "thread_resume", turnId: "turn_resume", prompt: "Continue", history: history([]) }, activeControls());
|
|
995
|
+
expect(JSON.parse(resumedFrom[0] ?? "{}").message.model).toBe("claude-opus-5-5");
|
|
996
|
+
}
|
|
997
|
+
finally {
|
|
998
|
+
await rm(configDir, { recursive: true, force: true });
|
|
999
|
+
}
|
|
1000
|
+
});
|
|
942
1001
|
it("publishes without the conversation, and without summarizing it first", async () => {
|
|
943
1002
|
const queryInputs = [];
|
|
944
1003
|
const savedBoundaries = [];
|
|
@@ -1739,6 +1798,28 @@ describe("BridgeJobExecutor", () => {
|
|
|
1739
1798
|
expect(queryInputs).toHaveLength(0);
|
|
1740
1799
|
expect(told).toBe(0);
|
|
1741
1800
|
});
|
|
1801
|
+
it("keeps the note for the next turn when the user stops this one before its prompt goes out", async () => {
|
|
1802
|
+
const queryInputs = [];
|
|
1803
|
+
const controls = activeControls();
|
|
1804
|
+
let told = 0;
|
|
1805
|
+
const executor = createExecutor({
|
|
1806
|
+
queryInputs,
|
|
1807
|
+
mcpServersProvider: () => {
|
|
1808
|
+
controls.deactivate();
|
|
1809
|
+
return Promise.resolve(undefined);
|
|
1810
|
+
},
|
|
1811
|
+
catchUpWithMain: () => Promise.resolve({
|
|
1812
|
+
inWorkspace: true,
|
|
1813
|
+
changed: ["src/pages/home/hero.md"],
|
|
1814
|
+
told: () => {
|
|
1815
|
+
told += 1;
|
|
1816
|
+
return Promise.resolve();
|
|
1817
|
+
},
|
|
1818
|
+
}),
|
|
1819
|
+
});
|
|
1820
|
+
await executor.execute({ threadId: "thread_stopped_late", turnId: "turn_stopped_late", prompt: "Cambia el hero", history: history([]) }, controls);
|
|
1821
|
+
expect(told).toBe(0);
|
|
1822
|
+
});
|
|
1742
1823
|
it("leaves the workspace of a finished Session alone while it is reviewed", async () => {
|
|
1743
1824
|
let caughtUp = false;
|
|
1744
1825
|
const executor = createExecutor({
|
|
@@ -1891,6 +1972,36 @@ describe("BridgeJobExecutor", () => {
|
|
|
1891
1972
|
expect(queryInputs[0]?.options).toMatchObject({ resume: "session_interrupted", forkSession: true });
|
|
1892
1973
|
expect(relay.contextSources.map((entry) => entry.source)).toEqual(["interrupted_resume"]);
|
|
1893
1974
|
});
|
|
1975
|
+
it("tells a turn sent again after a restart what another chat published while it was down", async () => {
|
|
1976
|
+
const queryInputs = [];
|
|
1977
|
+
let told = 0;
|
|
1978
|
+
const executor = createExecutor({
|
|
1979
|
+
loadedBoundary: cleanBoundary("session_clean"),
|
|
1980
|
+
queryInputs,
|
|
1981
|
+
inFlightTurns: memoryInFlightTurns({
|
|
1982
|
+
version: 1,
|
|
1983
|
+
turnId: "turn_interrupted_sync",
|
|
1984
|
+
sessionId: "session_interrupted",
|
|
1985
|
+
updatedAtMs: 1,
|
|
1986
|
+
}),
|
|
1987
|
+
catchUpWithMain: () => Promise.resolve({
|
|
1988
|
+
inWorkspace: false,
|
|
1989
|
+
changed: ["src/pages/home/hero.md"],
|
|
1990
|
+
told: () => {
|
|
1991
|
+
told += 1;
|
|
1992
|
+
return Promise.resolve();
|
|
1993
|
+
},
|
|
1994
|
+
}),
|
|
1995
|
+
});
|
|
1996
|
+
await executor.execute({
|
|
1997
|
+
threadId: "thread_interrupted_sync",
|
|
1998
|
+
turnId: "turn_interrupted_sync",
|
|
1999
|
+
prompt: "Rebuild the five pages",
|
|
2000
|
+
}, activeControls());
|
|
2001
|
+
expect(queryInputs[0]?.prompt).toBe(INTERRUPTED_RESUME_PROMPT);
|
|
2002
|
+
expect(queryInputs[0]?.reminder).toContain("- src/pages/home/hero.md");
|
|
2003
|
+
expect(told).toBe(1);
|
|
2004
|
+
});
|
|
1894
2005
|
it("runs the turn the ordinary way when the interrupted session cannot be resumed", async () => {
|
|
1895
2006
|
const relay = recordingRelay();
|
|
1896
2007
|
const queryInputs = [];
|
|
@@ -1919,6 +2030,39 @@ describe("BridgeJobExecutor", () => {
|
|
|
1919
2030
|
expect(clearCalls).toEqual([]);
|
|
1920
2031
|
expect(relay.contextSources.map((entry) => entry.source)).toEqual(["interrupted_resume", "local_resume"]);
|
|
1921
2032
|
});
|
|
2033
|
+
it("keeps the chat's conversation when an interrupted publish that cannot be resumed is run again", async () => {
|
|
2034
|
+
const executor = createExecutor({
|
|
2035
|
+
loadedBoundary: cleanBoundary("session_clean"),
|
|
2036
|
+
inFlightTurns: memoryInFlightTurns({
|
|
2037
|
+
version: 1,
|
|
2038
|
+
turnId: "turn_publish",
|
|
2039
|
+
sessionId: "session_never_written",
|
|
2040
|
+
updatedAtMs: 1,
|
|
2041
|
+
}),
|
|
2042
|
+
queryResults: [
|
|
2043
|
+
scriptedError(new Error("No conversation found with session ID: session_never_written")),
|
|
2044
|
+
scriptedMessages(assistantMessage("assistant_published", "Published", "session_publish_only"), resultMessage(0.2, "session_publish_only")),
|
|
2045
|
+
],
|
|
2046
|
+
});
|
|
2047
|
+
const outcome = await executor.execute({ threadId: "thread_publish", turnId: "turn_publish", prompt: "/publish" }, activeControls());
|
|
2048
|
+
expect(outcome).toMatchObject({ kind: "completed", cleanBoundary: undefined });
|
|
2049
|
+
});
|
|
2050
|
+
it("tells Convex a turn that left the workspace with no work of its own", async () => {
|
|
2051
|
+
const executor = createExecutor({ holdsNoWork: async () => true });
|
|
2052
|
+
const outcome = await executor.execute({ threadId: "thread_run", turnId: "turn_run", prompt: "Post the daily report to Slack. Change nothing." }, activeControls());
|
|
2053
|
+
expect(outcome).toMatchObject({ kind: "completed", workspaceUnchanged: true });
|
|
2054
|
+
});
|
|
2055
|
+
it("claims nothing about the workspace after a publish turn or one that left work", async () => {
|
|
2056
|
+
const publish = createExecutor({
|
|
2057
|
+
holdsNoWork: async () => true,
|
|
2058
|
+
queryResults: [scriptedMessages(resultMessage(0.1, "session_publish_only"))],
|
|
2059
|
+
});
|
|
2060
|
+
const edited = createExecutor({ holdsNoWork: async () => false });
|
|
2061
|
+
const published = await publish.execute({ threadId: "thread_publish", turnId: "turn_publish", prompt: "/publish" }, activeControls());
|
|
2062
|
+
const worked = await edited.execute({ threadId: "thread_run", turnId: "turn_run", prompt: "Rewrite the hero" }, activeControls());
|
|
2063
|
+
expect(published).not.toHaveProperty("workspaceUnchanged");
|
|
2064
|
+
expect(worked).not.toHaveProperty("workspaceUnchanged");
|
|
2065
|
+
});
|
|
1922
2066
|
it("keeps the running turn's session on record until the turn ends", async () => {
|
|
1923
2067
|
const recorded = [];
|
|
1924
2068
|
const inFlightTurns = memoryInFlightTurns();
|
|
@@ -2199,7 +2343,7 @@ describe("BridgeJobExecutor", () => {
|
|
|
2199
2343
|
expect(savedBoundaries).toEqual([]);
|
|
2200
2344
|
expect(clearCalls).toContainEqual({ threadId: "thread_result_error", reason: "result_error" });
|
|
2201
2345
|
});
|
|
2202
|
-
it("
|
|
2346
|
+
it("reports a provider's server error in product language, not its status line", async () => {
|
|
2203
2347
|
const clearCalls = [];
|
|
2204
2348
|
const executor = createExecutor({
|
|
2205
2349
|
clearCalls,
|
|
@@ -2225,10 +2369,86 @@ describe("BridgeJobExecutor", () => {
|
|
|
2225
2369
|
kind: "errored",
|
|
2226
2370
|
costUsd: 0.17,
|
|
2227
2371
|
model: "claude-test",
|
|
2228
|
-
errors: ["
|
|
2372
|
+
errors: [expect.stringContaining("on our side")],
|
|
2229
2373
|
});
|
|
2230
2374
|
expect(clearCalls).toContainEqual({ threadId: "thread_provider_result_error", reason: "result_error" });
|
|
2231
2375
|
});
|
|
2376
|
+
it("never shows the marketer a model provider's billing error, as a reply or as the turn's error", async () => {
|
|
2377
|
+
const providerError = "API Error: 402 Insufficient credits. Add more using https://openrouter.ai/settings/credits";
|
|
2378
|
+
const relay = recordingRelay();
|
|
2379
|
+
const executor = createExecutor({
|
|
2380
|
+
relay,
|
|
2381
|
+
queryResults: [
|
|
2382
|
+
scriptedMessages(apiErrorMessage("assistant_402", providerError, "unknown"), {
|
|
2383
|
+
type: "result",
|
|
2384
|
+
subtype: "success",
|
|
2385
|
+
is_error: true,
|
|
2386
|
+
result: providerError,
|
|
2387
|
+
api_error_status: 402,
|
|
2388
|
+
total_cost_usd: 0,
|
|
2389
|
+
session_id: "session_402",
|
|
2390
|
+
}),
|
|
2391
|
+
],
|
|
2392
|
+
});
|
|
2393
|
+
const outcome = await executor.execute({ threadId: "thread_402", turnId: "turn_402", prompt: "Build the creatives", history: history([]) }, activeControls());
|
|
2394
|
+
const shown = JSON.stringify([relay.assistantEvents, outcome.kind === "errored" ? outcome.errors : []]);
|
|
2395
|
+
expect(outcome.kind).toBe("errored");
|
|
2396
|
+
expect(shown).not.toMatch(/API Error|openrouter|402/i);
|
|
2397
|
+
});
|
|
2398
|
+
it("never shows the marketer the provider refusing Baker's own model key", async () => {
|
|
2399
|
+
const executor = createExecutor({
|
|
2400
|
+
queryResults: [
|
|
2401
|
+
scriptedMessages({
|
|
2402
|
+
type: "result",
|
|
2403
|
+
subtype: "success",
|
|
2404
|
+
is_error: true,
|
|
2405
|
+
result: 'API Error: 401 {"error":{"message":"User not found.","code":401}}',
|
|
2406
|
+
api_error_status: 401,
|
|
2407
|
+
total_cost_usd: 0,
|
|
2408
|
+
session_id: "session_revoked_key",
|
|
2409
|
+
}),
|
|
2410
|
+
],
|
|
2411
|
+
});
|
|
2412
|
+
const outcome = await executor.execute({ threadId: "thread_revoked_key", turnId: "turn_revoked_key", prompt: "Write headlines", history: history([]) }, activeControls());
|
|
2413
|
+
expect(outcome.kind === "errored" ? outcome.errors : []).toEqual([expect.stringContaining("on our side")]);
|
|
2414
|
+
});
|
|
2415
|
+
it("tells the marketer how to fix an image the provider could not read, not to send the same message again", async () => {
|
|
2416
|
+
const imageError = 'API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Could not process image"}}';
|
|
2417
|
+
const executor = createExecutor({
|
|
2418
|
+
queryResults: [
|
|
2419
|
+
scriptedMessages({
|
|
2420
|
+
type: "result",
|
|
2421
|
+
subtype: "success",
|
|
2422
|
+
is_error: true,
|
|
2423
|
+
result: imageError,
|
|
2424
|
+
api_error_status: 400,
|
|
2425
|
+
total_cost_usd: 0,
|
|
2426
|
+
session_id: "session_bad_image",
|
|
2427
|
+
}),
|
|
2428
|
+
],
|
|
2429
|
+
});
|
|
2430
|
+
const outcome = await executor.execute({ threadId: "thread_bad_image", turnId: "turn_bad_image", prompt: "Use this photo", history: history([]) }, activeControls());
|
|
2431
|
+
expect(outcome.kind === "errored" ? outcome.errors : []).toEqual([
|
|
2432
|
+
expect.stringContaining("extension lies about its format"),
|
|
2433
|
+
]);
|
|
2434
|
+
});
|
|
2435
|
+
it("keeps a server error the turn recovered from out of the chat, and in the turn events", async () => {
|
|
2436
|
+
const providerError = "API Error: Server error mid-response. The response above may be incomplete.";
|
|
2437
|
+
const relay = recordingRelay();
|
|
2438
|
+
const turnEvents = [];
|
|
2439
|
+
const executor = createExecutor({
|
|
2440
|
+
relay,
|
|
2441
|
+
turnEvents,
|
|
2442
|
+
queryResults: [
|
|
2443
|
+
scriptedMessages(apiErrorMessage("assistant_server_error", providerError, "server_error"), assistantMessage("assistant_recovered", "Your three headlines are ready.", "session_recovered"), resultMessage(0.3, "session_recovered")),
|
|
2444
|
+
],
|
|
2445
|
+
});
|
|
2446
|
+
const outcome = await executor.execute({ threadId: "thread_recovered", turnId: "turn_recovered", prompt: "Write headlines", history: history([]) }, activeControls());
|
|
2447
|
+
expect(outcome.kind).toBe("completed");
|
|
2448
|
+
expect(JSON.stringify(relay.assistantEvents)).not.toContain("API Error");
|
|
2449
|
+
expect(JSON.stringify(relay.assistantEvents)).toContain("Your three headlines are ready.");
|
|
2450
|
+
expect(turnEvents).toContainEqual(expect.objectContaining({ event: "api_error", detail: { error: "server_error", text: providerError } }));
|
|
2451
|
+
});
|
|
2232
2452
|
it("closes SDK queries on error and inactive exits but not on natural clean completion", async () => {
|
|
2233
2453
|
const naturalCloseCalls = [];
|
|
2234
2454
|
const naturalExecutor = createExecutor({
|
|
@@ -4495,6 +4715,34 @@ describe("BridgeJobExecutor model refusals", () => {
|
|
|
4495
4715
|
errors: ["I couldn't help with that request. Try rephrasing what you need."],
|
|
4496
4716
|
});
|
|
4497
4717
|
});
|
|
4718
|
+
// Tool results come back in user events, which are not stored as messages, so
|
|
4719
|
+
// their outcome is relayed on its own: a Session rebuilt from history needs it.
|
|
4720
|
+
it("relays how each tool call came out, bounded", async () => {
|
|
4721
|
+
const relay = recordingRelay();
|
|
4722
|
+
const toolResult = {
|
|
4723
|
+
type: "user",
|
|
4724
|
+
message: {
|
|
4725
|
+
role: "user",
|
|
4726
|
+
content: [
|
|
4727
|
+
{ type: "tool_result", tool_use_id: "toolu_1", is_error: true, content: "Meta refused the rule" },
|
|
4728
|
+
{ type: "tool_result", tool_use_id: "toolu_2", content: [{ type: "text", text: "x".repeat(5000) }] },
|
|
4729
|
+
],
|
|
4730
|
+
},
|
|
4731
|
+
parent_tool_use_id: null,
|
|
4732
|
+
session_id: "session_tools",
|
|
4733
|
+
};
|
|
4734
|
+
const executor = createExecutor({
|
|
4735
|
+
relay,
|
|
4736
|
+
queryResults: [scriptedMessages(toolResult, resultMessage(0.01, "session_tools"))],
|
|
4737
|
+
});
|
|
4738
|
+
await executor.execute({ threadId: "thread_tools", turnId: "turn_tools", prompt: "Hi", history: history([]) }, activeControls());
|
|
4739
|
+
expect(relay.toolOutcomeBatches).toEqual([
|
|
4740
|
+
[
|
|
4741
|
+
{ toolUseId: "toolu_1", isError: true, text: "Meta refused the rule" },
|
|
4742
|
+
{ toolUseId: "toolu_2", isError: false, text: "x".repeat(2000) },
|
|
4743
|
+
],
|
|
4744
|
+
]);
|
|
4745
|
+
});
|
|
4498
4746
|
// A fallback model retried the refused request. What the refused leg already
|
|
4499
4747
|
// posted is superseded, so it leaves the chat instead of sitting above the
|
|
4500
4748
|
// answer that replaced it.
|