@opencode-ai/ai 0.0.0-dev-18659 → 0.0.0-dev-18661

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,4 +1,4 @@
1
- import { Effect, Schema } from "effect";
1
+ import { Effect, Option, Schema } from "effect";
2
2
  import { HttpTransport } from "../route/transport/index.js";
3
3
  import { Protocol } from "../route/protocol.js";
4
4
  import { AIError, LLMEvent, ProviderInternalError, Usage, } from "../schema/index.js";
@@ -624,6 +624,14 @@ const onOutputTextDone = (state, event, id) => {
624
624
  const events = [];
625
625
  return [{ ...state, lifecycle: Lifecycle.textEnd(state.lifecycle, events, id) }, events];
626
626
  };
627
+ const decodeMessagePart = Schema.decodeUnknownOption(Schema.Union([OpenResponsesOutputText, Schema.Struct({ type: Schema.tag("refusal"), refusal: Schema.String })]));
628
+ const decodeSummaryPart = Schema.decodeUnknownOption(OpenResponsesReasoningSummaryText);
629
+ const decodeReasoningPart = Schema.decodeUnknownOption(Schema.Struct({ type: Schema.tag("reasoning_text"), text: Schema.String }));
630
+ const joinReasoningText = (parts) => {
631
+ if (!parts.some((part) => part !== undefined && part.length > 0))
632
+ return undefined;
633
+ return parts.filter((part) => part !== undefined).join("\n\n");
634
+ };
627
635
  export const outputItemID = (state, event) => event.output_index === undefined ? event.item_id : (state.outputItems[event.output_index] ?? event.item_id);
628
636
  const startReasoningSummaryPart = (state, itemID, index) => {
629
637
  const item = state.reasoningItems[itemID];
@@ -821,19 +829,30 @@ const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgu
821
829
  events.push(...result.events);
822
830
  return [{ ...state, lifecycle, tools: result.tools }, events];
823
831
  });
