@opencode/ai 0.0.0-dev-20536 → 0.0.0-dev-20538

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.
@@ -462,7 +462,7 @@ const serverToolResultType = (name) => {
462
462
  return "web_fetch_tool_result";
463
463
  return undefined;
464
464
  };
465
- const lowerServerToolResult = Effect.fn("AnthropicMessages.lowerServerToolResult")(function* (part, providerMetadataKey) {
465
+ const lowerServerToolResult = Effect.fnUntraced(function* (part, providerMetadataKey) {
466
466
  const wireType = serverToolResultType(part.name);
467
467
  if (!wireType)
468
468
  return yield* invalid(`Anthropic Messages does not know how to round-trip server tool result for ${part.name}`);
@@ -538,7 +538,7 @@ const citationsFromMetadata = (metadata) => {
538
538
  return undefined;
539
539
  };
540
540
  const isHttpUrl = (value) => /^https?:\/\//i.test(value.trim());
541
- const lowerMedia = Effect.fn("AnthropicMessages.lowerMedia")(function* (part, breakpoints) {
541
+ const lowerMedia = Effect.fnUntraced(function* (part, breakpoints) {
542
542
  const mime = part.media.mediaType.toLowerCase();
543
543
  const cacheControlValue = breakpoints ? cacheControl(breakpoints, part.cache) : undefined;
544
544
  const fileId = fileIdFromMetadata(part.metadata);
@@ -716,7 +716,7 @@ const splitsLocalToolResults = (messages, index) => {
716
716
  }
717
717
  return pending.size > 0;
718
718
  };
719
- const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUpdate")(function* (message, breakpoints) {
719
+ const lowerNativeSystemUpdate = Effect.fnUntraced(function* (message, breakpoints) {
720
720
  const content = yield* ProviderShared.systemUpdateText("Anthropic Messages", message);
721
721
  return {
722
722
  role: "system",
@@ -727,7 +727,7 @@ const lowerNativeSystemUpdate = Effect.fn("AnthropicMessages.lowerNativeSystemUp
727
727
  })),
728
728
  };
729
729
  });
730
- const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (request, breakpoints) {
730
+ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
731
731
  const messages = [];
732
732
  const providerMetadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
733
733
  for (const [index, message] of request.messages.entries()) {
@@ -1132,7 +1132,7 @@ const onContentBlockStart = (state, event) => {
1132
1132
  const events = [];
1133
1133
  return [{ ...state, lifecycle: Lifecycle.stepStart(state.lifecycle, events) }, [...events, result]];
1134
1134
  };
1135
- const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(function* (state, event) {
1135
+ const onContentBlockDelta = Effect.fnUntraced(function* (state, event) {
1136
1136
  const delta = event.delta;
1137
1137
  if (delta.type === "compaction_delta") {
1138
1138
  if (event.index === undefined || !(event.index in state.compactions) || delta.content === undefined)
@@ -1190,7 +1190,7 @@ const onContentBlockDelta = Effect.fn("AnthropicMessages.onContentBlockDelta")(f
1190
1190
  }
1191
1191
  return [state, NO_EVENTS];
1192
1192
  });
1193
- const onContentBlockStop = Effect.fn("AnthropicMessages.onContentBlockStop")(function* (state, event) {
1193
+ const onContentBlockStop = Effect.fnUntraced(function* (state, event) {
1194
1194
  if (event.index === undefined)
1195
1195
  return [state, NO_EVENTS];
1196
1196
  if (event.index in state.compactions) {
@@ -1246,7 +1246,7 @@ const onMessageDelta = (state, event) => {
1246
1246
  NO_EVENTS,
1247
1247
  ];
1248
1248
  };
1249
- const onMessageStop = Effect.fn("AnthropicMessages.onMessageStop")(function* (state) {
1249
+ const onMessageStop = Effect.fnUntraced(function* (state) {
1250
1250
  if (Object.keys(state.compactions).length)
1251
1251
  return yield* ProviderShared.eventError(ADAPTER, "Response ended with an incomplete compaction block");
1252
1252
  const result = yield* ToolStream.finishAll(ADAPTER, state.tools);
@@ -211,7 +211,7 @@ const lowerToolCall = (part, normalizeID) => ({
211
211
  input: removeEmptyToolInputKeys(part.input),
212
212
  },
213
213
  });
214
- const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent")(function* (part, documentNames) {
214
+ const lowerToolResultContent = Effect.fnUntraced(function* (part, documentNames) {
215
215
  if (part.result.type === "text" || part.result.type === "error")
216
216
  return [{ text: ProviderShared.toolResultText(part) }];
217
217
  if (part.result.type === "json")
@@ -227,7 +227,7 @@ const lowerToolResultContent = Effect.fn("BedrockConverse.lowerToolResultContent
227
227
  }
228
228
  return content;
229
229
  });
230
- const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (part, documentNames, normalizeID) {
230
+ const lowerToolResult = Effect.fnUntraced(function* (part, documentNames, normalizeID) {
231
231
  return {
232
232
  toolResult: {
233
233
  toolUseId: normalizeID(part.id),
@@ -238,7 +238,7 @@ const lowerToolResult = Effect.fn("BedrockConverse.lowerToolResult")(function* (
238
238
  });
239
239
  // Keep Claude and Nova tool-result images inline; put other models' images beside the result.
240
240
  const keepToolImagesInline = (id) => id.includes("anthropic.claude-") || id.includes("amazon.nova-");
241
- const lowerMessages = Effect.fn("BedrockConverse.lowerMessages")(function* (request, breakpoints) {
241
+ const lowerMessages = Effect.fnUntraced(function* (request, breakpoints) {
242
242
  const messages = [];
243
243
  const documentNames = new Set();
244
244
  // Mistral can reject replay IDs even when they satisfy Converse's broader ID syntax.
@@ -38,7 +38,7 @@ const decodeEvent = route.decodeFrame(SseEvent);
38
38
  /** Timestamps exist only on the SSE endpoint, so a `generate` that asks for them collects an SSE stream. */
39
39
  const usesSse = (request) => request.mode === "stream" || request.timestamps === true;
40
40
  const CONTAINERS = { pcm: "raw", wav: "wav", mp3: "mp3" };
41
- const outputFormat = Effect.fn("CartesiaSpeech.outputFormat")(function* (request) {
41
+ const outputFormat = Effect.fnUntraced(function* (request) {
42
42
  const sse = usesSse(request);
43
43
  const format = request.format ?? (sse ? "pcm" : "mp3");
44
44
  const container = CONTAINERS[format];
@@ -69,7 +69,7 @@ const fromRequest = Effect.fn("CartesiaSpeech.fromRequest")(function* (request)
69
69
  // ---------------------------------------------------------------------------
70
70
  // 6. Stream parsing
71
71
  // ---------------------------------------------------------------------------
72
- const onEvent = Effect.fn("CartesiaSpeech.onEvent")(function* (state, frame) {
72
+ const onEvent = Effect.fnUntraced(function* (state, frame) {
73
73
  const event = yield* decodeEvent(frame);
74
74
  if (event.type === "chunk" && event.data !== undefined)
75
75
  return SpeechStream.delta(state, event.data);
@@ -89,7 +89,7 @@ const onEvent = Effect.fn("CartesiaSpeech.onEvent")(function* (state, frame) {
89
89
  });
90
90
  return [state, []];
91
91
  });
92
- const finish = Effect.fn("CartesiaSpeech.finish")(function* (state, context) {
92
+ const finish = Effect.fnUntraced(function* (state, context) {
93
93
  if (usesSse(context.request) && !state.done)
94
94
  return yield* route.incomplete();
95
95
  const format = yield* outputFormat(context.request);
@@ -225,7 +225,7 @@ const mapUsage = (usage) => new Usage({
225
225
  providerMetadata: { cohere: usage },
226
226
  });
227
227
  // Lifecycle deltas open blocks on demand and ends are no-ops for closed blocks, so content-start needs no handling.
228
- const step = Effect.fn("CohereChat.step")(function* (state, event) {
228
+ const step = Effect.fnUntraced(function* (state, event) {
229
229
  const events = [];
230
230
  switch (event.type) {
231
231
  case "message-start":
@@ -32,7 +32,7 @@ const OUTPUT_FORMATS = {
32
32
  opus: "opus_48000_64",
33
33
  };
34
34
  /** WAV is served only by the non-streaming endpoints. */
35
- const outputFormat = Effect.fn("ElevenLabsSpeech.outputFormat")(function* (request) {
35
+ const outputFormat = Effect.fnUntraced(function* (request) {
36
36
  const format = request.providerOptions?.outputFormat ?? OUTPUT_FORMATS[request.format ?? "mp3"];
37
37
  if (format === undefined)
38
38
  return yield* route.unsupported("media.format", `${route.name} has no default output format for "${request.format}"; pass providerOptions.outputFormat`);
@@ -55,7 +55,7 @@ const path = (request) => `${PATH}/${encodeURIComponent(SpeechStream.voiceID(req
55
55
  // ---------------------------------------------------------------------------
56
56
  // 6. Stream parsing
57
57
  // ---------------------------------------------------------------------------
58
- const onRecord = Effect.fn("ElevenLabsSpeech.onRecord")(function* (state, frame) {
58
+ const onRecord = Effect.fnUntraced(function* (state, frame) {
59
59
  const record = yield* decodeRecord(frame);
60
60
  const [next, events] = SpeechStream.delta(state, record.audio_base64);
61
61
  const alignment = record.alignment;
@@ -80,7 +80,7 @@ const describeOutput = (format) => {
80
80
  const encoding = PCM_CODECS[codec];
81
81
  return encoding === undefined ? SpeechStream.container(codec, sampleRate) : SpeechStream.pcm(encoding, sampleRate);
82
82
  };
83
- const finish = Effect.fn("ElevenLabsSpeech.finish")(function* (state, context) {
83
+ const finish = Effect.fnUntraced(function* (state, context) {
84
84
  const requestID = context.http.headers["request-id"];
85
85
  return yield* SpeechStream.finish(route, state, {
86
86
  ...describeOutput(yield* outputFormat(context.request)),
@@ -201,7 +201,7 @@ const lowerToolConfig = (toolChoice) => ProviderShared.matchToolChoice("Gemini",
201
201
  required: () => ({ functionCallingConfig: { mode: "ANY" } }),
202
202
  tool: (name) => ({ functionCallingConfig: { mode: "ANY", allowedFunctionNames: [name] } }),
203
203
  });
204
- const lowerContentPart = Effect.fn("Gemini.lowerContentPart")(function* (part) {
204
+ const lowerContentPart = Effect.fnUntraced(function* (part) {
205
205
  if (part.type === "text")
206
206
  return { text: part.text };
207
207
  return yield* GeminiGenerateContent.mediaPart("Gemini", part.media);
@@ -217,7 +217,7 @@ const lowerToolCall = (part, omitIds, metadataKey) => ({
217
217
  functionCall: { ...(omitIds ? {} : { id: part.id }), name: part.name, args: part.input },
218
218
  thoughtSignature: thoughtSignature(part.providerMetadata, metadataKey),
219
219
  });
220
- const lowerMessages = Effect.fn("Gemini.lowerMessages")(function* (request) {
220
+ const lowerMessages = Effect.fnUntraced(function* (request) {
221
221
  const contents = [];
222
222
  const metadataKey = request.model.route.providerMetadataKey ?? String(request.model.provider);
223
223
  const omitCallIds = omitsFunctionCallIds(request.model.id);
@@ -140,7 +140,7 @@ const Event = Schema.Union([
140
140
  // =============================================================================
141
141
  // Request Body Construction
142
142
  // =============================================================================
143
- const mediaContent = Effect.fn("GoogleInteractions.mediaContent")(function* (asset) {
143
+ const mediaContent = Effect.fnUntraced(function* (asset) {
144
144
  if (asset.kind !== "image" &&
145
145
  asset.kind !== "audio" &&
146
146
  asset.kind !== "video" &&
@@ -160,7 +160,7 @@ const signature = (metadata, key) => {
160
160
  ? value.interactionSignature
161
161
  : undefined;
162
162
  };
163
- const lowerMessages = Effect.fn("GoogleInteractions.lowerMessages")(function* (request) {
163
+ const lowerMessages = Effect.fnUntraced(function* (request) {
164
164
  const steps = [];
165
165
  const key = request.model.route.providerMetadataKey ?? String(request.model.provider);
166
166
  for (const message of request.messages) {
@@ -223,7 +223,7 @@ const lowerMessages = Effect.fn("GoogleInteractions.lowerMessages")(function* (r
223
223
  }
224
224
  return steps;
225
225
  });
226
- const lowerToolResult = Effect.fn("GoogleInteractions.lowerToolResult")(function* (part) {
226
+ const lowerToolResult = Effect.fnUntraced(function* (part) {
227
227
  if (part.result.type === "json" && ProviderShared.isRecord(part.result.value))
228
228
  return part.result.value;
229
229
  if (part.result.type !== "content")
@@ -306,7 +306,7 @@ const mapUsage = (usage, key) => {
306
306
  providerMetadata: { [key]: usage },
307
307
  });
308
308
  };
309
- const onStart = Effect.fn("GoogleInteractions.onStart")(function* (state, index, step) {
309
+ const onStart = Effect.fnUntraced(function* (state, index, step) {
310
310
  const events = [];
311
311
  let lifecycle = Lifecycle.stepStart(state.lifecycle, events);
312
312
  let tools = state.tools;
@@ -340,7 +340,7 @@ const onStart = Effect.fn("GoogleInteractions.onStart")(function* (state, index,
340
340
  return yield* ProviderShared.eventError(ADAPTER, `Unsupported Interactions step: ${step.type}`, encodeJson(step));
341
341
  return [{ ...state, lifecycle, tools, steps: { ...state.steps, [index]: step } }, events];
342
342
  });
343
- const onDelta = Effect.fn("GoogleInteractions.onDelta")(function* (state, index, delta) {
343
+ const onDelta = Effect.fnUntraced(function* (state, index, delta) {
344
344
  const step = state.steps[index];
345
345
  if (!step)
346
346
  return yield* ProviderShared.eventError(ADAPTER, "Interactions delta without step.start", encodeJson(delta));
@@ -375,7 +375,7 @@ const onDelta = Effect.fn("GoogleInteractions.onDelta")(function* (state, index,
375
375
  }
376
376
  return yield* ProviderShared.eventError(ADAPTER, `Unsupported Interactions delta: ${delta.type}`, encodeJson(delta));
377
377
  });
378
- const onStop = Effect.fn("GoogleInteractions.onStop")(function* (state, index) {
378
+ const onStop = Effect.fnUntraced(function* (state, index) {
379
379
  const step = state.steps[index];
380
380
  if (!step)
381
381
  return yield* ProviderShared.eventError(ADAPTER, "Interactions step.stop without step.start");
@@ -393,7 +393,7 @@ const onStop = Effect.fn("GoogleInteractions.onStop")(function* (state, index) {
393
393
  const result = yield* ToolStream.finish(ADAPTER, state.tools, index);
394
394
  return [{ ...state, tools: result.tools }, result.events ?? []];
395
395
  });
396
- const step = Effect.fn("GoogleInteractions.step")(function* (state, event) {
396
+ const step = Effect.fnUntraced(function* (state, event) {
397
397
  switch (event.event_type) {
398
398
  case "step.start":
399
399
  return yield* onStart(state, event.index, event.step);
@@ -42,7 +42,7 @@ const fromRequest = Effect.fn("GoogleSpeech.fromRequest")(function* (request) {
42
42
  // ---------------------------------------------------------------------------
43
43
  // 6. Stream parsing
44
44
  // ---------------------------------------------------------------------------
45
- const step = Effect.fn("GoogleSpeech.step")(function* (state, frame) {
45
+ const step = Effect.fnUntraced(function* (state, frame) {
46
46
  const chunk = yield* decodeChunk(frame);
47
47
  const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
48
48
  if (blocked !== undefined)
@@ -70,7 +70,7 @@ const turn = (part) => {
70
70
  },
71
71
  };
72
72
  };
73
- const step = Effect.fn("GoogleTranscription.step")(function* (state, frame) {
73
+ const step = Effect.fnUntraced(function* (state, frame) {
74
74
  const chunk = yield* decodeChunk(frame);
75
75
  const blocked = GeminiGenerateContent.blocked(route.name, chunk, frame);
76
76
  if (blocked !== undefined)
@@ -74,7 +74,7 @@ const HOSTED_TOOLS = {
74
74
  image_generation_call: {
75
75
  name: "image_generation",
76
76
  input: () => ({}),
77
- result: Effect.fn("MetaResponses.imageResult")(function* (raw) {
77
+ result: Effect.fnUntraced(function* (raw) {
78
78
  const item = yield* Schema.decodeUnknownEffect(ImageItem)(raw).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "Meta returned an invalid image item", ProviderShared.encodeJson(raw), cause)));
79
79
  if (item.error !== undefined && item.error !== null)
80
80
  return { type: "error", value: item.error };
@@ -92,7 +92,7 @@ const HOSTED_TOOLS = {
92
92
  }),
93
93
  },
94
94
  };
95
- const onEvent = Effect.fn("MetaResponses.onEvent")(function* (state, input) {
95
+ const onEvent = Effect.fnUntraced(function* (state, input) {
96
96
  const event = OpenResponses.normalize(state, input);
97
97
  if (event.type === "response.output_item.done" && event.item && ResponsesHostedTools.isItem(event.item, HOSTED_TOOLS))
98
98
  return yield* ResponsesHostedTools.onDone(state, event.item, HOSTED_TOOLS);
@@ -116,7 +116,7 @@ const onEvent = Effect.fn("MetaResponses.onEvent")(function* (state, input) {
116
116
  : item),
117
117
  ];
118
118
  });
119
- const step = Effect.fn("MetaResponses.step")(function* (state, input) {
119
+ const step = Effect.fnUntraced(function* (state, input) {
120
120
  const completedItems = new Set(state.completedItems);
121
121
  const event = OpenResponses.normalize(state, input);
122
122
  if (event.type === "response.output_item.done" && event.item && completedItems.has(event.item.id))
@@ -143,7 +143,7 @@ const MistralEvent = Schema.StructWithRest(Schema.Struct({
143
143
  error: optionalNull(MistralError),
144
144
  }), [Schema.Record(Schema.String, Schema.Unknown)]);
145
145
  const MistralStreamEvent = Schema.Union([Schema.Literal(DONE), Protocol.jsonEvent(MistralEvent)]);
146
- const lowerMedia = Effect.fn("MistralChat.lowerMedia")(function* (part) {
146
+ const lowerMedia = Effect.fnUntraced(function* (part) {
147
147
  const mime = part.media.mediaType.toLowerCase();
148
148
  const url = ProviderShared.mediaUrl(part.media) ??
149
149
  (yield* ProviderShared.requireInlineMedia("Mistral Chat", part.media)).dataUrl;
@@ -153,7 +153,7 @@ const lowerMedia = Effect.fn("MistralChat.lowerMedia")(function* (part) {
153
153
  return { type: "document_url", document_url: url };
154
154
  return yield* ProviderShared.invalidRequest(`Mistral Chat does not support media type ${part.media.mediaType}`);
155
155
  });
156
- const lowerUser = Effect.fn("MistralChat.lowerUser")(function* (message) {
156
+ const lowerUser = Effect.fnUntraced(function* (message) {
157
157
  const content = [];
158
158
  for (const part of message.content) {
159
159
  if (part.type === "text") {
@@ -175,7 +175,7 @@ const lowerToolCall = (part, normalizeID) => ({
175
175
  type: "function",
176
176
  function: { name: part.name, arguments: ProviderShared.encodeJson(part.input) },
177
177
  });
178
- const lowerAssistant = Effect.fn("MistralChat.lowerAssistant")(function* (message, normalizeID, prefix) {
178
+ const lowerAssistant = Effect.fnUntraced(function* (message, normalizeID, prefix) {
179
179
  const structured = message.content.some((part) => part.type === "reasoning" && isMistralThinkingContent(part.providerMetadata?.mistral?.thinking));
180
180
  const content = [];
181
181
  const text = [];
@@ -211,7 +211,7 @@ const lowerAssistant = Effect.fn("MistralChat.lowerAssistant")(function* (messag
211
211
  ...(prefix ? { prefix: true } : {}),
212
212
  };
213
213
  });
214
- const lowerToolResults = Effect.fn("MistralChat.lowerToolResults")(function* (message, normalizeID) {
214
+ const lowerToolResults = Effect.fnUntraced(function* (message, normalizeID) {
215
215
  const output = [];
216
216
  for (const part of message.content) {
217
217
  if (part.type !== "tool-result")
@@ -244,7 +244,7 @@ const lowerToolResults = Effect.fn("MistralChat.lowerToolResults")(function* (me
244
244
  }
245
245
  return output;
246
246
  });
247
- const lowerMessages = Effect.fn("MistralChat.lowerMessages")(function* (request) {
247
+ const lowerMessages = Effect.fnUntraced(function* (request) {
248
248
  const normalizeID = MistralToolID.normalizer(request);
249
249
  const messages = request.system.length === 0
250
250
  ? []
@@ -451,7 +451,7 @@ const toolText = (tool) => {
451
451
  return value;
452
452
  return value === null || value === undefined ? "" : ProviderShared.encodeJson(value);
453
453
  };
454
- const appendTools = Effect.fn("MistralChat.appendTools")(function* (initial, events, deltas) {
454
+ const appendTools = Effect.fnUntraced(function* (initial, events, deltas) {
455
455
  if (deltas.length === 0)
456
456
  return initial;
457
457
  let state = closeActive(initial, events);
@@ -506,7 +506,7 @@ const hasLateContent = (event) => {
506
506
  return true;
507
507
  return (delta?.tool_calls ?? []).some((tool) => Boolean(tool.id) || Boolean(tool.function?.name) || tool.function?.arguments !== undefined);
508
508
  };
509
- const step = Effect.fn("MistralChat.step")(function* (state, event) {
509
+ const step = Effect.fnUntraced(function* (state, event) {
510
510
  if (event.error) {
511
511
  const body = ProviderShared.encodeJson(event);
512
512
  return yield* new AIError({
@@ -559,7 +559,7 @@ const step = Effect.fn("MistralChat.step")(function* (state, event) {
559
559
  events,
560
560
  ];
561
561
  });
562
- const finishEvents = Effect.fn("MistralChat.finishEvents")(function* (state) {
562
+ const finishEvents = Effect.fnUntraced(function* (state) {
563
563
  if (!state.finishReason)
564
564
  return yield* new AIError({
565
565
  reason: new InvalidProviderOutputError({
@@ -289,7 +289,7 @@ const BASE_ADAPTER = { id: ADAPTER, name: NAME };
289
289
  // =============================================================================
290
290
  // Request Lowering
291
291
  // =============================================================================
292
- export const lowerTool = Effect.fn("OpenResponses.lowerTool")(function* (protocolName, tool) {
292
+ export const lowerTool = Effect.fnUntraced(function* (protocolName, tool) {
293
293
  if (tool.native !== undefined)
294
294
  return yield* ProviderShared.invalidRequest(`${protocolName} does not support provider-native tool ${tool.name}`);
295
295
  return {
@@ -343,12 +343,15 @@ const lowerReasoning = (part, providerMetadataKey) => {
343
343
  encrypted_content: encryptedContent,
344
344
  };
345
345
  };
346
- const lowerMedia = Effect.fn("OpenResponses.lowerMedia")(function* (part, request, adapter, target) {
346
+ const decodeImageDetail = ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenResponsesInputImage.fields.detail));
347
+ const decodeMessageMetadata = ProviderShared.validateWith(Schema.decodeUnknownEffect(MessageMetadata));
348
+ const lowerMedia = Effect.fnUntraced(function* (part, request, adapter, target) {
347
349
  const media = part.media.inline();
348
350
  const providerMedia = adapter.lowerMedia?.({ part, media, request });
349
351
  if (providerMedia)
350
352
  return providerMedia;
351
- const detail = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(OpenResponsesInputImage.fields.detail))(part.providerMetadata?.[metadataKey(request.model)]?.detail);
353
+ const rawDetail = part.providerMetadata?.[metadataKey(request.model)]?.detail;
354
+ const detail = rawDetail === undefined ? undefined : yield* decodeImageDetail(rawDetail);
352
355
  const mime = part.media.mediaType.toLowerCase();
353
356
  const url = ProviderShared.mediaUrl(part.media);
354
357
  const location = url ?? (yield* ProviderShared.requireInlineMedia(adapter.name, part.media)).dataUrl;
@@ -403,11 +406,12 @@ const lowerToolResultOutput = Effect.fnUntraced(function* (part, request, adapte
403
406
  return yield* Effect.forEach(content, (item) => lowerToolResultContentItem(item, request, adapter));
404
407
  });
405
408
  const DEFAULT_EFFORT = "medium";
406
- const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (request, adapter) {
409
+ const lowerMessages = Effect.fnUntraced(function* (request, adapter) {
407
410
  const input = [];
408
411
  const providerMetadataKey = metadataKey(request.model);
409
412
  for (const message of request.messages) {
410
- const metadata = yield* ProviderShared.validateWith(Schema.decodeUnknownEffect(Schema.UndefinedOr(MessageMetadata)))(message.providerMetadata?.[providerMetadataKey]);
413
+ const rawMetadata = message.providerMetadata?.[providerMetadataKey];
414
+ const metadata = rawMetadata === undefined ? undefined : yield* decodeMessageMetadata(rawMetadata);
411
415
  if (message.role === "system") {
412
416
  const update = effortUpdate(message);
413
417
  if (update) {
@@ -555,7 +559,7 @@ const lowerMessages = Effect.fn("OpenResponses.lowerMessages")(function* (reques
555
559
  }
556
560
  return input;
557
561
  });
558
- export const lowerConversation = Effect.fn("OpenResponses.lowerConversation")(function* (request, adapter) {
562
+ export const lowerConversation = Effect.fnUntraced(function* (request, adapter) {
559
563
  const instructions = ProviderShared.joinText(request.system);
560
564
  return {
561
565
  model: request.model.id,
@@ -898,7 +902,7 @@ const onReasoningSummaryPartDone = (state, event) => {
898
902
  NO_EVENTS,
899
903
  ];
900
904
  };
901
- const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgumentsDelta")(function* (state, event) {
905
+ const onFunctionCallArgumentsDelta = Effect.fnUntraced(function* (state, event) {
902
906
  if (event.item_id === undefined)
903
907
  return [state, NO_EVENTS];
904
908
  const tool = state.tools[event.item_id];
@@ -923,7 +927,7 @@ const onFunctionCallArgumentsDelta = Effect.fn("OpenResponses.onFunctionCallArgu
923
927
  events.push(...result.events);
924
928
  return [{ ...state, lifecycle, tools: result.tools }, events];
925
929
  });
926
- const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (state, item) {
930
+ const onOutputItemDone = Effect.fnUntraced(function* (state, item) {
927
931
  if (!item)
928
932
  return [state, NO_EVENTS];
929
933
  if (item.type === "compaction") {
@@ -1051,7 +1055,7 @@ const onOutputItemDone = Effect.fn("OpenResponses.onOutputItemDone")(function* (
1051
1055
  }
1052
1056
  return [state, NO_EVENTS];
1053
1057
  });
1054
- const onResponseFinish = Effect.fn("OpenResponses.onResponseFinish")(function* (state, event) {
1058
+ const onResponseFinish = Effect.fnUntraced(function* (state, event) {
1055
1059
  let current = state;
1056
1060
  const events = [];
1057
1061
  if (event.type === "response.completed") {
@@ -233,7 +233,7 @@ const lowerToolCall = (part, options) => ({
233
233
  },
234
234
  extra_content: decodeExtraContent(part.providerMetadata?.[options.providerMetadataKey]?.extraContent),
235
235
  });
236
- const lowerMedia = Effect.fn("OpenAIChat.lowerMedia")(function* (part) {
236
+ const lowerMedia = Effect.fnUntraced(function* (part) {
237
237
  // Chat Completions accepts PDFs, and no other documents, as inline `file` parts; file URLs are not supported.
238
238
  if (part.media.mediaType.toLowerCase() === "application/pdf")
239
239
  return {
@@ -273,7 +273,7 @@ const lowerReasoningDetail = (detail) => {
273
273
  return detail;
274
274
  };
275
275
  const isKimiDetail = (detail) => detail.type === "summary" || detail.type === "encrypted";
276
- const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message, options) {
276
+ const lowerUserMessage = Effect.fnUntraced(function* (message, options) {
277
277
  const content = [];
278
278
  for (const part of message.content) {
279
279
  if (part.type === "text") {
@@ -293,7 +293,7 @@ const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (mes
293
293
  };
294
294
  return { role: "user", content };
295
295
  });
296
- const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(function* (message, configuredField, requireReasoning, options) {
296
+ const lowerAssistantMessage = Effect.fnUntraced(function* (message, configuredField, requireReasoning, options) {
297
297
  const content = [];
298
298
  const reasoning = [];
299
299
  const toolCalls = [];
@@ -359,7 +359,7 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func
359
359
  return result;
360
360
  return { ...result, [field]: reasoningText };
361
361
  });
362
- const lowerToolMessages = Effect.fn("OpenAIChat.lowerToolMessages")(function* (message, options) {
362
+ const lowerToolMessages = Effect.fnUntraced(function* (message, options) {
363
363
  const messages = [];
364
364
  const attachments = [];
365
365
  for (const part of message.content) {
@@ -383,14 +383,14 @@ const toolMessage = (toolCallID, text, cacheControl) => ({
383
383
  tool_call_id: toolCallID,
384
384
  content: cacheControl === undefined ? text : [{ type: "text", text, cache_control: cacheControl }],
385
385
  });
386
- const lowerMessage = Effect.fn("OpenAIChat.lowerMessage")(function* (message, reasoningField, requireReasoning, options) {
386
+ const lowerMessage = Effect.fnUntraced(function* (message, reasoningField, requireReasoning, options) {
387
387
  if (message.role === "user")
388
388
  return [yield* lowerUserMessage(message, options)];
389
389
  if (message.role === "assistant")
390
390
  return [yield* lowerAssistantMessage(message, reasoningField, requireReasoning, options)];
391
391
  return (yield* lowerToolMessages(message, options)).messages;
392
392
  });
393
- const lowerMessages = Effect.fn("OpenAIChat.lowerMessages")(function* (request, options) {
393
+ const lowerMessages = Effect.fnUntraced(function* (request, options) {
394
394
  const system = request.system.length === 0
395
395
  ? []
396
396
  : request.system.some((part) => part.cache !== undefined) && options.cacheControl !== undefined
@@ -674,7 +674,7 @@ export const fromRequest = Effect.fn("OpenAIChat.fromRequest")(function* (reques
674
674
  // Streaming parsers are small state machines: every event returns a new state
675
675
  // plus the common `LLMEvent`s produced by that event. Tool calls are accumulated
676
676
  // because OpenAI streams JSON arguments across multiple deltas.
677
- const mapFinishReason = Effect.fn("OpenAIChat.mapFinishReason")(function* (event, reason) {
677
+ const mapFinishReason = Effect.fnUntraced(function* (event, reason) {
678
678
  switch (reason) {
679
679
  case "error":
680
680
  return yield* new AIError({
@@ -981,7 +981,7 @@ const step = (state, event) => Effect.gen(function* () {
981
981
  events,
982
982
  ];
983
983
  });
984
- const finishEvents = Effect.fn("OpenAIChat.finishEvents")(function* (state) {
984
+ const finishEvents = Effect.fnUntraced(function* (state) {
985
985
  if (state.finishReason === undefined && state.requireFinishReason)
986
986
  return yield* new AIError({
987
987
  reason: new InvalidProviderOutputError({
@@ -126,7 +126,7 @@ const info = (format, size) => {
126
126
  const eventImage = (frame, label, data, format, size) => MediaInput.decodedAsset((message, cause) => route.frameError(message, frame, cause), label, data, `image/${format}`, {
127
127
  info: info(format, size),
128
128
  });
129
- const onEvent = Effect.fn("OpenAIImages.onEvent")(function* (state, frame) {
129
+ const onEvent = Effect.fnUntraced(function* (state, frame) {
130
130
  const event = yield* decodeEvent(frame);
131
131
  const format = event.output_format;
132
132
  if ("partial_image_index" in event) {
@@ -146,7 +146,7 @@ const onEvent = Effect.fn("OpenAIImages.onEvent")(function* (state, frame) {
146
146
  [ImageOutputEvent.make({ index: state.completed, image })],
147
147
  ];
148
148
  });
149
- const onDocument = Effect.fn("OpenAIImages.onDocument")(function* (frame) {
149
+ const onDocument = Effect.fnUntraced(function* (frame) {
150
150
  const invalid = (message, cause) => route.frameError(message, frame.document, cause);
151
151
  const decoded = yield* decodeDocument(frame.document).pipe(Effect.mapError((cause) => invalid(`${route.name} returned an invalid response`, cause)));
152
152
  const format = decoded.output_format ?? frame.requested ?? "png";
@@ -126,7 +126,7 @@ const nativeImageTool = (tool) => {
126
126
  const native = nativeImageToolInput(tool);
127
127
  return Schema.is(OpenAIResponsesImageGenerationTool)(native) ? native : undefined;
128
128
  };
129
- const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool) {
129
+ const lowerTool = Effect.fnUntraced(function* (tool) {
130
130
  const native = nativeImageToolInput(tool);
131
131
  if (native !== undefined) {
132
132
  if (Schema.is(OpenAIResponsesImageGenerationTool)(native))
@@ -137,7 +137,7 @@ const lowerTool = Effect.fn("OpenAIResponses.lowerTool")(function* (tool) {
137
137
  });
138
138
  // Native namespaces hold only function tools, so deeper levels flatten into
139
139
  // the leaf names the same way non-native protocols flatten the whole tree.
140
- const lowerToolEntry = Effect.fn("OpenAIResponses.lowerToolEntry")(function* (tool) {
140
+ const lowerToolEntry = Effect.fnUntraced(function* (tool) {
141
141
  if (tool.type === "tool")
142
142
  return yield* lowerTool(tool);
143
143
  // OpenAI requires a namespace description; fall back to a generic one so a
@@ -191,7 +191,7 @@ const checkpointBody = {
191
191
  };
192
192
  }),
193
193
  };
194
- const hostedToolResult = Effect.fn("OpenAIResponses.hostedToolResult")(function* (item) {
194
+ const hostedToolResult = Effect.fnUntraced(function* (item) {
195
195
  const isError = item.error !== undefined && item.error !== null;
196
196
  if (item.type === "image_generation_call" && item.result) {
197
197
  yield* Effect.fromResult(Encoding.decodeBase64(item.result)).pipe(Effect.mapError((cause) => ProviderShared.eventError(ADAPTER, "OpenAI Responses returned invalid image base64", undefined, cause)));
@@ -51,7 +51,7 @@ const fromRequest = Effect.fn("OpenAISpeech.fromRequest")(function* (request) {
51
51
  // 6. Stream parsing
52
52
  // ---------------------------------------------------------------------------
53
53
  const isSse = (body) => body.type === "json" && body.value.stream_format === "sse";
54
- const onEvent = Effect.fn("OpenAISpeech.onEvent")(function* (state, frame) {
54
+ const onEvent = Effect.fnUntraced(function* (state, frame) {
55
55
  const event = yield* decodeEvent(frame);
56
56
  if (event.type === "speech.audio.delta")
57
57
  return SpeechStream.delta(state, event.audio);
@@ -131,7 +131,7 @@ const segment = (value) => ({
131
131
  endSeconds: value.end,
132
132
  speaker: value.speaker,
133
133
  });
134
- const onEvent = Effect.fn("OpenAITranscription.onEvent")(function* (state, frame) {
134
+ const onEvent = Effect.fnUntraced(function* (state, frame) {
135
135
  if (!EVENT_TYPES.has((yield* decodeEventType(frame)).type))
136
136
  return [state, []];
137
137
  const event = yield* decodeEvent(frame);
@@ -108,7 +108,7 @@ export const wrapSystemUpdate = (parts) => `<system-update>\n${escapeSystemUpdat
108
108
  * raw retrieved, tool, or web content into privileged updates: keep untrusted
109
109
  * data in ordinary user/tool messages instead.
110
110
  */
111
- export const systemUpdateText = Effect.fn("ProviderShared.systemUpdateText")(function* (route, message) {
111
+ export const systemUpdateText = Effect.fnUntraced(function* (route, message) {
112
112
  const content = [];
113
113
  for (const part of message.content) {
114
114
  if (!supportsContent(part, ["text"]))
@@ -118,7 +118,7 @@ export const systemUpdateText = Effect.fn("ProviderShared.systemUpdateText")(fun
118
118
  return content;
119
119
  });
120
120
  /** Lower an unsupported privileged update into visible, in-order user text. */
121
- export const wrappedSystemUpdate = Effect.fn("ProviderShared.wrappedSystemUpdate")(function* (route, message) {
121
+ export const wrappedSystemUpdate = Effect.fnUntraced(function* (route, message) {
122
122
  const content = yield* systemUpdateText(route, message);
123
123
  return { type: "text", text: wrapSystemUpdate(content), cache: content.at(-1)?.cache };
124
124
  });
@@ -30,7 +30,7 @@ const Started = Schema.Struct({ id: Schema.String });
30
30
  /** `sd3.5-*` ids share the `sd3` endpoint and select the model with a form field. */
31
31
  const endpoint = (model) => (model.startsWith("sd3") ? "sd3" : model);
32
32
  const RESERVED_FORM_FIELDS = new Set(["image", "prompt", "mode", "model"]);
33
- const form = Effect.fn("StabilityImages.form")(function* (identity, fields, native, source) {
33
+ const form = Effect.fnUntraced(function* (identity, fields, native, source) {
34
34
  if ((source?.length ?? 0) > 1)
35
35
  return yield* identity.unsupported("media.images", `${identity.name} takes one source image`);
36
36
  const body = new FormData();
@@ -61,7 +61,7 @@ function documentName(filename, names) {
61
61
  names.add(name);
62
62
  return name;
63
63
  }
64
- const mediaBase64 = Effect.fn("BedrockMedia.mediaBase64")(function* (part) {
64
+ const mediaBase64 = Effect.fnUntraced(function* (part) {
65
65
  const media = yield* ProviderShared.requireInlineMedia("Bedrock Converse", part.media);
66
66
  const bytes = yield* Effect.fromResult(Encoding.decodeBase64(media.base64)).pipe(Effect.mapError((cause) => ProviderShared.invalidRequest("Bedrock Converse media data must be valid base64", cause)));
67
67
  return Encoding.encodeBase64(bytes);
@@ -71,7 +71,7 @@ const mediaBase64 = Effect.fn("BedrockMedia.mediaBase64")(function* (part) {
71
71
  // document block. Image MIME types not in `IMAGE_FORMATS` (e.g. `image/svg+xml`)
72
72
  // get an image-specific error so the caller knows it's a format-support issue,
73
73
  // not a kind-detection issue.
74
- export const lower = Effect.fn("BedrockMedia.lower")(function* (part, documentNames) {
74
+ export const lower = Effect.fnUntraced(function* (part, documentNames) {
75
75
  const mime = part.media.mediaType.toLowerCase();
76
76
  const imageFormat = IMAGE_FORMATS[mime];
77
77
  if (imageFormat) {
@@ -4,7 +4,7 @@ import { Protocol } from "../../route/protocol.js";
4
4
  import { CompactionCheckpointResponse, LLMEvent, LLMRequest } from "../../schema/index.js";
5
5
  import { OpenResponses } from "../open-responses.js";
6
6
  import { ProviderShared } from "../shared.js";
7
- const onOutputItem = Effect.fn("ResponsesCheckpoint.onOutputItem")(function* (state, input) {
7
+ const onOutputItem = Effect.fnUntraced(function* (state, input) {
8
8
  const event = OpenResponses.normalize(state.parser, input);
9
9
  const item = event.item;
10
10
  if (!item)
@@ -49,7 +49,7 @@ export const make = (body) => Effect.fn("ResponsesCheckpoint.execute")(function*
49
49
  checkpoints: {},
50
50
  }),
51
51
  terminal: OpenResponses.terminal,
52
- step: Effect.fn("ResponsesCheckpoint.step")(function* (state, event) {
52
+ step: Effect.fnUntraced(function* (state, event) {
53
53
  if (event.response?.id && state.responseID && event.response.id !== state.responseID)
54
54
  return yield* ProviderShared.eventError(source.id, "Compaction response ID changed during execution");
55
55
  if (event.type === "response.created")
@@ -3,7 +3,7 @@ import { LLMEvent } from "../../schema/index.js";
3
3
  import { OpenResponses } from "../open-responses.js";
4
4
  import { Lifecycle } from "./lifecycle.js";
5
5
  export const isItem = (item, tools) => item.type in tools;
6
- export const onDone = Effect.fn("ResponsesHostedTools.onDone")(function* (state, item, tools) {
6
+ export const onDone = Effect.fnUntraced(function* (state, item, tools) {
7
7
  const tool = tools[item.type];
8
8
  if (!tool)
9
9
  return [state, []];
@@ -14,32 +14,32 @@ export const sseFraming = (bytes, events, includeDone = false) => bytes.pipe(Str
14
14
  return {
15
15
  output,
16
16
  parser: makeParser((event) => {
17
- if (event._tag === "Event")
18
- output.push(event);
17
+ if (event._tag === "Event" &&
18
+ (events === undefined || events.has(event.event)) &&
19
+ event.data.length > 0 &&
20
+ // Some OpenAI-compatible proxies serialize an empty flush as a bare
21
+ // `data: null`, between events or after `[DONE]`. No protocol has a
22
+ // null event, so it carries nothing and must not abort the stream.
23
+ event.data !== "null" &&
24
+ // Vertex AI partner models (e.g. `xai/grok-4.6`) send their SSE
25
+ // keepalive comment as `data: : keepalive` while reasoning.
26
+ event.data !== ": keepalive" &&
27
+ (event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message")))
28
+ output.push(event.data);
19
29
  }),
20
30
  };
21
- }, (state, chunk) => Effect.gen(function* () {
31
+ }, (state, chunk) => {
22
32
  const error = state.parser.feed(chunk);
23
- if (error)
24
- return yield* new AIError({
25
- reason: new InvalidProviderOutputError({
26
- route: "sse",
27
- message: error.message,
28
- body: chunk,
29
- cause: error,
30
- }),
31
- });
32
- return [state, state.output.splice(0)];
33
- })), Stream.filter((event) => (events === undefined || events.has(event.event)) &&
34
- event.data.length > 0 &&
35
- // Some OpenAI-compatible proxies serialize an empty flush as a bare
36
- // `data: null`, between events or after `[DONE]`. No protocol has a
37
- // null event, so it carries nothing and must not abort the stream.
38
- event.data !== "null" &&
39
- // Vertex AI partner models (e.g. `xai/grok-4.6`) send their SSE
40
- // keepalive comment as `data: : keepalive` while reasoning.
41
- event.data !== ": keepalive" &&
42
- (event.data !== "[DONE]" || includeDone || (events !== undefined && event.event !== "message"))), Stream.map((event) => event.data));
33
+ if (!error)
34
+ return Effect.succeed([state, state.output.splice(0)]);
35
+ const reason = new InvalidProviderOutputError({
36
+ route: "sse",
37
+ message: error.message,
38
+ body: chunk,
39
+ cause: error,
40
+ });
41
+ return Effect.fail(new AIError({ reason }));
42
+ }));
43
43
  /** Server-Sent Events framing. Used by every JSON-streaming HTTP provider. */
44
44
  export const sse = { id: "sse", frame: sseFraming };
45
45
  /** Server-Sent Events framing that retains the conventional `[DONE]` sentinel. */
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-20536",
3
+ "version": "0.0.0-dev-20538",
4
4
  "name": "@opencode/ai",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "devDependencies": {
35
35
  "@clack/prompts": "1.0.0-alpha.1",
36
36
  "@effect/platform-node": "4.0.0-rc.112",
37
- "@opencode/http-recorder": "0.0.0-dev-20536",
37
+ "@opencode/http-recorder": "0.0.0-dev-20538",
38
38
  "@tsconfig/bun": "1.0.9",
39
39
  "@types/bun": "1.4.0",
40
40
  "@typescript/native-preview": "7.0.0-dev.20251207.1",
@@ -44,7 +44,7 @@
44
44
  "@aws-sdk/credential-providers": "3.1057.0",
45
45
  "@smithy/eventstream-codec": "4.2.14",
46
46
  "@smithy/util-utf8": "4.2.2",
47
- "@opencode/schema": "0.0.0-dev-20536",
47
+ "@opencode/schema": "0.0.0-dev-20538",
48
48
  "aws4fetch": "1.0.20",
49
49
  "effect": "4.0.0-rc.112",
50
50
  "google-auth-library": "10.5.0"