@opencode-ai/ai 0.0.0-dev-18548 → 0.0.0-dev-18550

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.
@@ -569,12 +569,15 @@ export interface ParserState {
569
569
  readonly hasFunctionCall: boolean;
570
570
  readonly lifecycle: Lifecycle.State;
571
571
  readonly outputItems: Readonly<Record<number, string>>;
572
- readonly messageItems: ReadonlySet<string>;
573
- readonly messagePhases: Readonly<Record<string, MessagePhase | null>>;
572
+ readonly message: {
573
+ readonly id: string;
574
+ readonly phase: MessagePhase | null | undefined;
575
+ } | undefined;
574
576
  readonly reasoningItems: Readonly<Record<string, ReasoningStreamItem>>;
575
577
  }
576
578
  type ReasoningSummaryStatus = "active" | "can-conclude" | "concluded";
577
579
  interface ReasoningStreamItem {
580
+ readonly open: boolean;
578
581
  readonly encryptedContent: string | null | undefined;
579
582
  readonly summaryParts: Readonly<Record<number, ReasoningSummaryStatus>>;
580
583
  readonly deltaIndexes: ReadonlySet<number>;
@@ -607,16 +607,16 @@ const NO_EVENTS = [];
607
607
  const TERMINAL_TYPES = new Set(["error", "response.completed", "response.incomplete", "response.failed"]);
608
608
  export const terminal = (event) => TERMINAL_TYPES.has(event.type);
609
609
  const onOutputTextDelta = (state, event, id) => {
610
- if (!event.delta || !state.messageItems.has(id))
610
+ if (!event.delta || state.message?.id !== id)
611
611
  return [state, NO_EVENTS];
612
612
  const events = [];
613
- const phase = state.messagePhases[id];
613
+ const phase = state.message.phase;
614
614
  const metadata = providerMetadata(state, { itemId: id, ...(phase === undefined ? {} : { phase }) });
615
615
  const lifecycle = Lifecycle.textStart(state.lifecycle, events, id, metadata);
616
616
  return [{ ...state, lifecycle: Lifecycle.textDelta(lifecycle, events, id, event.delta) }, events];
617
617
  };
618
618
  const onOutputTextDone = (state, event, id) => {
619
- if (state.messageItems.has(id)) {
619
+ if (state.message?.id === id) {
620
620
  if (state.lifecycle.text.has(id) || event.text === undefined)
621
621
  return [state, NO_EVENTS];
622
622
  return onOutputTextDelta(state, { ...event, delta: event.text }, id);
@@ -627,9 +627,7 @@ const onOutputTextDone = (state, event, id) => {
627
627
  export const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);
628
628
  const startReasoningSummaryPart = (state, itemID, index) => {
629
629
  const item = state.reasoningItems[itemID];
630
- if (!item || index === 0 || item.summaryParts[index] !== undefined)
631
- return [state, NO_EVENTS];
632
- if (Object.values(item.summaryParts).every((status) => status === "concluded"))
630
+ if (!item?.open || index === 0 || item.summaryParts[index] !== undefined)
633
631
  return [state, NO_EVENTS];
634
632
  const events = [];
635
633
  const lifecycle = Object.entries(item.summaryParts)
@@ -655,26 +653,22 @@ const startReasoningSummaryPart = (state, itemID, index) => {
655
653
  };
656
654
  export const onReasoningDelta = (state, event, itemID) => {
657
655
  const item = state.reasoningItems[itemID];
658
- if (!event.delta || !item)
656
+ if (!event.delta || !item?.open)
659
657
  return [state, NO_EVENTS];
660
658
  const index = event.summary_index ?? 0;
661
659
  if (item.summaryParts[index] === "concluded")
662
660
  return [state, NO_EVENTS];
663
- // An unseen index cannot reopen an item whose parts have all concluded.
664
- if (item.summaryParts[index] === undefined &&
665
- Object.values(item.summaryParts).every((status) => status === "concluded"))
666
- return [state, NO_EVENTS];
667
- const started = item.summaryParts[index] === undefined ? startReasoningSummaryPart(state, itemID, index) : [state, NO_EVENTS];
668
- const current = started[0].reasoningItems[itemID];
661
+ const [started, emitted] = startReasoningSummaryPart(state, itemID, index);
662
+ const current = started.reasoningItems[itemID];
669
663
  if (!current)
670
- return started;
671
- const events = [...started[1]];
664
+ return [started, emitted];
665
+ const events = [...emitted];
672
666
  return [
673
667
  {
674
- ...started[0],
675
- lifecycle: Lifecycle.reasoningDelta(started[0].lifecycle, events, `${itemID}:${index}`, event.delta),
668
+ ...started,
669
+ lifecycle: Lifecycle.reasoningDelta(started.lifecycle, events, `${itemID}:${index}`, event.delta),
676
670
  reasoningItems: {
677
- ...started[0].reasoningItems,
671
+ ...started.reasoningItems,
678
672
  [itemID]: { ...current, deltaIndexes: new Set([...current.deltaIndexes, index]) },
679
673
  },
680
674
  },
@@ -686,7 +680,7 @@ export const onReasoningDelta = (state, event, itemID) => {
686
680
  // as a single delta unless that summary index already streamed one.
687
681
  export const onReasoningDone = (state, event, itemID) => {
688
682
  const item = state.reasoningItems[itemID];
689
- if (!item || typeof event.text !== "string")
683
+ if (!item?.open || typeof event.text !== "string")
690
684
  return [state, NO_EVENTS];
691
685
  const index = event.summary_index ?? 0;
692
686
  if (item.deltaIndexes.has(index))
@@ -709,23 +703,22 @@ const onOutputItemAdded = (state, event) => {
709
703
  if (item?.type === "message" && item.id !== undefined) {
710
704
  const itemID = item.id;
711
705
  const phase = messagePhase(item.phase);
712
- // A new message item is an implicit boundary for every earlier message
713
- // item: text still streaming is ended, and all older items leave the
714
- // tracked set so their late deltas stay no-ops instead of overlapping.
706
+ // A new message closes earlier messages, including ones that never streamed.
715
707
  const events = [];
716
708
  const lifecycle = [...state.lifecycle.text]
717
709
  .filter((id) => id !== itemID)
718
710
  .reduce((lifecycle, id) => {
719
- const openPhase = state.messagePhases[id];
711
+ const openPhase = state.message?.id === id ? state.message.phase : undefined;
720
712
  return Lifecycle.textEnd(lifecycle, events, id, providerMetadata(state, { itemId: id, ...(openPhase === undefined ? {} : { phase: openPhase }) }));
721
713
  }, state.lifecycle);
722
- const nextPhase = phase === undefined ? state.messagePhases[itemID] : phase;
723
714
  return [
724
715
  {
725
716
  ...state,
726
717
  lifecycle,
727
- messageItems: new Set([itemID]),
728
- messagePhases: nextPhase === undefined ? {} : { [itemID]: nextPhase },
718
+ message: {
719
+ id: itemID,
720
+ phase: phase === undefined && state.message?.id === itemID ? state.message.phase : phase,
721
+ },
729
722
  },
730
723
  events,
731
724
  ];
@@ -741,6 +734,7 @@ const onOutputItemAdded = (state, event) => {
741
734
  reasoningItems: {
742
735
  ...state.reasoningItems,
743
736
  [item.id]: {
737
+ open: true,
744
738
  encryptedContent: item.encrypted_content,
745
739
  summaryParts: { 0: "active" },
746
740
  deltaIndexes: new Set(),
@@ -753,8 +747,6 @@ const onOutputItemAdded = (state, event) => {
753
747
  if (item?.type !== "function_call" || !item.call_id)
754
748
  return [state, NO_EVENTS];
755
749
  const id = item.id ?? item.call_id;
756
- // Pending tools always store the call id, so this also catches duplicates
757
- // that disagree on whether `item.id` is present.
758
750
  if (Object.values(state.tools).some((tool) => tool?.id === item.call_id) || state.completedTools.has(item.call_id))
759
751
  return [state, NO_EVENTS];
760
752
  const metadata = item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined;
@@ -783,7 +775,7 @@ const onReasoningSummaryPartDone = (state, event) => {
783
775
  if (event.item_id === undefined || event.summary_index === undefined)
784
776
  return [state, NO_EVENTS];
785
777
  const item = state.reasoningItems[event.item_id];
786
- if (!item)
778
+ if (!item?.open)
787
779
  return [state, NO_EVENTS];
788
780
  if (item.summaryParts[event.summary_index] !== "active")
789
781
  return [state, NO_EVENTS];
@@ -835,17 +827,13 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
835
827
  return [state, NO_EVENTS];
836
828
  if (item.type === "message" && item.id !== undefined) {
837
829
  const itemPhase = messagePhase(item.phase);
838
- const phase = itemPhase === undefined ? state.messagePhases[item.id] : itemPhase;
830
+ const phase = itemPhase === undefined && state.message?.id === item.id ? state.message.phase : itemPhase;
839
831
  const events = [];
840
- const messageItems = new Set(state.messageItems);
841
- messageItems.delete(item.id);
842
- const { [item.id]: _phase, ...messagePhases } = state.messagePhases;
843
832
  return [
844
833
  {
845
834
  ...state,
846
835
  lifecycle: Lifecycle.textEnd(state.lifecycle, events, item.id, providerMetadata(state, { itemId: item.id, ...(phase === undefined ? {} : { phase }) })),
847
- messageItems,
848
- messagePhases,
836
+ message: state.message?.id === item.id ? undefined : state.message,
849
837
  },
850
838
  events,
851
839
  ];
@@ -899,11 +887,11 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
899
887
  const metadata = reasoningMetadata(state, item);
900
888
  const reasoningItem = state.reasoningItems[item.id];
901
889
  if (reasoningItem) {
890
+ if (!reasoningItem.open)
891
+ return [state, NO_EVENTS];
902
892
  const lifecycle = Object.entries(reasoningItem.summaryParts)
903
893
  .filter((entry) => entry[1] === "active" || entry[1] === "can-conclude")
904
894
  .reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${entry[0]}`, metadata), state.lifecycle);
905
- // Keep the fully-concluded entry so duplicate or late events for this
906
- // item remain no-ops instead of reopening lifecycle state.
907
895
  return [
908
896
  {
909
897
  ...state,
@@ -912,8 +900,8 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
912
900
  ...state.reasoningItems,
913
901
  [item.id]: {
914
902
  ...reasoningItem,
903
+ open: false,
915
904
  encryptedContent: item.encrypted_content ?? reasoningItem.encryptedContent,
916
- summaryParts: Object.fromEntries(Object.keys(reasoningItem.summaryParts).map((index) => [index, "concluded"])),
917
905
  },
918
906
  },
919
907
  },
@@ -931,6 +919,7 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
931
919
  reasoningItems: {
932
920
  ...state.reasoningItems,
933
921
  [item.id]: {
922
+ open: false,
934
923
  encryptedContent: item.encrypted_content,
935
924
  summaryParts: { 0: "concluded" },
936
925
  deltaIndexes: new Set(),
@@ -953,7 +942,7 @@ const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (
953
942
  const id = item.id ?? (item.type === "function_call" ? item.call_id : undefined);
954
943
  if (id === undefined ||
955
944
  ((item.type !== "function_call" || !current.tools[id]) &&
956
- (item.type !== "reasoning" || !current.reasoningItems[id])))
945
+ (item.type !== "reasoning" || !current.reasoningItems[id]?.open)))
957
946
  return Effect.succeed([current, events]);
958
947
  return onOutputItemDone(current, { type: "response.output_item.done", item }).pipe(Effect.map(([next, emitted]) => [next, [...events, ...emitted]]));
959
948
  })
@@ -1098,8 +1087,7 @@ export const initial = (request, extension = BASE) => ({
1098
1087
  completedTools: new Set(),
1099
1088
  lifecycle: Lifecycle.initial(),
1100
1089
  outputItems: {},
1101
- messageItems: new Set(),
1102
- messagePhases: {},
1090
+ message: undefined,
1103
1091
  reasoningItems: {},
1104
1092
  });
1105
1093
  export const protocol = Protocol.make({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
- "version": "0.0.0-dev-18548",
3
+ "version": "0.0.0-dev-18550",
4
4
  "name": "@opencode-ai/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -30,7 +30,7 @@
30
30
  "devDependencies": {
31
31
  "@clack/prompts": "1.0.0-alpha.1",
32
32
  "@effect/platform-node": "4.0.0-rc.112",
33
- "@opencode-ai/http-recorder": "0.0.0-dev-18548",
33
+ "@opencode-ai/http-recorder": "0.0.0-dev-18550",
34
34
  "@tsconfig/bun": "1.0.9",
35
35
  "@types/bun": "1.3.13",
36
36
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -39,7 +39,7 @@
39
39
  "dependencies": {
40
40
  "@smithy/eventstream-codec": "4.2.14",
41
41
  "@smithy/util-utf8": "4.2.2",
42
- "@opencode-ai/schema": "0.0.0-dev-18548",
42
+ "@opencode-ai/schema": "0.0.0-dev-18550",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-rc.112",
45
45
  "google-auth-library": "10.5.0"