@opencode-ai/ai 0.0.0-dev-18539 → 0.0.0-dev-18545
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.
|
@@ -565,6 +565,7 @@ export interface ParserState {
|
|
|
565
565
|
readonly name: string;
|
|
566
566
|
readonly providerMetadataKey: string;
|
|
567
567
|
readonly tools: ToolStream.State<string>;
|
|
568
|
+
readonly completedTools: ReadonlySet<string>;
|
|
568
569
|
readonly hasFunctionCall: boolean;
|
|
569
570
|
readonly lifecycle: Lifecycle.State;
|
|
570
571
|
readonly outputItems: Readonly<Record<number, string>>;
|
|
@@ -625,19 +625,57 @@ const onOutputTextDone = (state, event, id) => {
|
|
|
625
625
|
return [{ ...state, lifecycle: Lifecycle.textEnd(state.lifecycle, events, id) }, events];
|
|
626
626
|
};
|
|
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 (!
|
|
630
|
+
if (!item || index === 0 || item.summaryParts[index] !== undefined)
|
|
631
|
+
return [state, NO_EVENTS];
|
|
632
|
+
if (Object.values(item.summaryParts).every((status) => status === "concluded"))
|
|
631
633
|
return [state, NO_EVENTS];
|
|
632
|
-
const index = event.summary_index ?? 0;
|
|
633
634
|
const events = [];
|
|
635
|
+
const lifecycle = Object.entries(item.summaryParts)
|
|
636
|
+
.filter((entry) => entry[1] !== "concluded")
|
|
637
|
+
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${itemID}:${entry[0]}`, providerMetadata(state, { itemId: itemID })), state.lifecycle);
|
|
634
638
|
return [
|
|
635
639
|
{
|
|
636
640
|
...state,
|
|
637
|
-
lifecycle: Lifecycle.
|
|
641
|
+
lifecycle: Lifecycle.reasoningStart(lifecycle, events, `${itemID}:${index}`, providerMetadata(state, { itemId: itemID, reasoningEncryptedContent: item.encryptedContent ?? null })),
|
|
638
642
|
reasoningItems: {
|
|
639
643
|
...state.reasoningItems,
|
|
640
|
-
[itemID]: {
|
|
644
|
+
[itemID]: {
|
|
645
|
+
...item,
|
|
646
|
+
summaryParts: {
|
|
647
|
+
...Object.fromEntries(Object.entries(item.summaryParts).map((entry) => entry[1] === "concluded" ? entry : [entry[0], "concluded"])),
|
|
648
|
+
[index]: "active",
|
|
649
|
+
},
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
events,
|
|
654
|
+
];
|
|
655
|
+
};
|
|
656
|
+
export const onReasoningDelta = (state, event, itemID) => {
|
|
657
|
+
const item = state.reasoningItems[itemID];
|
|
658
|
+
if (!event.delta || !item)
|
|
659
|
+
return [state, NO_EVENTS];
|
|
660
|
+
const index = event.summary_index ?? 0;
|
|
661
|
+
if (item.summaryParts[index] === "concluded")
|
|
662
|
+
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];
|
|
669
|
+
if (!current)
|
|
670
|
+
return started;
|
|
671
|
+
const events = [...started[1]];
|
|
672
|
+
return [
|
|
673
|
+
{
|
|
674
|
+
...started[0],
|
|
675
|
+
lifecycle: Lifecycle.reasoningDelta(started[0].lifecycle, events, `${itemID}:${index}`, event.delta),
|
|
676
|
+
reasoningItems: {
|
|
677
|
+
...started[0].reasoningItems,
|
|
678
|
+
[itemID]: { ...current, deltaIndexes: new Set([...current.deltaIndexes, index]) },
|
|
641
679
|
},
|
|
642
680
|
},
|
|
643
681
|
events,
|
|
@@ -656,32 +694,45 @@ export const onReasoningDone = (state, event, itemID) => {
|
|
|
656
694
|
return onReasoningDelta(state, { ...event, delta: event.text }, itemID);
|
|
657
695
|
};
|
|
658
696
|
const reasoningMetadata = (state, item) => providerMetadata(state, { itemId: item.id, reasoningEncryptedContent: item.encrypted_content ?? null });
|
|
659
|
-
// Responses APIs stream reasoning items in
|
|
697
|
+
// Responses APIs normally stream reasoning items in this order:
|
|
660
698
|
// `output_item.added` (reasoning) →
|
|
661
699
|
// `reasoning_summary_part.added` (index=0) →
|
|
662
700
|
// `reasoning_summary_text.delta` →
|
|
663
701
|
// `reasoning_summary_part.done` (index=0) →
|
|
664
702
|
// (repeat for index>0) →
|
|
665
703
|
// `output_item.done` (reasoning).
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
// fold against the same entry. Behaviour for out-of-order events is
|
|
670
|
-
// best-effort, not guaranteed.
|
|
704
|
+
// `onOutputItemAdded` seeds the per-item entry, while each later part start is
|
|
705
|
+
// also an implicit boundary for the previous part. This keeps the common event
|
|
706
|
+
// lifecycle ordered when a compatible provider omits or delays a part-done event.
|
|
671
707
|
const onOutputItemAdded = (state, event) => {
|
|
672
708
|
const item = event.item;
|
|
673
709
|
if (item?.type === "message" && item.id !== undefined) {
|
|
710
|
+
const itemID = item.id;
|
|
674
711
|
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.
|
|
715
|
+
const events = [];
|
|
716
|
+
const lifecycle = [...state.lifecycle.text]
|
|
717
|
+
.filter((id) => id !== itemID)
|
|
718
|
+
.reduce((lifecycle, id) => {
|
|
719
|
+
const openPhase = state.messagePhases[id];
|
|
720
|
+
return Lifecycle.textEnd(lifecycle, events, id, providerMetadata(state, { itemId: id, ...(openPhase === undefined ? {} : { phase: openPhase }) }));
|
|
721
|
+
}, state.lifecycle);
|
|
722
|
+
const nextPhase = phase === undefined ? state.messagePhases[itemID] : phase;
|
|
675
723
|
return [
|
|
676
724
|
{
|
|
677
725
|
...state,
|
|
678
|
-
|
|
679
|
-
|
|
726
|
+
lifecycle,
|
|
727
|
+
messageItems: new Set([itemID]),
|
|
728
|
+
messagePhases: nextPhase === undefined ? {} : { [itemID]: nextPhase },
|
|
680
729
|
},
|
|
681
|
-
|
|
730
|
+
events,
|
|
682
731
|
];
|
|
683
732
|
}
|
|
684
733
|
if (item && isReasoningItem(item)) {
|
|
734
|
+
if (state.reasoningItems[item.id] !== undefined)
|
|
735
|
+
return [state, NO_EVENTS];
|
|
685
736
|
const events = [];
|
|
686
737
|
return [
|
|
687
738
|
{
|
|
@@ -702,6 +753,10 @@ const onOutputItemAdded = (state, event) => {
|
|
|
702
753
|
if (item?.type !== "function_call" || !item.call_id)
|
|
703
754
|
return [state, NO_EVENTS];
|
|
704
755
|
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
|
+
if (Object.values(state.tools).some((tool) => tool?.id === item.call_id) || state.completedTools.has(item.call_id))
|
|
759
|
+
return [state, NO_EVENTS];
|
|
705
760
|
const metadata = item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined;
|
|
706
761
|
const events = [];
|
|
707
762
|
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
|
|
@@ -722,32 +777,7 @@ const onOutputItemAdded = (state, event) => {
|
|
|
722
777
|
const onReasoningSummaryPartAdded = (state, event) => {
|
|
723
778
|
if (event.item_id === undefined || event.summary_index === undefined)
|
|
724
779
|
return [state, NO_EVENTS];
|
|
725
|
-
|
|
726
|
-
if (!item)
|
|
727
|
-
return [state, NO_EVENTS];
|
|
728
|
-
if (event.summary_index === 0)
|
|
729
|
-
return [state, NO_EVENTS];
|
|
730
|
-
const events = [];
|
|
731
|
-
const closed = Object.entries(item.summaryParts)
|
|
732
|
-
.filter((entry) => entry[1] === "can-conclude")
|
|
733
|
-
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${event.item_id}:${entry[0]}`, providerMetadata(state, { itemId: event.item_id })), state.lifecycle);
|
|
734
|
-
return [
|
|
735
|
-
{
|
|
736
|
-
...state,
|
|
737
|
-
lifecycle: Lifecycle.reasoningStart(closed, events, `${event.item_id}:${event.summary_index}`, providerMetadata(state, { itemId: event.item_id, reasoningEncryptedContent: item.encryptedContent ?? null })),
|
|
738
|
-
reasoningItems: {
|
|
739
|
-
...state.reasoningItems,
|
|
740
|
-
[event.item_id]: {
|
|
741
|
-
...item,
|
|
742
|
-
summaryParts: {
|
|
743
|
-
...Object.fromEntries(Object.entries(item.summaryParts).map((entry) => entry[1] === "can-conclude" ? [entry[0], "concluded"] : entry)),
|
|
744
|
-
[event.summary_index]: "active",
|
|
745
|
-
},
|
|
746
|
-
},
|
|
747
|
-
},
|
|
748
|
-
},
|
|
749
|
-
events,
|
|
750
|
-
];
|
|
780
|
+
return startReasoningSummaryPart(state, event.item_id, event.summary_index);
|
|
751
781
|
};
|
|
752
782
|
const onReasoningSummaryPartDone = (state, event) => {
|
|
753
783
|
if (event.item_id === undefined || event.summary_index === undefined)
|
|
@@ -755,6 +785,8 @@ const onReasoningSummaryPartDone = (state, event) => {
|
|
|
755
785
|
const item = state.reasoningItems[event.item_id];
|
|
756
786
|
if (!item)
|
|
757
787
|
return [state, NO_EVENTS];
|
|
788
|
+
if (item.summaryParts[event.summary_index] !== "active")
|
|
789
|
+
return [state, NO_EVENTS];
|
|
758
790
|
return [
|
|
759
791
|
{
|
|
760
792
|
...state,
|
|
@@ -821,19 +853,33 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
|
|
|
821
853
|
if (item.type === "function_call") {
|
|
822
854
|
if (!item.call_id || !item.name)
|
|
823
855
|
return [state, NO_EVENTS];
|
|
824
|
-
const
|
|
825
|
-
|
|
856
|
+
const callID = item.call_id;
|
|
857
|
+
if (state.completedTools.has(callID))
|
|
858
|
+
return [state, NO_EVENTS];
|
|
859
|
+
const metadata = item.id !== undefined ? providerMetadata(state, { itemId: item.id }) : undefined;
|
|
860
|
+
const fallback = item.id ?? callID;
|
|
861
|
+
// Match the pending tool by call id so item events that disagree on
|
|
862
|
+
// whether `item.id` is present still resolve the same call.
|
|
863
|
+
const registered = state.tools[fallback] !== undefined
|
|
864
|
+
? fallback
|
|
865
|
+
: Object.keys(state.tools).find((key) => state.tools[key]?.id === callID);
|
|
866
|
+
const id = registered ?? fallback;
|
|
867
|
+
const tools = registered !== undefined
|
|
826
868
|
? state.tools
|
|
827
869
|
: ToolStream.start(state.tools, id, {
|
|
828
|
-
id:
|
|
870
|
+
id: callID,
|
|
829
871
|
name: item.name,
|
|
830
|
-
providerMetadata:
|
|
872
|
+
providerMetadata: metadata,
|
|
831
873
|
});
|
|
832
874
|
const result = item.arguments === undefined
|
|
833
875
|
? yield* ToolStream.finish(state.id, tools, id)
|
|
834
876
|
: yield* ToolStream.finishWithInput(state.id, tools, id, item.arguments);
|
|
835
877
|
const events = [];
|
|
836
|
-
const
|
|
878
|
+
const finished = result.events ?? [];
|
|
879
|
+
// A done-only call never streamed a start event, so open its lifecycle here.
|
|
880
|
+
const resultEvents = registered !== undefined || finished.length === 0
|
|
881
|
+
? finished
|
|
882
|
+
: [LLMEvent.toolInputStart({ id: callID, name: item.name, providerMetadata: metadata }), ...finished];
|
|
837
883
|
const lifecycle = resultEvents.length ? Lifecycle.stepStart(state.lifecycle, events) : state.lifecycle;
|
|
838
884
|
events.push(...resultEvents);
|
|
839
885
|
return [
|
|
@@ -843,6 +889,7 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
|
|
|
843
889
|
hasFunctionCall: resultEvents.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
|
|
844
890
|
state.hasFunctionCall,
|
|
845
891
|
tools: result.tools,
|
|
892
|
+
completedTools: new Set([...state.completedTools, callID]),
|
|
846
893
|
},
|
|
847
894
|
events,
|
|
848
895
|
];
|
|
@@ -855,14 +902,43 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
|
|
|
855
902
|
const lifecycle = Object.entries(reasoningItem.summaryParts)
|
|
856
903
|
.filter((entry) => entry[1] === "active" || entry[1] === "can-conclude")
|
|
857
904
|
.reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${entry[0]}`, metadata), state.lifecycle);
|
|
858
|
-
|
|
859
|
-
|
|
905
|
+
// Keep the fully-concluded entry so duplicate or late events for this
|
|
906
|
+
// item remain no-ops instead of reopening lifecycle state.
|
|
907
|
+
return [
|
|
908
|
+
{
|
|
909
|
+
...state,
|
|
910
|
+
lifecycle,
|
|
911
|
+
reasoningItems: {
|
|
912
|
+
...state.reasoningItems,
|
|
913
|
+
[item.id]: {
|
|
914
|
+
...reasoningItem,
|
|
915
|
+
encryptedContent: item.encrypted_content ?? reasoningItem.encryptedContent,
|
|
916
|
+
summaryParts: Object.fromEntries(Object.keys(reasoningItem.summaryParts).map((index) => [index, "concluded"])),
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
},
|
|
920
|
+
events,
|
|
921
|
+
];
|
|
860
922
|
}
|
|
861
923
|
if (!state.lifecycle.reasoning.has(item.id)) {
|
|
862
924
|
const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
|
|
863
925
|
events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata: metadata }));
|
|
864
926
|
events.push(LLMEvent.reasoningEnd({ id: item.id, providerMetadata: metadata }));
|
|
865
|
-
return [
|
|
927
|
+
return [
|
|
928
|
+
{
|
|
929
|
+
...state,
|
|
930
|
+
lifecycle,
|
|
931
|
+
reasoningItems: {
|
|
932
|
+
...state.reasoningItems,
|
|
933
|
+
[item.id]: {
|
|
934
|
+
encryptedContent: item.encrypted_content,
|
|
935
|
+
summaryParts: { 0: "concluded" },
|
|
936
|
+
deltaIndexes: new Set(),
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
},
|
|
940
|
+
events,
|
|
941
|
+
];
|
|
866
942
|
}
|
|
867
943
|
return [
|
|
868
944
|
{ ...state, lifecycle: Lifecycle.reasoningEnd(state.lifecycle, events, item.id, metadata) },
|
|
@@ -979,6 +1055,11 @@ export const step = (state, input) => {
|
|
|
979
1055
|
if (event.type === "response.output_item.added") {
|
|
980
1056
|
if (event.item?.type === "message" && event.item.id === undefined)
|
|
981
1057
|
return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
|
|
1058
|
+
if (event.item &&
|
|
1059
|
+
isReasoningItem(event.item) &&
|
|
1060
|
+
state.reasoningItems[event.item.id] === undefined &&
|
|
1061
|
+
state.lifecycle.reasoning.size > 0)
|
|
1062
|
+
return ProviderShared.eventError(state.id, `${event.type} started reasoning before the previous item ended`);
|
|
982
1063
|
const id = event.item?.id ?? (event.item?.type === "function_call" ? event.item.call_id : undefined);
|
|
983
1064
|
return Effect.succeed(onOutputItemAdded(event.output_index !== undefined && id !== undefined
|
|
984
1065
|
? { ...state, outputItems: { ...state.outputItems, [event.output_index]: id } }
|
|
@@ -1014,6 +1095,7 @@ export const initial = (request, extension = BASE) => ({
|
|
|
1014
1095
|
providerMetadataKey: request.model.route.providerMetadataKey ?? "openresponses",
|
|
1015
1096
|
hasFunctionCall: false,
|
|
1016
1097
|
tools: ToolStream.empty(),
|
|
1098
|
+
completedTools: new Set(),
|
|
1017
1099
|
lifecycle: Lifecycle.initial(),
|
|
1018
1100
|
outputItems: {},
|
|
1019
1101
|
messageItems: new Set(),
|
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-
|
|
3
|
+
"version": "0.0.0-dev-18545",
|
|
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-
|
|
33
|
+
"@opencode-ai/http-recorder": "0.0.0-dev-18545",
|
|
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-
|
|
42
|
+
"@opencode-ai/schema": "0.0.0-dev-18545",
|
|
43
43
|
"aws4fetch": "1.0.20",
|
|
44
44
|
"effect": "4.0.0-rc.112",
|
|
45
45
|
"google-auth-library": "10.5.0"
|