824
- const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (state, event) {
825
- const item = event.item;
832
+ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (state, item) {
826
833
  if (!item)
827
834
  return [state, NO_EVENTS];
828
835
  if (item.type === "message" && item.id !== undefined) {
836
+ const message = state.message?.id === item.id ? state.message : undefined;
829
837
  const itemPhase = messagePhase(item.phase);
830
- const phase = itemPhase === undefined && state.message?.id === item.id ? state.message.phase : itemPhase;
838
+ const phase = itemPhase === undefined ? message?.phase : itemPhase;
839
+ const parts = Array.isArray(item.content) ? item.content : [];
840
+ const content = [];
841
+ for (const part of parts) {
842
+ const decoded = Option.getOrUndefined(decodeMessagePart(part));
843
+ if (!decoded)
844
+ continue;
845
+ content.push(decoded.type === "output_text" ? decoded.text : decoded.refusal);
846
+ }
847
+ const text = content.length > 0 ? content.join("") : undefined;
848
+ const metadata = providerMetadata(state, { itemId: item.id, ...(phase === undefined ? {} : { phase }) });
831
849
  const events = [];
850
+ const lifecycle = message && text ? Lifecycle.textStart(state.lifecycle, events, item.id, metadata) : state.lifecycle;
832
851
  return [
833
852
  {
834
853
  ...state,
835
- lifecycle: Lifecycle.textEnd(state.lifecycle, events, item.id, providerMetadata(state, { itemId: item.id, ...(phase === undefined ? {} : { phase }) })),
836
- message: state.message?.id === item.id ? undefined : state.message,
854
+ lifecycle: Lifecycle.textEnd(lifecycle, events, item.id, metadata, text),
855
+ message: message ? undefined : state.message,
837
856
  },
838
857
  events,
839
858
  ];
@@ -883,15 +902,36 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
883
902
  ];
884
903
  }
885
904
  if (isReasoningItem(item)) {
886
- const events = [];
905
+ if (state.reasoningItems[item.id]?.open === false)
906
+ return [state, NO_EVENTS];
887
907
  const metadata = reasoningMetadata(state, item);
908
+ const summaryParts = Array.isArray(item.summary) ? item.summary : [];
909
+ const summary = [];
910
+ for (const part of summaryParts) {
911
+ const decoded = Option.getOrUndefined(decodeSummaryPart(part));
912
+ // Keep missing entries so the array still matches the provider's summary indexes.
913
+ summary.push(decoded?.text);
914
+ }
915
+ const reasoningParts = Array.isArray(item.content) ? item.content : [];
916
+ const content = [];
917
+ for (const part of reasoningParts) {
918
+ const decoded = Option.getOrUndefined(decodeReasoningPart(part));
919
+ if (decoded)
920
+ content.push(decoded.text);
921
+ }
922
+ const itemText = joinReasoningText(summary) ?? joinReasoningText(content);
923
+ const events = [];
888
924
  const reasoningItem = state.reasoningItems[item.id];
889
925
  if (reasoningItem) {
890
- if (!reasoningItem.open)
891
- return [state, NO_EVENTS];
892
- const lifecycle = Object.entries(reasoningItem.summaryParts)
893
- .filter((entry) => entry[1] === "active" || entry[1] === "can-conclude")
894
- .reduce((lifecycle, entry) => Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${entry[0]}`, metadata), state.lifecycle);
926
+ const fragments = Object.entries(reasoningItem.summaryParts);
927
+ let lifecycle = state.lifecycle;
928
+ for (const [index, status] of fragments) {
929
+ if (status === "concluded")
930
+ continue;
931
+ // Do not repeat earlier summaries that were already emitted as separate fragments.
932
+ const finalText = fragments.length === 1 ? itemText : summary[Number(index)];
933
+ lifecycle = Lifecycle.reasoningEnd(lifecycle, events, `${item.id}:${index}`, metadata, finalText || undefined);
934
+ }
895
935
  return [
896
936
  {
897
937
  ...state,
@@ -911,7 +951,11 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
911
951
  if (!state.lifecycle.reasoning.has(item.id)) {
912
952
  const lifecycle = Lifecycle.stepStart(state.lifecycle, events);
913
953
  events.push(LLMEvent.reasoningStart({ id: item.id, providerMetadata: metadata }));
914
- events.push(LLMEvent.reasoningEnd({ id: item.id, providerMetadata: metadata }));
954
+ events.push(LLMEvent.reasoningEnd({
955
+ id: item.id,
956
+ providerMetadata: metadata,
957
+ text: itemText,
958
+ }));
915
959
  return [
916
960
  {
917
961
  ...state,
@@ -937,22 +981,25 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
937
981
  return [state, NO_EVENTS];
938
982
  });
939
983
  const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (state, event) {
940
- const reconciled = event.type === "response.completed"
941
- ? yield* Effect.reduce(event.response?.output ?? [], () => [state, NO_EVENTS], ([current, events], item) => {
984
+ let current = state;
985
+ const events = [];
986
+ if (event.type === "response.completed") {
987
+ for (const item of event.response?.output ?? []) {
942
988
  const id = item.id ?? (item.type === "function_call" ? item.call_id : undefined);
943
- if (id === undefined ||
944
- ((item.type !== "function_call" || !current.tools[id]) &&
945
- (item.type !== "reasoning" || !current.reasoningItems[id]?.open)))
946
- return Effect.succeed([current, events]);
947
- return onOutputItemDone(current, { type: "response.output_item.done", item }).pipe(Effect.map(([next, emitted]) => [next, [...events, ...emitted]]));
948
- })
949
- : [state, NO_EVENTS];
950
- const current = reconciled[0];
989
+ if (id === undefined)
990
+ continue;
991
+ if (item.type !== "function_call" || !current.tools[id])
992
+ continue;
993
+ const [next, emitted] = yield* onOutputItemDone(current, item);
994
+ current = next;
995
+ events.push(...emitted);
996
+ }
997
+ }
951
998
  // Some compatible providers omit output_item.done even after completing the response.
952
999
  const pending = event.type === "response.completed"
953
1000
  ? yield* ToolStream.finishAll(current.id, current.tools)
954
1001
  : { tools: current.tools, events: NO_EVENTS };
955
- const events = [...reconciled[1], ...pending.events];
1002
+ events.push(...pending.events);
956
1003
  const hasFunctionCall = pending.events.some((event) => LLMEvent.is.toolCall(event) || LLMEvent.is.toolInputError(event)) ||
957
1004
  current.hasFunctionCall;
958
1005
  const lifecycle = Lifecycle.finish(current.lifecycle, events, {
@@ -1061,7 +1108,7 @@ export const step = (state, input) => {
1061
1108
  if (event.type === "response.output_item.done") {
1062
1109
  if (event.item?.type === "message" && event.item.id === undefined)
1063
1110
  return ProviderShared.eventError(state.id, `${event.type} message is missing id`);
1064
- return onOutputItemDone(state, event);
1111
+ return onOutputItemDone(state, event.item);
1065
1112
  }
1066
1113
  if (event.type === "response.completed" || event.type === "response.incomplete")
1067
1114
  return onResponseFinish(state, event);
@@ -846,15 +846,15 @@ const step = (state, event) => Effect.gen(function* () {
846
846
  lifecycle = Lifecycle.stepStart(lifecycle, events);
847
847
  events.push(...result.events);
848
848
  }
849
- const contentFiltered = finishReason?.normalized === "content-filter";
849
+ const incompleteTools = finishReason?.normalized === "content-filter" || finishReason?.normalized === "length";
850
850
  if (finishReason !== undefined &&
851
- !contentFiltered &&
851
+ !incompleteTools &&
852
852
  state.finishReason === undefined &&
853
853
  Object.keys(pendingTools).length)
854
854
  return yield* ProviderShared.eventError(ADAPTER, "OpenAI Chat tool call delta is missing id or name", ProviderShared.encodeJson(event));
855
- // A content filter terminates the response without confirming pending tool calls.
855
+ // Filtering or truncation terminates the response without confirming pending tool calls.
856
856
  const finished = finishReason !== undefined &&
857
- !contentFiltered &&
857
+ !incompleteTools &&
858
858
  state.finishReason === undefined &&
859
859
  Object.keys(tools).length > 0
860
860
  ? yield* ToolStream.finishAll(ADAPTER, tools)
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-18659",
3
+ "version": "0.0.0-dev-18661",
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-18659",
33
+ "@opencode-ai/http-recorder": "0.0.0-dev-18661",
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-18659",
42
+ "@opencode-ai/schema": "0.0.0-dev-18661",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-rc.112",
45
45
  "google-auth-library": "10.5.0"