baa-conductor


commit
ab3ddcb
parent
fdfa382
author
im_wower
date
2026-04-01 23:04:34 +0800 CST
fix: restore Claude final-message relay ingestion
4 files changed,  +72, -8
Raw patch view.
  1diff --git a/plugins/baa-firefox/controller.js b/plugins/baa-firefox/controller.js
  2index 4ba7953bfcc4f94229ca7fe53a6108c2ba16ed5d..1664b68fd3eeda510e8b5ad6501f60a000506530 100644
  3--- a/plugins/baa-firefox/controller.js
  4+++ b/plugins/baa-firefox/controller.js
  5@@ -1399,10 +1399,6 @@ function createPlatformMap(factory) {
  6 }
  7 
  8 function createFinalMessageRelayObserver(platform) {
  9-  if (platform !== "chatgpt" && platform !== "gemini") {
 10-    return null;
 11-  }
 12-
 13   return FINAL_MESSAGE_HELPERS?.createRelayState(platform) || null;
 14 }
 15 
 16diff --git a/plugins/baa-firefox/controller.test.cjs b/plugins/baa-firefox/controller.test.cjs
 17index 01dabd952b971e99e4d7a51c49dd2848f11af527..340f3330764f27746386be6375050cdd0493c7ab 100644
 18--- a/plugins/baa-firefox/controller.test.cjs
 19+++ b/plugins/baa-firefox/controller.test.cjs
 20@@ -76,6 +76,7 @@ function createControllerHarness(options = {}) {
 21     Response,
 22     URL,
 23     URLSearchParams,
 24+    BAAFinalMessage: options.finalMessageHelpers || null,
 25     WebSocket: FakeWebSocket,
 26     crypto: globalThis.crypto,
 27     browser: {
 28@@ -156,6 +157,23 @@ function createControllerHarness(options = {}) {
 29   };
 30 }
 31 
 32+test("controller creates final message relay observers for every supported platform, including Claude", () => {
 33+  const createRelayStateCalls = [];
 34+  const { api } = createControllerHarness({
 35+    finalMessageHelpers: {
 36+      createRelayState(platform) {
 37+        createRelayStateCalls.push(platform);
 38+        return { platform };
 39+      }
 40+    }
 41+  });
 42+
 43+  assert.deepEqual(createRelayStateCalls, ["claude", "chatgpt", "gemini"]);
 44+  assert.deepEqual(api.state.finalMessageRelayObservers.claude, { platform: "claude" });
 45+  assert.deepEqual(api.state.finalMessageRelayObservers.chatgpt, { platform: "chatgpt" });
 46+  assert.deepEqual(api.state.finalMessageRelayObservers.gemini, { platform: "gemini" });
 47+});
 48+
 49 function parseGeminiRequestTuple(reqBody) {
 50   const params = new URLSearchParams(reqBody);
 51   const outer = JSON.parse(params.get("f.req"));
 52diff --git a/plugins/baa-firefox/final-message.js b/plugins/baa-firefox/final-message.js
 53index 474d7e6568710834b5cbc02104cfdd31512dfcc0..21676aba8cd73759817bfbe0e404cb499d6f1ea4 100644
 54--- a/plugins/baa-firefox/final-message.js
 55+++ b/plugins/baa-firefox/final-message.js
 56@@ -39,6 +39,12 @@
 57     }
 58   }
 59 
 60+  function splitSseBlocks(text) {
 61+    return String(text || "")
 62+      .split(/\r?\n\r?\n+/u)
 63+      .filter((block) => block.trim());
 64+  }
 65+
 66   function simpleHash(input) {
 67     const text = String(input || "");
 68     let hash = 2166136261;
 69@@ -191,7 +197,7 @@
 70   function parseSseChunkPayload(chunk) {
 71     const source = String(chunk || "");
 72     const dataLines = source
 73-      .split("\n")
 74+      .split(/\r?\n/u)
 75       .filter((line) => line.startsWith("data:"))
 76       .map((line) => line.slice(5).trimStart());
 77     const payloadText = dataLines.join("\n").trim();
 78@@ -479,7 +485,7 @@
 79     }
 80 
 81     let merged = null;
 82-    for (const block of String(text || "").split(/\n\n+/u)) {
 83+    for (const block of splitSseBlocks(text)) {
 84       merged = mergeCandidates(merged, extractChatgptCandidateFromChunk(block, context));
 85     }
 86     return merged;
 87@@ -826,7 +832,7 @@
 88   function extractClaudeMetadataFromText(text, context) {
 89     let merged = null;
 90 
 91-    for (const block of String(text || "").split(/\n\n+/u)) {
 92+    for (const block of splitSseBlocks(text)) {
 93       const parsedChunk = parseClaudeSsePayload(block);
 94       if (!parsedChunk) continue;
 95       merged = mergeCandidates(merged, buildClaudeCandidate(null, parsedChunk.payload, context));
 96@@ -841,7 +847,7 @@
 97     let metadata = null;
 98     let matched = false;
 99 
100-    for (const block of String(text || "").split(/\n\n+/u)) {
101+    for (const block of splitSseBlocks(text)) {
102       const parsedChunk = parseClaudeSsePayload(block);
103       if (!parsedChunk) continue;
104 
105diff --git a/plugins/baa-firefox/final-message.test.cjs b/plugins/baa-firefox/final-message.test.cjs
106index c4094d10b16374bb8a1e62c38a31ec4b51659cb8..c70afd2fe3831aeabb31a7893821ec15f6a59eea 100644
107--- a/plugins/baa-firefox/final-message.test.cjs
108+++ b/plugins/baa-firefox/final-message.test.cjs
109@@ -294,6 +294,50 @@ test("observeSse relays Claude text_delta fallback after an aborted SSE stream",
110   assert.equal(state.activeStream, null);
111 });
112 
113+test("observeSse relays Claude text_delta fallback when a CRLF-delimited stream arrives as one buffered chunk", () => {
114+  const state = createRelayState("claude");
115+  const meta = {
116+    observedAt: 1743206400000,
117+    pageUrl: "https://claude.ai/chat/conv-claude-crlf"
118+  };
119+  const url = "https://claude.ai/api/organizations/org-1/chat_conversations/conv-claude-crlf/completion";
120+  const chunk = [
121+    "event: message_start",
122+    'data: {"type":"message_start","message":{"uuid":"msg-claude-crlf"}}',
123+    "",
124+    "event: content_block_delta",
125+    'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":"Alpha"}}',
126+    "",
127+    "event: content_block_delta",
128+    'data: {"type":"content_block_delta","delta":{"type":"text_delta","text":" beta"}}',
129+    "",
130+    "event: message_stop",
131+    'data: {"type":"message_stop"}',
132+    ""
133+  ].join("\r\n");
134+
135+  assert.equal(
136+    observeSse(state, {
137+      chunk,
138+      url
139+    }, meta),
140+    null
141+  );
142+
143+  const relay = observeSse(state, {
144+    error: "The operation was aborted.",
145+    url
146+  }, meta);
147+
148+  assert.ok(relay);
149+  assert.equal(relay.payload.platform, "claude");
150+  assert.equal(relay.payload.conversation_id, "conv-claude-crlf");
151+  assert.equal(relay.payload.assistant_message_id, "msg-claude-crlf");
152+  assert.equal(relay.payload.raw_text, "Alpha beta");
153+  assert.equal(relay.payload.observed_at, 1743206400000);
154+  assert.equal(state.activeStream, null);
155+});
156+
157 test("observeSse ignores Claude thinking-only protocol chunks", () => {
158   const state = createRelayState("claude");
159   const meta = {