@positronic/spec 0.0.67 → 0.0.69

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.
@@ -1 +1 @@
1
- {"version":3,"file":"brains.d.ts","sourceRoot":"","sources":["../../src/api/brains.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,eAAO,MAAM,MAAM;IACjB;;OAEG;eAEM,KAAK,cACA,MAAM,YACR,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAwCzB;;OAEG;0BAEM,KAAK,cACA,MAAM,WACT,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmCzB;;OAEG;uBAEM,KAAK,yBACW,MAAM,GAC5B,OAAO,CAAC,OAAO,CAAC;IAuDnB;;OAEG;iBACgB,KAAK,SAAS,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2D1D;;OAEG;mBAEM,KAAK,cACA,MAAM,UACV,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;IA2DnB;;OAEG;oBACmB,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAsD9C;;OAEG;gBACe,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IA6D1C;;;;;OAKG;kBAEM,KAAK,SACL,MAAM,GACZ,OAAO,CAAC;QACT,MAAM,EAAE,KAAK,CAAC;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;QACH,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAqET;;;OAGG;wBACuB,KAAK,cAAc,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtE;;OAEG;sBACqB,KAAK,cAAc,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsEpE;;OAEG;kBACiB,KAAK,SAAS,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0F3D;;OAEG;0BAEM,KAAK,oBACM,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC;IA4BnB;;;OAGG;iCAEM,KAAK,uBACS,MAAM,GAC1B,OAAO,CAAC,OAAO,CAAC;IAqDnB;;OAEG;wBAEM,KAAK,uBACS,MAAM,GAC1B,OAAO,CAAC,OAAO,CAAC;IAkEnB;;OAEG;iBAEM,KAAK,cACA,MAAM,UACV,MAAM,aACH,MAAM,eACJ,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuCzB;;OAEG;gBACe,KAAK,SAAS,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBzD;;;;;;;;OAQG;yBAEM,KAAK,uBACS,MAAM,eACd,MAAM,kBACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC;IA4LnB;;;;;;;;;;OAUG;4BAEM,KAAK,wBACU,MAAM,GAC3B,OAAO,CAAC,OAAO,CAAC;IA2JnB;;;;;;;;;;OAUG;8BAEM,KAAK,wBACU,MAAM,eACf,MAAM,kBACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC;IA4NnB;;;;;;;;;;;;;OAaG;sDAEM,KAAK,wBACU,MAAM,eACf,MAAM,kBACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC;CA+OpB,CAAC"}
1
+ {"version":3,"file":"brains.d.ts","sourceRoot":"","sources":["../../src/api/brains.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC,eAAO,MAAM,MAAM;IACjB;;OAEG;eAEM,KAAK,cACA,MAAM,YACR,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAwCzB;;OAEG;0BAEM,KAAK,cACA,MAAM,WACT,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC9B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmCzB;;OAEG;uBAEM,KAAK,yBACW,MAAM,GAC5B,OAAO,CAAC,OAAO,CAAC;IAuDnB;;OAEG;iBACgB,KAAK,SAAS,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA2D1D;;OAEG;mBAEM,KAAK,cACA,MAAM,UACV,MAAM,GACb,OAAO,CAAC,OAAO,CAAC;IA2DnB;;OAEG;oBACmB,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IAsD9C;;OAEG;gBACe,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC;IA6D1C;;;;;OAKG;kBAEM,KAAK,SACL,MAAM,GACZ,OAAO,CAAC;QACT,MAAM,EAAE,KAAK,CAAC;YACZ,KAAK,EAAE,MAAM,CAAC;YACd,WAAW,EAAE,MAAM,CAAC;SACrB,CAAC,CAAC;QACH,KAAK,EAAE,MAAM,CAAC;KACf,GAAG,IAAI,CAAC;IAqET;;;OAGG;wBACuB,KAAK,cAAc,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAqFtE;;OAEG;sBACqB,KAAK,cAAc,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsEpE;;OAEG;kBACiB,KAAK,SAAS,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA0F3D;;OAEG;0BAEM,KAAK,oBACM,MAAM,GACvB,OAAO,CAAC,OAAO,CAAC;IA4BnB;;;OAGG;iCAEM,KAAK,uBACS,MAAM,GAC1B,OAAO,CAAC,OAAO,CAAC;IAqDnB;;OAEG;wBAEM,KAAK,uBACS,MAAM,GAC1B,OAAO,CAAC,OAAO,CAAC;IAkEnB;;OAEG;iBAEM,KAAK,cACA,MAAM,UACV,MAAM,aACH,MAAM,eACJ,MAAM,GAClB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAuCzB;;OAEG;gBACe,KAAK,SAAS,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBzD;;;;;;;;OAQG;yBAEM,KAAK,uBACS,MAAM,eACd,MAAM,kBACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC;IA0JnB;;;;;;;;;;OAUG;4BAEM,KAAK,wBACU,MAAM,GAC3B,OAAO,CAAC,OAAO,CAAC;IAuGnB;;;;;;;;;;OAUG;8BAEM,KAAK,wBACU,MAAM,eACf,MAAM,kBACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC;IAuJnB;;;;;;;;;;;;;OAaG;sDAEM,KAAK,wBACU,MAAM,eACf,MAAM,kBACH,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAClC,OAAO,CAAC,OAAO,CAAC;CAqKpB,CAAC"}
@@ -1,37 +1,38 @@
1
1
  import { STATUS, BRAIN_EVENTS } from '@positronic/core';
2
+ import { startBrainRun, readSseUntil } from './helpers.js';
2
3
  export const brains = {
3
4
  /**
4
5
  * Test POST /brains/runs - Create a new brain run
5
6
  */
6
7
  async run(fetch, identifier, options) {
7
- try {
8
- const body = { identifier };
9
- if (options && Object.keys(options).length > 0) {
10
- body.options = options;
11
- }
12
- const request = new Request('http://example.com/brains/runs', {
13
- method: 'POST',
14
- headers: {
15
- 'Content-Type': 'application/json',
16
- },
17
- body: JSON.stringify(body),
18
- });
19
- const response = await fetch(request);
20
- if (response.status !== 201) {
21
- console.error(`POST /brains/runs returned ${response.status}, expected 201`);
22
- return null;
8
+ if (options && Object.keys(options).length > 0) {
9
+ // When options are provided, we need the full request (can't use startBrainRun)
10
+ try {
11
+ const request = new Request('http://example.com/brains/runs', {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ },
16
+ body: JSON.stringify({ identifier, options }),
17
+ });
18
+ const response = await fetch(request);
19
+ if (response.status !== 201) {
20
+ console.error(`POST /brains/runs returned ${response.status}, expected 201`);
21
+ return null;
22
+ }
23
+ const data = (await response.json());
24
+ if (!data.brainRunId || typeof data.brainRunId !== 'string') {
25
+ console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
26
+ return null;
27
+ }
28
+ return data.brainRunId;
23
29
  }
24
- const data = (await response.json());
25
- if (!data.brainRunId || typeof data.brainRunId !== 'string') {
26
- console.error(`Expected brainRunId to be string, got ${typeof data.brainRunId}`);
30
+ catch (error) {
31
+ console.error(`Failed to test POST /brains/runs:`, error);
27
32
  return null;
28
33
  }
29
- return data.brainRunId;
30
- }
31
- catch (error) {
32
- console.error(`Failed to test POST /brains/runs:`, error);
33
- return null;
34
34
  }
35
+ return startBrainRun(fetch, identifier);
35
36
  },
36
37
  /**
37
38
  * Test POST /brains/runs with options - Create a brain run with runtime options
@@ -636,17 +637,9 @@ export const brains = {
636
637
  async killSuspended(fetch, loopBrainIdentifier, webhookSlug, webhookPayload) {
637
638
  try {
638
639
  // Step 1: Start the loop brain
639
- const runRequest = new Request('http://example.com/brains/runs', {
640
- method: 'POST',
641
- headers: { 'Content-Type': 'application/json' },
642
- body: JSON.stringify({ identifier: loopBrainIdentifier }),
643
- });
644
- const runResponse = await fetch(runRequest);
645
- if (runResponse.status !== 201) {
646
- console.error(`POST /brains/runs returned ${runResponse.status}, expected 201`);
640
+ const brainRunId = await startBrainRun(fetch, loopBrainIdentifier);
641
+ if (!brainRunId)
647
642
  return false;
648
- }
649
- const { brainRunId } = (await runResponse.json());
650
643
  // Step 2: Watch until WEBHOOK event (brain pauses)
651
644
  const watchRequest = new Request(`http://example.com/brains/runs/${brainRunId}/watch`, { method: 'GET' });
652
645
  const watchResponse = await fetch(watchRequest);
@@ -654,45 +647,25 @@ export const brains = {
654
647
  console.error(`GET /brains/runs/${brainRunId}/watch returned ${watchResponse.status}`);
655
648
  return false;
656
649
  }
657
- let foundWebhookEvent = false;
658
- if (watchResponse.body) {
659
- const reader = watchResponse.body.getReader();
660
- const decoder = new TextDecoder();
661
- let buffer = '';
662
- try {
663
- while (!foundWebhookEvent) {
664
- const { value, done } = await reader.read();
665
- if (done)
666
- break;
667
- buffer += decoder.decode(value, { stream: true });
668
- let eventEndIndex;
669
- while ((eventEndIndex = buffer.indexOf('\n\n')) !== -1) {
670
- const message = buffer.substring(0, eventEndIndex);
671
- buffer = buffer.substring(eventEndIndex + 2);
672
- if (message.startsWith('data: ')) {
673
- try {
674
- const event = JSON.parse(message.substring(6));
675
- if (event.type === BRAIN_EVENTS.WEBHOOK) {
676
- foundWebhookEvent = true;
677
- break;
678
- }
679
- if (event.type === BRAIN_EVENTS.COMPLETE ||
680
- event.type === BRAIN_EVENTS.ERROR) {
681
- console.error(`Brain completed/errored before WEBHOOK event: ${event.type}`);
682
- return false;
683
- }
684
- }
685
- catch (e) {
686
- // Ignore parse errors
687
- }
688
- }
689
- }
690
- }
691
- }
692
- finally {
693
- await reader.cancel();
694
- }
650
+ if (!watchResponse.body) {
651
+ console.error('Watch response has no body');
652
+ return false;
695
653
  }
654
+ let earlyTermination = false;
655
+ const events = await readSseUntil(watchResponse.body, (event) => {
656
+ if (event.type === BRAIN_EVENTS.WEBHOOK)
657
+ return true;
658
+ if (event.type === BRAIN_EVENTS.COMPLETE ||
659
+ event.type === BRAIN_EVENTS.ERROR) {
660
+ console.error(`Brain completed/errored before WEBHOOK event: ${event.type}`);
661
+ earlyTermination = true;
662
+ return true;
663
+ }
664
+ return false;
665
+ });
666
+ if (earlyTermination)
667
+ return false;
668
+ const foundWebhookEvent = events.some((e) => e.type === BRAIN_EVENTS.WEBHOOK);
696
669
  if (!foundWebhookEvent) {
697
670
  console.error('Brain did not emit WEBHOOK event');
698
671
  return false;
@@ -768,17 +741,9 @@ export const brains = {
768
741
  async watchAgentEvents(fetch, agentBrainIdentifier) {
769
742
  try {
770
743
  // Start the agent brain
771
- const runRequest = new Request('http://example.com/brains/runs', {
772
- method: 'POST',
773
- headers: { 'Content-Type': 'application/json' },
774
- body: JSON.stringify({ identifier: agentBrainIdentifier }),
775
- });
776
- const runResponse = await fetch(runRequest);
777
- if (runResponse.status !== 201) {
778
- console.error(`POST /brains/runs returned ${runResponse.status}, expected 201`);
744
+ const brainRunId = await startBrainRun(fetch, agentBrainIdentifier);
745
+ if (!brainRunId)
779
746
  return false;
780
- }
781
- const { brainRunId } = (await runResponse.json());
782
747
  // Watch the brain run
783
748
  const watchRequest = new Request(`http://example.com/brains/runs/${brainRunId}/watch`, { method: 'GET' });
784
749
  const watchResponse = await fetch(watchRequest);
@@ -787,46 +752,11 @@ export const brains = {
787
752
  return false;
788
753
  }
789
754
  // Read SSE events until we get WEBHOOK or COMPLETE/ERROR
790
- const events = [];
791
- if (watchResponse.body) {
792
- const reader = watchResponse.body.getReader();
793
- const decoder = new TextDecoder();
794
- let buffer = '';
795
- let done = false;
796
- try {
797
- while (!done) {
798
- const { value, done: streamDone } = await reader.read();
799
- if (streamDone)
800
- break;
801
- buffer += decoder.decode(value, { stream: true });
802
- // Process complete SSE messages
803
- let eventEndIndex;
804
- while ((eventEndIndex = buffer.indexOf('\n\n')) !== -1) {
805
- const message = buffer.substring(0, eventEndIndex);
806
- buffer = buffer.substring(eventEndIndex + 2);
807
- if (message.startsWith('data: ')) {
808
- try {
809
- const event = JSON.parse(message.substring(6));
810
- events.push(event);
811
- // Stop on terminal events
812
- if (event.type === BRAIN_EVENTS.WEBHOOK ||
813
- event.type === BRAIN_EVENTS.COMPLETE ||
814
- event.type === BRAIN_EVENTS.ERROR) {
815
- done = true;
816
- break;
817
- }
818
- }
819
- catch (e) {
820
- // Ignore parse errors
821
- }
822
- }
823
- }
824
- }
825
- }
826
- finally {
827
- await reader.cancel();
828
- }
829
- }
755
+ const events = watchResponse.body
756
+ ? await readSseUntil(watchResponse.body, (event) => event.type === BRAIN_EVENTS.WEBHOOK ||
757
+ event.type === BRAIN_EVENTS.COMPLETE ||
758
+ event.type === BRAIN_EVENTS.ERROR)
759
+ : [];
830
760
  // Verify required agent events are present
831
761
  const hasAgentStart = events.some((e) => e.type === BRAIN_EVENTS.AGENT_START);
832
762
  if (!hasAgentStart) {
@@ -890,17 +820,9 @@ export const brains = {
890
820
  async agentWebhookResume(fetch, agentBrainIdentifier, webhookSlug, webhookPayload) {
891
821
  try {
892
822
  // Step 1: Start the agent brain
893
- const runRequest = new Request('http://example.com/brains/runs', {
894
- method: 'POST',
895
- headers: { 'Content-Type': 'application/json' },
896
- body: JSON.stringify({ identifier: agentBrainIdentifier }),
897
- });
898
- const runResponse = await fetch(runRequest);
899
- if (runResponse.status !== 201) {
900
- console.error(`POST /brains/runs returned ${runResponse.status}, expected 201`);
823
+ const brainRunId = await startBrainRun(fetch, agentBrainIdentifier);
824
+ if (!brainRunId)
901
825
  return false;
902
- }
903
- const { brainRunId } = (await runResponse.json());
904
826
  // Step 2: Watch until WEBHOOK event (brain pauses)
905
827
  const watchRequest = new Request(`http://example.com/brains/runs/${brainRunId}/watch`, { method: 'GET' });
906
828
  const watchResponse = await fetch(watchRequest);
@@ -908,45 +830,25 @@ export const brains = {
908
830
  console.error(`GET /brains/runs/${brainRunId}/watch returned ${watchResponse.status}`);
909
831
  return false;
910
832
  }
911
- let foundWebhookEvent = false;
912
- if (watchResponse.body) {
913
- const reader = watchResponse.body.getReader();
914
- const decoder = new TextDecoder();
915
- let buffer = '';
916
- try {
917
- while (!foundWebhookEvent) {
918
- const { value, done } = await reader.read();
919
- if (done)
920
- break;
921
- buffer += decoder.decode(value, { stream: true });
922
- let eventEndIndex;
923
- while ((eventEndIndex = buffer.indexOf('\n\n')) !== -1) {
924
- const message = buffer.substring(0, eventEndIndex);
925
- buffer = buffer.substring(eventEndIndex + 2);
926
- if (message.startsWith('data: ')) {
927
- try {
928
- const event = JSON.parse(message.substring(6));
929
- if (event.type === BRAIN_EVENTS.WEBHOOK) {
930
- foundWebhookEvent = true;
931
- break;
932
- }
933
- if (event.type === BRAIN_EVENTS.COMPLETE ||
934
- event.type === BRAIN_EVENTS.ERROR) {
935
- console.error(`Brain completed/errored before WEBHOOK event: ${event.type}`);
936
- return false;
937
- }
938
- }
939
- catch (e) {
940
- // Ignore parse errors
941
- }
942
- }
943
- }
944
- }
945
- }
946
- finally {
947
- await reader.cancel();
948
- }
833
+ if (!watchResponse.body) {
834
+ console.error('Watch response has no body');
835
+ return false;
949
836
  }
837
+ let earlyTermination = false;
838
+ const watchEvents = await readSseUntil(watchResponse.body, (event) => {
839
+ if (event.type === BRAIN_EVENTS.WEBHOOK)
840
+ return true;
841
+ if (event.type === BRAIN_EVENTS.COMPLETE ||
842
+ event.type === BRAIN_EVENTS.ERROR) {
843
+ console.error(`Brain completed/errored before WEBHOOK event: ${event.type}`);
844
+ earlyTermination = true;
845
+ return true;
846
+ }
847
+ return false;
848
+ });
849
+ if (earlyTermination)
850
+ return false;
851
+ const foundWebhookEvent = watchEvents.some((e) => e.type === BRAIN_EVENTS.WEBHOOK);
950
852
  if (!foundWebhookEvent) {
951
853
  console.error('Brain did not emit WEBHOOK event');
952
854
  return false;
@@ -978,43 +880,10 @@ export const brains = {
978
880
  console.error(`GET /brains/runs/${brainRunId}/watch (resume) returned ${resumeWatchResponse.status}`);
979
881
  return false;
980
882
  }
981
- const resumeEvents = [];
982
- if (resumeWatchResponse.body) {
983
- const reader = resumeWatchResponse.body.getReader();
984
- const decoder = new TextDecoder();
985
- let buffer = '';
986
- let done = false;
987
- try {
988
- while (!done) {
989
- const { value, done: streamDone } = await reader.read();
990
- if (streamDone)
991
- break;
992
- buffer += decoder.decode(value, { stream: true });
993
- let eventEndIndex;
994
- while ((eventEndIndex = buffer.indexOf('\n\n')) !== -1) {
995
- const message = buffer.substring(0, eventEndIndex);
996
- buffer = buffer.substring(eventEndIndex + 2);
997
- if (message.startsWith('data: ')) {
998
- try {
999
- const event = JSON.parse(message.substring(6));
1000
- resumeEvents.push(event);
1001
- if (event.type === BRAIN_EVENTS.COMPLETE ||
1002
- event.type === BRAIN_EVENTS.ERROR) {
1003
- done = true;
1004
- break;
1005
- }
1006
- }
1007
- catch (e) {
1008
- // Ignore parse errors
1009
- }
1010
- }
1011
- }
1012
- }
1013
- }
1014
- finally {
1015
- await reader.cancel();
1016
- }
1017
- }
883
+ const resumeEvents = resumeWatchResponse.body
884
+ ? await readSseUntil(resumeWatchResponse.body, (event) => event.type === BRAIN_EVENTS.COMPLETE ||
885
+ event.type === BRAIN_EVENTS.ERROR)
886
+ : [];
1018
887
  // Verify WEBHOOK_RESPONSE event is present
1019
888
  const hasWebhookResponse = resumeEvents.some((e) => e.type === BRAIN_EVENTS.WEBHOOK_RESPONSE);
1020
889
  if (!hasWebhookResponse) {
@@ -1061,17 +930,9 @@ export const brains = {
1061
930
  async innerBrainCompleteDoesNotAffectOuterStatus(fetch, outerBrainIdentifier, webhookSlug, webhookPayload) {
1062
931
  try {
1063
932
  // Step 1: Start the outer brain
1064
- const runRequest = new Request('http://example.com/brains/runs', {
1065
- method: 'POST',
1066
- headers: { 'Content-Type': 'application/json' },
1067
- body: JSON.stringify({ identifier: outerBrainIdentifier }),
1068
- });
1069
- const runResponse = await fetch(runRequest);
1070
- if (runResponse.status !== 201) {
1071
- console.error(`POST /brains/runs returned ${runResponse.status}, expected 201`);
933
+ const brainRunId = await startBrainRun(fetch, outerBrainIdentifier);
934
+ if (!brainRunId)
1072
935
  return false;
1073
- }
1074
- const { brainRunId } = (await runResponse.json());
1075
936
  // Step 2: Watch SSE until WEBHOOK event from outer brain (after inner completes)
1076
937
  const watchRequest = new Request(`http://example.com/brains/runs/${brainRunId}/watch`, { method: 'GET' });
1077
938
  const watchResponse = await fetch(watchRequest);
@@ -1079,51 +940,28 @@ export const brains = {
1079
940
  console.error(`GET /brains/runs/${brainRunId}/watch returned ${watchResponse.status}`);
1080
941
  return false;
1081
942
  }
1082
- let foundOuterWebhook = false;
943
+ if (!watchResponse.body) {
944
+ console.error('Watch response has no body');
945
+ return false;
946
+ }
1083
947
  let innerCompleteCount = 0;
1084
- if (watchResponse.body) {
1085
- const reader = watchResponse.body.getReader();
1086
- const decoder = new TextDecoder();
1087
- let buffer = '';
1088
- try {
1089
- while (!foundOuterWebhook) {
1090
- const { value, done } = await reader.read();
1091
- if (done)
1092
- break;
1093
- buffer += decoder.decode(value, { stream: true });
1094
- let eventEndIndex;
1095
- while ((eventEndIndex = buffer.indexOf('\n\n')) !== -1) {
1096
- const message = buffer.substring(0, eventEndIndex);
1097
- buffer = buffer.substring(eventEndIndex + 2);
1098
- if (message.startsWith('data: ')) {
1099
- try {
1100
- const event = JSON.parse(message.substring(6));
1101
- // Track inner brain completes
1102
- if (event.type === BRAIN_EVENTS.COMPLETE) {
1103
- innerCompleteCount++;
1104
- // First complete is inner brain, second would be outer
1105
- }
1106
- // Outer brain webhook (happens after inner brain completes)
1107
- if (event.type === BRAIN_EVENTS.WEBHOOK) {
1108
- foundOuterWebhook = true;
1109
- break;
1110
- }
1111
- if (event.type === BRAIN_EVENTS.ERROR) {
1112
- console.error(`Brain errored: ${JSON.stringify(event.error)}`);
1113
- return false;
1114
- }
1115
- }
1116
- catch {
1117
- // Ignore parse errors
1118
- }
1119
- }
1120
- }
1121
- }
948
+ let hitError = false;
949
+ const watchEvents = await readSseUntil(watchResponse.body, (event) => {
950
+ if (event.type === BRAIN_EVENTS.COMPLETE) {
951
+ innerCompleteCount++;
1122
952
  }
1123
- finally {
1124
- await reader.cancel();
953
+ if (event.type === BRAIN_EVENTS.WEBHOOK)
954
+ return true;
955
+ if (event.type === BRAIN_EVENTS.ERROR) {
956
+ console.error(`Brain errored: ${JSON.stringify(event.error)}`);
957
+ hitError = true;
958
+ return true;
1125
959
  }
1126
- }
960
+ return false;
961
+ });
962
+ if (hitError)
963
+ return false;
964
+ const foundOuterWebhook = watchEvents.some((e) => e.type === BRAIN_EVENTS.WEBHOOK);
1127
965
  if (!foundOuterWebhook) {
1128
966
  console.error('Did not receive outer brain WEBHOOK event');
1129
967
  return false;
@@ -1173,48 +1011,19 @@ export const brains = {
1173
1011
  // Wait for the OUTER brain's COMPLETE event specifically (by tracking depth)
1174
1012
  // When resuming, the SSE stream includes historical events, so we need to
1175
1013
  // track START/COMPLETE events to know when the outer brain truly finishes
1176
- let foundFinalComplete = false;
1177
- let depth = 0;
1178
1014
  if (resumeWatchResponse.body) {
1179
- const reader = resumeWatchResponse.body.getReader();
1180
- const decoder = new TextDecoder();
1181
- let buffer = '';
1182
- try {
1183
- while (!foundFinalComplete) {
1184
- const { value, done } = await reader.read();
1185
- if (done)
1186
- break;
1187
- buffer += decoder.decode(value, { stream: true });
1188
- let eventEndIndex;
1189
- while ((eventEndIndex = buffer.indexOf('\n\n')) !== -1) {
1190
- const message = buffer.substring(0, eventEndIndex);
1191
- buffer = buffer.substring(eventEndIndex + 2);
1192
- if (message.startsWith('data: ')) {
1193
- try {
1194
- const event = JSON.parse(message.substring(6));
1195
- // Track depth to find the outer brain's COMPLETE
1196
- if (event.type === BRAIN_EVENTS.START) {
1197
- depth++;
1198
- }
1199
- else if (event.type === BRAIN_EVENTS.COMPLETE) {
1200
- depth--;
1201
- // When depth reaches 0, the outer brain has completed
1202
- if (depth <= 0) {
1203
- foundFinalComplete = true;
1204
- break;
1205
- }
1206
- }
1207
- }
1208
- catch {
1209
- // Ignore parse errors
1210
- }
1211
- }
1212
- }
1015
+ let depth = 0;
1016
+ await readSseUntil(resumeWatchResponse.body, (event) => {
1017
+ if (event.type === BRAIN_EVENTS.START) {
1018
+ depth++;
1213
1019
  }
1214
- }
1215
- finally {
1216
- await reader.cancel();
1217
- }
1020
+ else if (event.type === BRAIN_EVENTS.COMPLETE) {
1021
+ depth--;
1022
+ if (depth <= 0)
1023
+ return true;
1024
+ }
1025
+ return false;
1026
+ });
1218
1027
  }
1219
1028
  // Step 6: Verify final status is COMPLETE
1220
1029
  const finalHistoryResponse = await fetch(historyRequest);