@koda-sl/baker-bridge 0.161.0 → 0.163.0
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 +15 -8
- package/dist/hono/agent-session.d.ts +1 -0
- package/dist/hono/agent-session.d.ts.map +1 -1
- package/dist/hono/agent-session.js +1 -1
- package/dist/hono/agent-session.js.map +1 -1
- package/dist/hono/agent-session.test.js +1 -1
- package/dist/hono/agent-session.test.js.map +1 -1
- package/dist/hono/agent.d.ts +1 -1
- package/dist/hono/agent.d.ts.map +1 -1
- package/dist/hono/agent.js +2 -2
- package/dist/hono/agent.js.map +1 -1
- package/dist/hono/bridge-job.d.ts +8 -1
- package/dist/hono/bridge-job.d.ts.map +1 -1
- package/dist/hono/bridge-job.js +47 -5
- package/dist/hono/bridge-job.js.map +1 -1
- package/dist/hono/bridge-job.test.js +134 -5
- package/dist/hono/bridge-job.test.js.map +1 -1
- package/dist/hono/jev.d.ts +4 -1
- package/dist/hono/jev.d.ts.map +1 -1
- package/dist/hono/jev.js +5 -2
- package/dist/hono/jev.js.map +1 -1
- package/dist/hono/jev.test.js +2 -1
- package/dist/hono/jev.test.js.map +1 -1
- package/dist/hono/openrouter-attribution.d.ts +31 -0
- package/dist/hono/openrouter-attribution.d.ts.map +1 -0
- package/dist/hono/openrouter-attribution.js +70 -0
- package/dist/hono/openrouter-attribution.js.map +1 -0
- package/dist/hono/openrouter-attribution.test.d.ts +2 -0
- package/dist/hono/openrouter-attribution.test.d.ts.map +1 -0
- package/dist/hono/openrouter-attribution.test.js +48 -0
- package/dist/hono/openrouter-attribution.test.js.map +1 -0
- package/dist/hono/openrouter-session.d.ts +11 -5
- package/dist/hono/openrouter-session.d.ts.map +1 -1
- package/dist/hono/openrouter-session.js +9 -5
- package/dist/hono/openrouter-session.js.map +1 -1
- package/dist/hono/openrouter-session.test.js +9 -0
- package/dist/hono/openrouter-session.test.js.map +1 -1
- package/dist/hono/server.d.ts.map +1 -1
- package/dist/hono/server.js +3 -1
- package/dist/hono/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -18,7 +18,7 @@ vi.mock("./runtime-files.ts", async (importOriginal) => {
|
|
|
18
18
|
});
|
|
19
19
|
import { AVATAR_CASTING_TOOL_NAME } from "./avatar-casting.js";
|
|
20
20
|
import { FACE_PICK_QUESTION } from "./avatar-face-question.js";
|
|
21
|
-
import { BridgeJobExecutor, createStallDetector, isContextOverflowRefusal, isControlChannelDeny, parsePlanDecision, remapAnswersToQuestionText, } from "./bridge-job.js";
|
|
21
|
+
import { agentFeature, BridgeJobExecutor, createStallDetector, isContextOverflowRefusal, isControlChannelDeny, parsePlanDecision, remapAnswersToQuestionText, } from "./bridge-job.js";
|
|
22
22
|
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";
|
|
@@ -1244,6 +1244,9 @@ describe("BridgeJobExecutor", () => {
|
|
|
1244
1244
|
});
|
|
1245
1245
|
it("propagates the inherited process env (OpenRouter routing) to the agent subprocess", async () => {
|
|
1246
1246
|
vi.stubEnv("ANTHROPIC_AUTH_TOKEN", "sk-or-test-key");
|
|
1247
|
+
vi.stubEnv("BAKER_ENVIRONMENT", "production");
|
|
1248
|
+
vi.stubEnv("BAKER_RUNTIME_COMPANY_ID", "company_env");
|
|
1249
|
+
vi.stubEnv("BAKER_CHAT_ID", "chat_env");
|
|
1247
1250
|
const queryInputs = [];
|
|
1248
1251
|
const executor = createExecutor({
|
|
1249
1252
|
queryInputs,
|
|
@@ -1265,8 +1268,25 @@ describe("BridgeJobExecutor", () => {
|
|
|
1265
1268
|
allow_fallbacks: true,
|
|
1266
1269
|
},
|
|
1267
1270
|
session_id: "thread_env",
|
|
1271
|
+
user: "agent.turn | company:company_env | chat:chat_env",
|
|
1272
|
+
trace: {
|
|
1273
|
+
trace_name: "agent.turn",
|
|
1274
|
+
generation_name: "agent.turn",
|
|
1275
|
+
feature: "agent.turn",
|
|
1276
|
+
service: "agent",
|
|
1277
|
+
environment: "production",
|
|
1278
|
+
company_id: "company_env",
|
|
1279
|
+
chat_id: "chat_env",
|
|
1280
|
+
},
|
|
1268
1281
|
plugins: [{ id: "web", enabled: false }],
|
|
1269
1282
|
});
|
|
1283
|
+
// The same tag as headers, which is what names the app in OpenRouter.
|
|
1284
|
+
expect(env?.ANTHROPIC_CUSTOM_HEADERS).toBe([
|
|
1285
|
+
"HTTP-Referer: https://agent.production.withbaker.com",
|
|
1286
|
+
"X-OpenRouter-Title: Baker agent (production)",
|
|
1287
|
+
"X-Title: Baker agent (production)",
|
|
1288
|
+
"x-session-id: thread_env",
|
|
1289
|
+
].join("\n"));
|
|
1270
1290
|
});
|
|
1271
1291
|
// OpenRouter has no Bedrock endpoint for a per-message effort, so while the CLI
|
|
1272
1292
|
// sends one every Opus 5.5 request skips Bedrock and bills OpenRouter credits.
|
|
@@ -2532,9 +2552,9 @@ describe("BridgeJobExecutor", () => {
|
|
|
2532
2552
|
});
|
|
2533
2553
|
// BAKER-1025 (AskUserQuestion), the request_tag_input sighting, and the ExitPlanMode and
|
|
2534
2554
|
// request_connection sightings: each held the turn ~900 s before the same deny, although
|
|
2535
|
-
// the card stays up and a later answer resumes the chat as a new turn anyway.
|
|
2555
|
+
// the card stays up and a later answer resumes the chat as a new turn anyway. A question
|
|
2556
|
+
// card is the exception since it picks for the user (BAKER-3550): see the test below.
|
|
2536
2557
|
it.each([
|
|
2537
|
-
{ toolName: "AskUserQuestion", input: { questions: [{ question: "Which budget?" }] }, planMode: false },
|
|
2538
2558
|
{ toolName: "ExitPlanMode", input: { plan: "Draft flow" }, planMode: true },
|
|
2539
2559
|
{
|
|
2540
2560
|
toolName: "mcp__baker_ui__request_tag_input",
|
|
@@ -2592,10 +2612,15 @@ describe("BridgeJobExecutor", () => {
|
|
|
2592
2612
|
return Promise.resolve();
|
|
2593
2613
|
};
|
|
2594
2614
|
let guardResult;
|
|
2615
|
+
let reachedTool = () => undefined;
|
|
2616
|
+
const toolReached = new Promise((resolve) => {
|
|
2617
|
+
reachedTool = resolve;
|
|
2618
|
+
});
|
|
2595
2619
|
const executor = new BridgeJobExecutor({
|
|
2596
2620
|
sdk: {
|
|
2597
2621
|
query: (input) => ({
|
|
2598
2622
|
async *[Symbol.asyncIterator]() {
|
|
2623
|
+
reachedTool();
|
|
2599
2624
|
guardResult = await input.options.canUseTool?.("AskUserQuestion", { questions: [{ label: "Confirm?" }] }, {
|
|
2600
2625
|
toolUseID: "tool_1",
|
|
2601
2626
|
requestId: "req_1",
|
|
@@ -2612,10 +2637,20 @@ describe("BridgeJobExecutor", () => {
|
|
|
2612
2637
|
clear: async () => undefined,
|
|
2613
2638
|
},
|
|
2614
2639
|
relay,
|
|
2615
|
-
askUserQuestionTimeoutMs: 1,
|
|
2616
2640
|
});
|
|
2617
2641
|
const controls = activeControls();
|
|
2618
|
-
|
|
2642
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] });
|
|
2643
|
+
let outcome;
|
|
2644
|
+
try {
|
|
2645
|
+
const running = executor.execute({ threadId: "thread_question_timeout", turnId: "turn_question_timeout", prompt: "Ask first" }, controls);
|
|
2646
|
+
await toolReached;
|
|
2647
|
+
await vi.advanceTimersByTimeAsync(120_000);
|
|
2648
|
+
await vi.runOnlyPendingTimersAsync();
|
|
2649
|
+
outcome = await running;
|
|
2650
|
+
}
|
|
2651
|
+
finally {
|
|
2652
|
+
vi.useRealTimers();
|
|
2653
|
+
}
|
|
2619
2654
|
// The unanswered prompt denies the tool with guidance — the turn completes
|
|
2620
2655
|
// cleanly so the user can answer with a normal message later.
|
|
2621
2656
|
expect(guardResult).toMatchObject({ behavior: "deny" });
|
|
@@ -2630,6 +2665,93 @@ describe("BridgeJobExecutor", () => {
|
|
|
2630
2665
|
// recovery turn. Posting input-resolved here would clear it.
|
|
2631
2666
|
expect(inputResolvedCalls).toBe(0);
|
|
2632
2667
|
});
|
|
2668
|
+
// BAKER-3550: a question card answers itself with the recommended option after a
|
|
2669
|
+
// countdown, so the turn must still be waiting when that pick arrives.
|
|
2670
|
+
it("holds a question card's turn past the countdown that picks for the user, then lets it go", async () => {
|
|
2671
|
+
vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] });
|
|
2672
|
+
try {
|
|
2673
|
+
let guardResult;
|
|
2674
|
+
let reachedTool = () => undefined;
|
|
2675
|
+
const toolReached = new Promise((resolve) => {
|
|
2676
|
+
reachedTool = resolve;
|
|
2677
|
+
});
|
|
2678
|
+
const controls = activeControls();
|
|
2679
|
+
const executor = new BridgeJobExecutor({
|
|
2680
|
+
sdk: {
|
|
2681
|
+
query: (queryInput) => ({
|
|
2682
|
+
async *[Symbol.asyncIterator]() {
|
|
2683
|
+
reachedTool();
|
|
2684
|
+
guardResult = await queryInput.options.canUseTool?.("AskUserQuestion", { questions: [{ question: "Which budget?", options: [{ label: "Low" }, { label: "High" }] }] }, {
|
|
2685
|
+
toolUseID: "tool_countdown",
|
|
2686
|
+
requestId: "req_countdown",
|
|
2687
|
+
signal: queryInput.options.abortController?.signal ?? new AbortController().signal,
|
|
2688
|
+
});
|
|
2689
|
+
yield resultMessage(0, "session_countdown");
|
|
2690
|
+
},
|
|
2691
|
+
}),
|
|
2692
|
+
},
|
|
2693
|
+
model: "claude-test",
|
|
2694
|
+
sessionStore: { load: async () => undefined, save: async () => undefined, clear: async () => undefined },
|
|
2695
|
+
relay: recordingRelay(),
|
|
2696
|
+
});
|
|
2697
|
+
const outcome = executor.execute({ threadId: "thread_countdown", turnId: "turn_countdown", prompt: "Ask first" }, controls);
|
|
2698
|
+
await toolReached;
|
|
2699
|
+
await vi.advanceTimersByTimeAsync(65_000);
|
|
2700
|
+
expect(guardResult).toBeUndefined();
|
|
2701
|
+
expect(controls.pendingQuestions.has("tool_countdown")).toBe(true);
|
|
2702
|
+
await vi.advanceTimersByTimeAsync(60_000);
|
|
2703
|
+
expect(guardResult).toMatchObject({ behavior: "deny", message: expect.stringContaining("has not answered") });
|
|
2704
|
+
await vi.runOnlyPendingTimersAsync();
|
|
2705
|
+
await outcome;
|
|
2706
|
+
}
|
|
2707
|
+
finally {
|
|
2708
|
+
vi.useRealTimers();
|
|
2709
|
+
}
|
|
2710
|
+
});
|
|
2711
|
+
it("carries on the same turn with the recommended option Baker picked, and tells the agent nobody answered", async () => {
|
|
2712
|
+
const relay = recordingRelay();
|
|
2713
|
+
let guardResult;
|
|
2714
|
+
const controls = activeControls();
|
|
2715
|
+
const question = "Which colour should the ad use?";
|
|
2716
|
+
const executor = new BridgeJobExecutor({
|
|
2717
|
+
sdk: {
|
|
2718
|
+
query: (input) => ({
|
|
2719
|
+
async *[Symbol.asyncIterator]() {
|
|
2720
|
+
const decision = input.options.canUseTool?.("AskUserQuestion", {
|
|
2721
|
+
questions: [
|
|
2722
|
+
{ question, header: "Colour", options: [{ label: "Blue" }, { label: "Teal (Recommended)" }] },
|
|
2723
|
+
],
|
|
2724
|
+
}, {
|
|
2725
|
+
toolUseID: "tool_auto",
|
|
2726
|
+
requestId: "req_auto",
|
|
2727
|
+
signal: input.options.abortController?.signal ?? new AbortController().signal,
|
|
2728
|
+
});
|
|
2729
|
+
while (!controls.pendingQuestions.has("tool_auto")) {
|
|
2730
|
+
await Promise.resolve();
|
|
2731
|
+
}
|
|
2732
|
+
controls.pendingQuestions
|
|
2733
|
+
.get("tool_auto")
|
|
2734
|
+
?.resolve({ answers: { q0: "Teal (Recommended)" }, chosenBy: "auto" });
|
|
2735
|
+
guardResult = await decision;
|
|
2736
|
+
yield resultMessage(0, "session_auto");
|
|
2737
|
+
},
|
|
2738
|
+
}),
|
|
2739
|
+
},
|
|
2740
|
+
model: "claude-test",
|
|
2741
|
+
sessionStore: { load: async () => undefined, save: async () => undefined, clear: async () => undefined },
|
|
2742
|
+
relay,
|
|
2743
|
+
});
|
|
2744
|
+
await executor.execute({ threadId: "thread_auto", turnId: "turn_auto", prompt: "Make an ad" }, controls);
|
|
2745
|
+
expect(guardResult).toMatchObject({
|
|
2746
|
+
behavior: "allow",
|
|
2747
|
+
updatedInput: {
|
|
2748
|
+
answers: { [question]: "Teal (Recommended)" },
|
|
2749
|
+
annotations: {
|
|
2750
|
+
[question]: { notes: expect.stringMatching(/nobody answered.*picked the recommended option automatically/i) },
|
|
2751
|
+
},
|
|
2752
|
+
},
|
|
2753
|
+
});
|
|
2754
|
+
});
|
|
2633
2755
|
// Without this the run deadlocks: the wait expires, the deny tells the agent to
|
|
2634
2756
|
// end its turn, and the pending card it leaves behind parks the thread on a
|
|
2635
2757
|
// question nobody will ever answer — stranding the Session and its Task.
|
|
@@ -4864,4 +4986,11 @@ describe("BridgeJobExecutor model refusals", () => {
|
|
|
4864
4986
|
expect(outcome).toMatchObject({ kind: "completed" });
|
|
4865
4987
|
});
|
|
4866
4988
|
});
|
|
4989
|
+
describe("agentFeature", () => {
|
|
4990
|
+
it("tells OpenRouter a Session review, a plan and a chat turn apart", () => {
|
|
4991
|
+
expect(agentFeature({ review: {} })).toBe("chatReview.session");
|
|
4992
|
+
expect(agentFeature({ planMode: true })).toBe("agent.plan");
|
|
4993
|
+
expect(agentFeature({})).toBe("agent.turn");
|
|
4994
|
+
});
|
|
4995
|
+
});
|
|
4867
4996
|
//# sourceMappingURL=bridge-job.test.js.map
|