@opencode-ai/ai 0.0.0-dev-17938 → 0.0.0-dev-17945

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.
@@ -451,7 +451,6 @@ export interface ParserState {
451
451
  readonly hasFunctionCall: boolean;
452
452
  readonly lifecycle: Lifecycle.State;
453
453
  readonly messageItems: ReadonlySet<string>;
454
- readonly messagePhase: (value: unknown) => MessagePhase | null | undefined;
455
454
  readonly messagePhases: Readonly<Record<string, MessagePhase | null>>;
456
455
  readonly reasoningItems: Readonly<Record<string, ReasoningStreamItem>>;
457
456
  readonly store: boolean | undefined;
@@ -39,6 +39,11 @@ const OpenResponsesOutputText = Schema.Struct({
39
39
  text: Schema.String,
40
40
  });
41
41
  export const MessagePhase = Schema.NullOr(Schema.Literals(["commentary", "final_answer"]));
42
+ const messagePhase = (value) => {
43
+ if (value === null || value === "commentary" || value === "final_answer")
44
+ return value;
45
+ return undefined;
46
+ };
42
47
  const OpenResponsesReasoningSummaryText = Schema.Struct({
43
48
  type: Schema.tag("summary_text"),
44
49
  text: Schema.String,
@@ -218,19 +223,6 @@ export const Event = Schema.StructWithRest(Schema.Struct({
218
223
  status_code: Schema.optional(Schema.Unknown),
219
224
  headers: Schema.optional(Schema.Unknown),
220
225
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
221
- const RefusalEvent = Schema.Union([
222
- Schema.Struct({
223
- type: Schema.tag("response.refusal.delta"),
224
- item_id: Schema.String,
225
- delta: Schema.String,
226
- }),
227
- Schema.Struct({
228
- type: Schema.tag("response.refusal.done"),
229
- item_id: Schema.String,
230
- refusal: Schema.String,
231
- }),
232
- ]);
233
- const isRefusalEvent = Schema.is(RefusalEvent);
234
226
  const BASE = { id: ADAPTER, name: NAME };
235
227
  // =============================================================================
236
228
  // Request Lowering
@@ -284,9 +276,6 @@ const lowerReasoning = (part, providerMetadataKey) => {
284
276
  encrypted_content: encryptedContent,
285
277
  };
286
278
  };
287
- const hostedToolItemID = (part, providerMetadataKey) => {
288
- return itemID(part.providerMetadata, providerMetadataKey);
289
- };
290
279
  const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (part, request, extension, target) {
291
280
  const media = ProviderShared.normalizeMedia(part);
292
281
  const extended = extension.lowerMedia?.({ part, media, request });
@@ -424,9 +413,9 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
424
413
  }
425
414
  if (part.type === "tool-result" && part.providerExecuted === true) {
426
415
  flushText();
427
- const itemID = hostedToolItemID(part, providerMetadataKey);
428
- if (store !== false && itemID && !hostedToolReferences.has(itemID))
429
- input.push({ type: "item_reference", id: itemID });
416
+ const id = itemID(part.providerMetadata, providerMetadataKey);
417
+ if (store !== false && id && !hostedToolReferences.has(id))
418
+ input.push({ type: "item_reference", id });
430
419
  if (store === false && part.result.type === "content") {
431
420
  const content = part.result.value;
432
421
  input.push({
@@ -434,8 +423,8 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
434
423
  content: yield* Effect.forEach(content, (item) => lowerHostedToolResultContentItem(item, request, extension)),
435
424
  });
436
425
  }
437
- if (itemID)
438
- hostedToolReferences.add(itemID);
426
+ if (id)
427
+ hostedToolReferences.add(id);
439
428
  continue;
440
429
  }
441
430
  return yield* ProviderShared.unsupportedContent(extension.name, "assistant", [
@@ -619,18 +608,17 @@ const reasoningMetadata = (state, item) => providerMetadata(state, { itemId: ite
619
608
  // best-effort, not guaranteed.
620
609
  const onOutputItemAdded = (state, event) => {
621
610
  const item = event.item;
622
- if (item?.type === "message" && item.id)
611
+ if (item?.type === "message" && item.id) {
612
+ const phase = messagePhase(item.phase);
623
613
  return [
624
614
  {
625
615
  ...state,
626
616
  messageItems: new Set([...state.messageItems, item.id]),
627
- messagePhases: (() => {
628
- const phase = state.messagePhase(item.phase);
629
- return phase === undefined ? state.messagePhases : { ...state.messagePhases, [item.id]: phase };
630
- })(),
617
+ messagePhases: phase === undefined ? state.messagePhases : { ...state.messagePhases, [item.id]: phase },
631
618
  },
632
619
  NO_EVENTS,
633
620
  ];
621
+ }
634
622
  if (item && isReasoningItem(item)) {
635
623
  const events = [];
636
624
  return [
@@ -752,7 +740,7 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
752
740
  if (!item)
753
741
  return [state, NO_EVENTS];
754
742
  if (item.type === "message" && item.id) {
755
- const itemPhase = state.messagePhase(item.phase);
743
+ const itemPhase = messagePhase(item.phase);
756
744
  const phase = itemPhase === undefined ? state.messagePhases[item.id] : itemPhase;
757
745
  const events = [];
758
746
  const messageItems = new Set(state.messageItems);
@@ -843,22 +831,26 @@ const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (
843
831
  });
844
832
  return [{ ...state, lifecycle, hasFunctionCall, tools: pending.tools }, events];
845
833
  });
846
- // Build a single human-readable message from whatever the provider supplied.
834
+ // Build the prettiest summary available from whatever the provider supplied.
847
835
  // When both code and message are present, prefix the code so consumers see
848
836
  // the failure mode (e.g. `rate_limit_exceeded: Slow down`) instead of just
849
837
  // the bare message — production rate limits and context-length failures used
850
- // to be indistinguishable from generic stream drops.
851
- const providerErrorMessage = (event, fallback) => {
852
- const nested = event.error ?? event.response?.error ?? undefined;
838
+ // to be indistinguishable from generic stream drops. Returns undefined when
839
+ // the payload carries no usable summary.
840
+ const providerErrorMessage = (event, nested) => {
853
841
  const message = event.message || nested?.message || undefined;
854
842
  const code = event.code || nested?.code || undefined;
855
843
  if (message && code)
856
844
  return `${code}: ${message}`;
857
- return message || code || fallback;
845
+ return message || code;
858
846
  };
859
847
  export const providerFailure = (id, event, fallback) => {
860
- const code = event.code || event.error?.code || event.response?.error?.code || undefined;
861
- const message = providerErrorMessage(event, fallback);
848
+ const nested = event.error ?? event.response?.error ?? undefined;
849
+ const code = event.code || nested?.code || undefined;
850
+ // Keep the full raw payload on the error even when the message is a summary.
851
+ const body = JSON.stringify(nested ?? event) ?? "";
852
+ const summary = providerErrorMessage(event, nested);
853
+ const message = summary ?? (body === "{}" ? fallback : body);
862
854
  const status = typeof event.status === "number"
863
855
  ? event.status
864
856
  : typeof event.status_code === "number"
@@ -867,7 +859,8 @@ export const providerFailure = (id, event, fallback) => {
867
859
  return new AIError({
868
860
  module: id,
869
861
  method: "stream",
870
- reason: classifyProviderFailure({ message, code, status }),
862
+ body,
863
+ reason: classifyProviderFailure({ message, code, status, rawBody: body }),
871
864
  });
872
865
  };
873
866
  const providerError = (state, event, fallback) => providerFailure(state.id, event, fallback);
@@ -880,22 +873,18 @@ export const step = (state, event) => {
880
873
  : onOutputTextDone(state, event, event.item_id));
881
874
  }
882
875
  if (event.type === "response.refusal.delta" || event.type === "response.refusal.done") {
883
- if (!isRefusalEvent(event))
876
+ const value = event.type === "response.refusal.delta" ? event.delta : event.refusal;
877
+ if (!event.item_id || typeof value !== "string")
884
878
  return ProviderShared.eventError(state.id, `${event.type} is malformed`);
885
879
  return Effect.succeed(event.type === "response.refusal.delta"
886
880
  ? onOutputTextDelta(state, event, event.item_id)
887
- : onOutputTextDone(state, { ...event, text: event.refusal }, event.item_id));
881
+ : onOutputTextDone(state, { ...event, text: value }, event.item_id));
888
882
  }
889
883
  if (event.type === "response.reasoning.delta" || event.type === "response.reasoning_summary_text.delta") {
890
884
  if (!event.item_id)
891
885
  return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
892
886
  return Effect.succeed(onReasoningDelta(state, event, event.item_id));
893
887
  }
894
- if (event.type === "response.reasoning.done" || event.type === "response.reasoning_summary_text.done") {
895
- if (!event.item_id)
896
- return ProviderShared.eventError(state.id, `${event.type} is missing item_id`);
897
- return Effect.succeed(onReasoningDone(state, event));
898
- }
899
888
  if (event.type === "response.reasoning_summary_part.added")
900
889
  return event.item_id
901
890
  ? Effect.succeed(onReasoningSummaryPartAdded(state, event))
@@ -939,16 +928,10 @@ export const initial = (request, extension = BASE) => ({
939
928
  tools: ToolStream.empty(),
940
929
  lifecycle: Lifecycle.initial(),
941
930
  messageItems: new Set(),
942
- messagePhase,
943
931
  messagePhases: {},
944
932
  reasoningItems: {},
945
933
  store: OpenResponsesOptions.resolve(request).store,
946
934
  });
947
- const messagePhase = (value) => {
948
- if (value === null || value === "commentary" || value === "final_answer")
949
- return value;
950
- return undefined;
951
- };
952
935
  export const protocol = Protocol.make({
953
936
  id: ADAPTER,
954
937
  body: {
@@ -6,6 +6,7 @@ export interface ProviderFailure {
6
6
  readonly message: string;
7
7
  readonly status?: number | undefined;
8
8
  readonly code?: string | undefined;
9
+ readonly rawBody?: string | undefined;
9
10
  readonly retryAfterMs?: number | undefined;
10
11
  readonly rateLimit?: HttpRateLimitDetails | undefined;
11
12
  readonly http?: HttpContext | undefined;
@@ -56,11 +56,13 @@ const NETWORK_ERROR_TEXT = /network[-_\s]error/i;
56
56
  // Keep HTTP failures and provider-reported stream failures on one typed path so
57
57
  // session retry policy never needs provider-specific string matching.
58
58
  export function classifyProviderFailure(input) {
59
- const body = input.http?.body ?? "";
59
+ const body = input.http?.body ?? input.rawBody ?? "";
60
60
  const codes = [input.code, ...providerCodes(body), ...providerCodes(input.message)]
61
61
  .filter((code) => code !== undefined)
62
62
  .map((code) => code.toLowerCase());
63
- const text = body || input.message;
63
+ // Scan the raw payload too so signals missing from the summary message
64
+ // (e.g. overflow phrases nested in a JSON error body) still classify.
65
+ const text = [input.message, body].filter((value) => value.length > 0).join("\n");
64
66
  const common = { message: input.message, providerMetadata: input.providerMetadata, http: input.http };
65
67
  const clientScoped = input.status === undefined || (input.status >= 400 && input.status < 500);
66
68
  if (clientScoped &&
@@ -140,6 +140,7 @@ declare const AIError_base: Schema.Class<AIError, Schema.TaggedStruct<"AI.Error"
140
140
  readonly module: Schema.String;
141
141
  readonly method: Schema.String;
142
142
  readonly reason: Schema.toTaggedUnion<"_tag", readonly [typeof InvalidRequestReason, typeof NoRouteReason, typeof AuthenticationReason, typeof RateLimitReason, typeof QuotaExceededReason, typeof ContentPolicyReason, typeof ProviderInternalReason, typeof TransportReason, typeof InvalidProviderOutputReason, typeof UnknownProviderReason]>;
143
+ readonly body: Schema.optional<Schema.String>;
143
144
  }>, import("effect/Cause").YieldableError>;
144
145
  export declare class AIError extends AIError_base {
145
146
  readonly cause: InvalidRequestReason | NoRouteReason | AuthenticationReason | RateLimitReason | QuotaExceededReason | ContentPolicyReason | ProviderInternalReason | TransportReason | InvalidProviderOutputReason | UnknownProviderReason;
@@ -137,6 +137,9 @@ export class AIError extends Schema.TaggedError()("AI.Error", {
137
137
  module: Schema.String,
138
138
  method: Schema.String,
139
139
  reason: AIErrorReason,
140
+ // Raw provider payload as a string, so classified failures never lose the
141
+ // original error detail even when the pretty message is a summary.
142
+ body: Schema.optional(Schema.String),
140
143
  }) {
141
144
  cause = this.reason;
142
145
  get message() {
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-17938",
3
+ "version": "0.0.0-dev-17945",
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.110",
33
- "@opencode-ai/http-recorder": "0.0.0-dev-17938",
33
+ "@opencode-ai/http-recorder": "0.0.0-dev-17945",
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-17938",
42
+ "@opencode-ai/schema": "0.0.0-dev-17945",
43
43
  "aws4fetch": "1.0.20",
44
44
  "effect": "4.0.0-rc.110",
45
45
  "google-auth-library": "10.5.0"