@effect-uai/responses 0.18.0 → 0.19.0-dev-20261110065930

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.
package/README.md CHANGED
@@ -38,7 +38,7 @@ provider-agnostic loop bodies).
38
38
 
39
39
  ## Docs
40
40
 
41
- <https://effect-uai.betalyra.com/providers/responses/>
41
+ <https://effect-uai.com/providers/responses/>
42
42
 
43
43
  ## License
44
44
 
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;cAsCa,gBAAc,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;cAkEd,gBAAc,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAQf,wBAAwB,eAAe;cA6BtC,uBAAqB,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KActB,+BAA+B,sBAAsB;;cAoGpD,eAAY,OAChB,cAAc,iBACpB,cAAc;cAiBJ,iBAAc,MAAU,mBAAiB,cAAc;cAwEvD,oBAAiB,SAAa,0BAAwB"}
1
+ {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;cA6Ca,gBAAc,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;cAkEd,gBAAc,OAAA,gBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAQf,wBAAwB,eAAe;cA6BtC,uBAAqB,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KActB,+BAA+B,sBAAsB;;cA+HpD,eAAY,OAChB,cAAc,iBACpB,cAAc;cAiBJ,iBAAc,MAAU,mBAAiB,cAAc;cAwEvD,oBAAiB,SAAa,0BAAwB"}
package/dist/codec.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
2
  import { Array, Match, Option, Result, Schema, pipe } from "effect";
3
3
  import { Base64 } from "effect/encoding";
4
+ import * as ToolOutput from "@effect-uai/core/ToolOutput";
4
5
  //#region src/codec.ts
5
6
  var codec_exports = /* @__PURE__ */ __exportAll({
6
7
  WireAnnotation: () => WireAnnotation,
@@ -148,6 +149,27 @@ const passthrough = (item) => pipe(decodeResponsesData(item.providerData), Resul
148
149
  * is fine - the model just dereferences `image_url`.
149
150
  */
150
151
  const imageSourceToUrl = Match.type().pipe(Match.tag("url", (s) => s.url), Match.tag("base64", (s) => `data:${s.mimeType};base64,${s.base64}`), Match.tag("bytes", (s) => `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`), Match.exhaustive);
152
+ const fileSourceToInput = Match.type().pipe(Match.tag("url", (s) => ({ file_url: s.url })), Match.tag("base64", (s) => ({ file_data: `data:${s.mimeType};base64,${s.base64}` })), Match.tag("bytes", (s) => ({ file_data: `data:${s.mimeType};base64,${Base64.encode(s.bytes)}` })), Match.exhaustive);
153
+ const toolOutputPart = Match.type().pipe(Match.discriminatorsExhaustive("type")({
154
+ input_text: (p) => ({
155
+ type: "input_text",
156
+ text: ToolOutput.partText(p)
157
+ }),
158
+ input_json: (p) => ({
159
+ type: "input_text",
160
+ text: ToolOutput.partText(p)
161
+ }),
162
+ input_image: (p) => ({
163
+ type: "input_image",
164
+ image_url: imageSourceToUrl(p.source)
165
+ }),
166
+ input_file: (p) => ({
167
+ type: "input_file",
168
+ ...fileSourceToInput(p.source),
169
+ ...p.filename !== void 0 && { filename: p.filename }
170
+ })
171
+ }));
172
+ const toolOutput = (parts) => Option.getOrElse(ToolOutput.text(parts), () => parts.map(toolOutputPart));
151
173
  const contentBlockToInput = Match.type().pipe(Match.discriminatorsExhaustive("type")({
152
174
  input_text: (b) => ({
153
175
  type: "input_text",
@@ -185,7 +207,7 @@ const itemToInput = (item) => passthrough(item) ?? Match.value(item).pipe(Match.
185
207
  function_call_output: (o) => ({
186
208
  type: "function_call_output",
187
209
  call_id: o.call_id,
188
- output: o.output
210
+ output: toolOutput(o.output)
189
211
  }),
190
212
  reasoning: (r) => ({
191
213
  type: "reasoning",
@@ -1 +1 @@
1
- {"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Match, Option, Result, Schema, pipe } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport type { ContentBlock, InputImage, HistoryItem } from \"@effect-uai/core/Items\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\n\n// ---------------------------------------------------------------------------\n// Wire schemas - minimal subset of the Responses API output we consume.\n// Reference: https://www.openresponses.org/specification\n// ---------------------------------------------------------------------------\n\nconst WireUrlCitation = Schema.Struct({\n type: Schema.Literal(\"url_citation\"),\n url: Schema.String,\n start_index: Schema.Number,\n end_index: Schema.Number,\n title: Schema.String,\n})\n\nconst WireFileCitation = Schema.Struct({\n type: Schema.Literal(\"file_citation\"),\n file_id: Schema.String,\n index: Schema.Number,\n})\n\nconst WireContainerFileCitation = Schema.Struct({\n type: Schema.Literal(\"container_file_citation\"),\n container_id: Schema.String,\n file_id: Schema.String,\n start_index: Schema.Number,\n end_index: Schema.Number,\n})\n\nconst WireFilePath = Schema.Struct({\n type: Schema.Literal(\"file_path\"),\n file_id: Schema.String,\n index: Schema.Number,\n})\n\nexport const WireAnnotation = Schema.Union([\n WireUrlCitation,\n WireFileCitation,\n WireContainerFileCitation,\n WireFilePath,\n])\n\nconst WireOutputTextContent = Schema.Struct({\n type: Schema.Literal(\"output_text\"),\n text: Schema.String,\n annotations: Schema.optional(Schema.NullOr(Schema.Array(WireAnnotation))),\n})\n\nconst WireRefusalContent = Schema.Struct({\n type: Schema.Literal(\"refusal\"),\n refusal: Schema.String,\n})\n\nconst WireMessageContent = Schema.Union([WireOutputTextContent, WireRefusalContent])\n\nconst WireSummaryText = Schema.Struct({\n type: Schema.Literal(\"summary_text\"),\n text: Schema.String,\n})\n\nconst WireMessage = Schema.Struct({\n type: Schema.Literal(\"message\"),\n id: Schema.optional(Schema.String),\n role: Schema.Literal(\"assistant\"),\n content: Schema.Array(WireMessageContent),\n})\n\nconst WireFunctionCall = Schema.Struct({\n type: Schema.Literal(\"function_call\"),\n id: Schema.optional(Schema.String),\n call_id: Schema.String,\n name: Schema.String,\n arguments: Schema.String,\n})\n\nconst WireReasoning = Schema.Struct({\n type: Schema.Literal(\"reasoning\"),\n id: Schema.optional(Schema.String),\n summary: Schema.optional(Schema.Array(WireSummaryText)),\n encrypted_content: Schema.optional(Schema.String),\n})\n\n// Provider-hosted tool call items (web search, code interpreter, file search).\n// The provider executes them, not us — we model them only so a grounded\n// `response.completed` still decodes and assembles a Turn; they are dropped\n// from the turn's items in Phase 1. Surfacing them is a follow-up.\nconst WireWebSearchCall = Schema.Struct({\n type: Schema.Literal(\"web_search_call\"),\n id: Schema.optional(Schema.String),\n})\n\nconst WireCodeInterpreterCall = Schema.Struct({\n type: Schema.Literal(\"code_interpreter_call\"),\n id: Schema.optional(Schema.String),\n})\n\nconst WireFileSearchCall = Schema.Struct({\n type: Schema.Literal(\"file_search_call\"),\n id: Schema.optional(Schema.String),\n})\n\nexport const WireOutputItem = Schema.Union([\n WireMessage,\n WireFunctionCall,\n WireReasoning,\n WireWebSearchCall,\n WireCodeInterpreterCall,\n WireFileSearchCall,\n])\nexport type WireOutputItem = typeof WireOutputItem.Type\n\nconst WireInputTokensDetails = Schema.Struct({\n cached_tokens: Schema.optional(Schema.NullOr(Schema.Number)),\n})\n\nconst WireOutputTokensDetails = Schema.Struct({\n reasoning_tokens: Schema.optional(Schema.NullOr(Schema.Number)),\n})\n\nconst WireUsage = Schema.Struct({\n input_tokens: Schema.optional(Schema.Number),\n output_tokens: Schema.optional(Schema.Number),\n total_tokens: Schema.optional(Schema.Number),\n input_tokens_details: Schema.optional(Schema.NullOr(WireInputTokensDetails)),\n output_tokens_details: Schema.optional(Schema.NullOr(WireOutputTokensDetails)),\n})\n\nconst WireResponseError = Schema.Struct({\n code: Schema.optional(Schema.NullOr(Schema.String)),\n message: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\n// Many Responses-API fields are emitted as explicit `null` rather than\n// missing - `Schema.optional` alone (T | undefined) doesn't cover that, so\n// we lift each through `NullOr` first. Used by `response.completed`,\n// `response.incomplete`, and `response.failed` events; not every field is\n// populated on every event (e.g. `error` only on failed, `incomplete_details`\n// only on incomplete).\nexport const WireResponseCompleted = Schema.Struct({\n id: Schema.optional(Schema.NullOr(Schema.String)),\n status: Schema.optional(Schema.NullOr(Schema.String)),\n output: Schema.optional(Schema.NullOr(Schema.Array(WireOutputItem))),\n usage: Schema.optional(Schema.NullOr(WireUsage)),\n incomplete_details: Schema.optional(\n Schema.NullOr(\n Schema.Struct({\n reason: Schema.optional(Schema.NullOr(Schema.String)),\n }),\n ),\n ),\n error: Schema.optional(Schema.NullOr(WireResponseError)),\n})\nexport type WireResponseCompleted = typeof WireResponseCompleted.Type\n\n// ---------------------------------------------------------------------------\n// History → request body input\n// ---------------------------------------------------------------------------\n\n/**\n * The item shapes we stash for round-tripping. Narrower than\n * `WireOutputItem`: the provider-hosted call variants are dropped from the\n * turn, so they are never stored. Composed from the internal `Wire*` structs,\n * which keeps the wire model out of the package's public surface.\n */\nconst StoredItem = Schema.Union([WireMessage, WireFunctionCall, WireReasoning])\ntype StoredItem = typeof StoredItem.Type\n\nconst ProviderDataResponses = Schema.Struct({ responses: StoredItem })\nconst decodeResponsesData = Schema.decodeUnknownResult(ProviderDataResponses)\n\n/**\n * If an item carries `providerData.responses` from a prior turn on this API,\n * re-emit it verbatim - preserves `encrypted_content` and item ids, which a\n * fresh encode from our `HistoryItem` shape cannot reconstruct.\n *\n * Namespaced because `providerData` is a shared slot: an item routed through\n * another provider first (dynamic fallback) carries that provider's key, and\n * re-emitting the whole object would send that provider's data as the wire\n * item and drop the real content. Anything that fails to decode is not ours,\n * so we fall back to encoding the item normally.\n */\nconst passthrough = (item: HistoryItem): StoredItem | undefined =>\n pipe(\n decodeResponsesData(item.providerData),\n Result.match({\n onSuccess: (data) => data.responses,\n onFailure: (): StoredItem | undefined => undefined,\n }),\n )\n\n/**\n * OpenAI's `input_image` content block carries a single `image_url` field;\n * inline bytes get encoded as a `data:` URI. Either form (URL or data URI)\n * is fine - the model just dereferences `image_url`.\n */\nconst imageSourceToUrl = Match.type<InputImage[\"source\"]>().pipe(\n Match.tag(\"url\", (s) => s.url),\n Match.tag(\"base64\", (s) => `data:${s.mimeType};base64,${s.base64}`),\n Match.tag(\"bytes\", (s) => `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`),\n Match.exhaustive,\n)\n\nconst contentBlockToInput = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => ({ type: \"input_text\", text: b.text }),\n input_image: (b) => ({\n type: \"input_image\",\n image_url: imageSourceToUrl(b.source),\n }),\n output_text: (b) => ({ type: \"output_text\", text: b.text }),\n // No assistant-image content type here. Dropped rather than relocated\n // onto a user message, which is the caller's call: `Turn.imagesAsInput`.\n output_image: () => undefined,\n refusal: (b) => ({ type: \"refusal\", refusal: b.text }),\n }),\n)\n\nconst itemToInput = (item: HistoryItem): Record<string, unknown> =>\n passthrough(item) ??\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m) => ({\n type: \"message\",\n role: m.role,\n content: Arr.filterMap(m.content, (b) => {\n const encoded = contentBlockToInput(b)\n return encoded === undefined ? Result.failVoid : Result.succeed(encoded)\n }),\n }),\n function_call: (f) => ({\n type: \"function_call\",\n call_id: f.call_id,\n name: f.name,\n arguments: f.arguments,\n }),\n function_call_output: (o) => ({\n type: \"function_call_output\",\n call_id: o.call_id,\n output: o.output,\n }),\n reasoning: (r) => ({\n type: \"reasoning\",\n ...(r.id !== undefined && { id: r.id }),\n ...(r.summary !== undefined && {\n summary: [{ type: \"summary_text\", text: r.summary }],\n }),\n ...(r.signature !== undefined && { encrypted_content: r.signature }),\n }),\n }),\n )\n\n/** Convert our `HistoryItem[]` history into the Responses API `input` array. */\nexport const itemsToInput = (\n items: ReadonlyArray<HistoryItem>,\n): ReadonlyArray<Record<string, unknown>> => items.map(itemToInput)\n\n// ---------------------------------------------------------------------------\n// Wire output items → our Items\n// ---------------------------------------------------------------------------\n\nconst wireMessageContentToBlock = Match.type<typeof WireMessageContent.Type>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n output_text: (c): ContentBlock => ({\n type: \"output_text\",\n text: c.text,\n ...(c.annotations !== undefined && c.annotations !== null && { annotations: c.annotations }),\n }),\n refusal: (c): ContentBlock => ({ type: \"refusal\", text: c.refusal }),\n }),\n)\n\nexport const wireItemToItem = (wire: WireOutputItem): ReadonlyArray<HistoryItem> =>\n Match.value(wire).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m): ReadonlyArray<HistoryItem> => [\n {\n type: \"message\" as const,\n role: m.role,\n content: m.content.map(wireMessageContentToBlock),\n providerData: { responses: m },\n },\n ],\n function_call: (f): ReadonlyArray<HistoryItem> => [\n {\n type: \"function_call\" as const,\n call_id: f.call_id,\n name: f.name,\n arguments: f.arguments,\n providerData: { responses: f },\n },\n ],\n reasoning: (r): ReadonlyArray<HistoryItem> => [\n {\n type: \"reasoning\" as const,\n ...(r.id !== undefined && { id: r.id }),\n ...(r.summary !== undefined && {\n summary: r.summary.map((s) => s.text).join(\"\\n\"),\n }),\n ...(r.encrypted_content !== undefined && { signature: r.encrypted_content }),\n providerData: { responses: r },\n },\n ],\n // Hosted-tool calls are provider-executed; drop from the turn (Phase 1).\n web_search_call: (): ReadonlyArray<HistoryItem> => [],\n code_interpreter_call: (): ReadonlyArray<HistoryItem> => [],\n file_search_call: (): ReadonlyArray<HistoryItem> => [],\n }),\n )\n\n// ---------------------------------------------------------------------------\n// response.completed → Turn\n// ---------------------------------------------------------------------------\n\nconst reasonToStop = Match.type<string>().pipe(\n Match.when(\"max_output_tokens\", () => \"max_tokens\" as const),\n Match.when(\"refusal\", () => \"refusal\" as const),\n Match.when(\"content_filter\", () => \"content_filter\" as const),\n Match.when(\"max_tool_calls\", () => \"max_tool_calls\" as const),\n Match.option,\n)\n\nconst hasRefusalContent = (payload: WireResponseCompleted): boolean =>\n (payload.output ?? []).some(\n (i) => i.type === \"message\" && i.content.some((c) => c.type === \"refusal\"),\n )\n\nconst stopReasonFromCompleted = (payload: WireResponseCompleted): Turn[\"stop_reason\"] =>\n Option.match(reasonToStop(payload.incomplete_details?.reason ?? \"\"), {\n onSome: (s) => s,\n onNone: () =>\n hasRefusalContent(payload)\n ? (\"refusal\" as const)\n : (payload.output ?? []).some((i) => i.type === \"function_call\")\n ? (\"tool_calls\" as const)\n : (\"stop\" as const),\n })\n\nconst cachedTokens = (payload: WireResponseCompleted): number | undefined =>\n payload.usage?.input_tokens_details?.cached_tokens ?? undefined\n\nconst reasoningTokens = (payload: WireResponseCompleted): number | undefined =>\n payload.usage?.output_tokens_details?.reasoning_tokens ?? undefined\n\nexport const turnFromCompleted = (payload: WireResponseCompleted): Turn => {\n const cached = cachedTokens(payload)\n const reasoning = reasoningTokens(payload)\n return {\n items: (payload.output ?? []).flatMap(wireItemToItem),\n usage: {\n ...(payload.usage?.input_tokens !== undefined && {\n input_tokens: payload.usage.input_tokens,\n }),\n ...(payload.usage?.output_tokens !== undefined && {\n output_tokens: payload.usage.output_tokens,\n }),\n ...(payload.usage?.total_tokens !== undefined && {\n total_tokens: payload.usage.total_tokens,\n }),\n ...(cached !== undefined && {\n input_tokens_details: { cached_tokens: cached },\n }),\n ...(reasoning !== undefined && {\n output_tokens_details: { reasoning_tokens: reasoning },\n }),\n },\n stop_reason: stopReasonFromCompleted(payload),\n }\n}\n"],"mappings":";;;;;;;;;;;;AAUA,MAAM,kBAAkB,OAAO,OAAO;CACpC,MAAM,OAAO,QAAQ,cAAc;CACnC,KAAK,OAAO;CACZ,aAAa,OAAO;CACpB,WAAW,OAAO;CAClB,OAAO,OAAO;AAChB,CAAC;AAED,MAAM,mBAAmB,OAAO,OAAO;CACrC,MAAM,OAAO,QAAQ,eAAe;CACpC,SAAS,OAAO;CAChB,OAAO,OAAO;AAChB,CAAC;AAED,MAAM,4BAA4B,OAAO,OAAO;CAC9C,MAAM,OAAO,QAAQ,yBAAyB;CAC9C,cAAc,OAAO;CACrB,SAAS,OAAO;CAChB,aAAa,OAAO;CACpB,WAAW,OAAO;AACpB,CAAC;AAED,MAAM,eAAe,OAAO,OAAO;CACjC,MAAM,OAAO,QAAQ,WAAW;CAChC,SAAS,OAAO;CAChB,OAAO,OAAO;AAChB,CAAC;AAED,MAAa,iBAAiB,OAAO,MAAM;CACzC;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,wBAAwB,OAAO,OAAO;CAC1C,MAAM,OAAO,QAAQ,aAAa;CAClC,MAAM,OAAO;CACb,aAAa,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,cAAc,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,qBAAqB,OAAO,OAAO;CACvC,MAAM,OAAO,QAAQ,SAAS;CAC9B,SAAS,OAAO;AAClB,CAAC;AAED,MAAM,qBAAqB,OAAO,MAAM,CAAC,uBAAuB,kBAAkB,CAAC;AAEnF,MAAM,kBAAkB,OAAO,OAAO;CACpC,MAAM,OAAO,QAAQ,cAAc;CACnC,MAAM,OAAO;AACf,CAAC;AAED,MAAM,cAAc,OAAO,OAAO;CAChC,MAAM,OAAO,QAAQ,SAAS;CAC9B,IAAI,OAAO,SAAS,OAAO,MAAM;CACjC,MAAM,OAAO,QAAQ,WAAW;CAChC,SAAS,OAAO,MAAM,kBAAkB;AAC1C,CAAC;AAED,MAAM,mBAAmB,OAAO,OAAO;CACrC,MAAM,OAAO,QAAQ,eAAe;CACpC,IAAI,OAAO,SAAS,OAAO,MAAM;CACjC,SAAS,OAAO;CAChB,MAAM,OAAO;CACb,WAAW,OAAO;AACpB,CAAC;AAED,MAAM,gBAAgB,OAAO,OAAO;CAClC,MAAM,OAAO,QAAQ,WAAW;CAChC,IAAI,OAAO,SAAS,OAAO,MAAM;CACjC,SAAS,OAAO,SAAS,OAAO,MAAM,eAAe,CAAC;CACtD,mBAAmB,OAAO,SAAS,OAAO,MAAM;AAClD,CAAC;AAMD,MAAM,oBAAoB,OAAO,OAAO;CACtC,MAAM,OAAO,QAAQ,iBAAiB;CACtC,IAAI,OAAO,SAAS,OAAO,MAAM;AACnC,CAAC;AAED,MAAM,0BAA0B,OAAO,OAAO;CAC5C,MAAM,OAAO,QAAQ,uBAAuB;CAC5C,IAAI,OAAO,SAAS,OAAO,MAAM;AACnC,CAAC;AAED,MAAM,qBAAqB,OAAO,OAAO;CACvC,MAAM,OAAO,QAAQ,kBAAkB;CACvC,IAAI,OAAO,SAAS,OAAO,MAAM;AACnC,CAAC;AAED,MAAa,iBAAiB,OAAO,MAAM;CACzC;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAGD,MAAM,yBAAyB,OAAO,OAAO,EAC3C,eAAe,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC,EAC7D,CAAC;AAED,MAAM,0BAA0B,OAAO,OAAO,EAC5C,kBAAkB,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC,EAChE,CAAC;AAED,MAAM,YAAY,OAAO,OAAO;CAC9B,cAAc,OAAO,SAAS,OAAO,MAAM;CAC3C,eAAe,OAAO,SAAS,OAAO,MAAM;CAC5C,cAAc,OAAO,SAAS,OAAO,MAAM;CAC3C,sBAAsB,OAAO,SAAS,OAAO,OAAO,sBAAsB,CAAC;CAC3E,uBAAuB,OAAO,SAAS,OAAO,OAAO,uBAAuB,CAAC;AAC/E,CAAC;AAED,MAAM,oBAAoB,OAAO,OAAO;CACtC,MAAM,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC;CAClD,SAAS,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC;AACvD,CAAC;AAQD,MAAa,wBAAwB,OAAO,OAAO;CACjD,IAAI,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC;CAChD,QAAQ,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC;CACpD,QAAQ,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,cAAc,CAAC,CAAC;CACnE,OAAO,OAAO,SAAS,OAAO,OAAO,SAAS,CAAC;CAC/C,oBAAoB,OAAO,SACzB,OAAO,OACL,OAAO,OAAO,EACZ,QAAQ,OAAO,SAAS,OAAO,OAAO,OAAO,MAAM,CAAC,EACtD,CAAC,CACH,CACF;CACA,OAAO,OAAO,SAAS,OAAO,OAAO,iBAAiB,CAAC;AACzD,CAAC;;;;;;;AAaD,MAAM,aAAa,OAAO,MAAM;CAAC;CAAa;CAAkB;AAAa,CAAC;AAG9E,MAAM,wBAAwB,OAAO,OAAO,EAAE,WAAW,WAAW,CAAC;AACrE,MAAM,sBAAsB,OAAO,oBAAoB,qBAAqB;;;;;;;;;;;;AAa5E,MAAM,eAAe,SACnB,KACE,oBAAoB,KAAK,YAAY,GACrC,OAAO,MAAM;CACX,YAAY,SAAS,KAAK;CAC1B,iBAAyC,KAAA;AAC3C,CAAC,CACH;;;;;;AAOF,MAAM,mBAAmB,MAAM,KAA2B,CAAC,CAAC,KAC1D,MAAM,IAAI,QAAQ,MAAM,EAAE,GAAG,GAC7B,MAAM,IAAI,WAAW,MAAM,QAAQ,EAAE,SAAS,UAAU,EAAE,QAAQ,GAClE,MAAM,IAAI,UAAU,MAAM,QAAQ,EAAE,SAAS,UAAU,OAAO,OAAO,EAAE,KAAK,GAAG,GAC/E,MAAM,UACR;AAEA,MAAM,sBAAsB,MAAM,KAAmB,CAAC,CAAC,KACrD,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,aAAa,OAAO;EAAE,MAAM;EAAc,MAAM,EAAE;CAAK;CACvD,cAAc,OAAO;EACnB,MAAM;EACN,WAAW,iBAAiB,EAAE,MAAM;CACtC;CACA,cAAc,OAAO;EAAE,MAAM;EAAe,MAAM,EAAE;CAAK;CAGzD,oBAAoB,KAAA;CACpB,UAAU,OAAO;EAAE,MAAM;EAAW,SAAS,EAAE;CAAK;AACtD,CAAC,CACH;AAEA,MAAM,eAAe,SACnB,YAAY,IAAI,KAChB,MAAM,MAAM,IAAI,CAAC,CAAC,KAChB,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,UAAU,OAAO;EACf,MAAM;EACN,MAAM,EAAE;EACR,SAASA,MAAI,UAAU,EAAE,UAAU,MAAM;GACvC,MAAM,UAAU,oBAAoB,CAAC;GACrC,OAAO,YAAY,KAAA,IAAY,OAAO,WAAW,OAAO,QAAQ,OAAO;EACzE,CAAC;CACH;CACA,gBAAgB,OAAO;EACrB,MAAM;EACN,SAAS,EAAE;EACX,MAAM,EAAE;EACR,WAAW,EAAE;CACf;CACA,uBAAuB,OAAO;EAC5B,MAAM;EACN,SAAS,EAAE;EACX,QAAQ,EAAE;CACZ;CACA,YAAY,OAAO;EACjB,MAAM;EACN,GAAI,EAAE,OAAO,KAAA,KAAa,EAAE,IAAI,EAAE,GAAG;EACrC,GAAI,EAAE,YAAY,KAAA,KAAa,EAC7B,SAAS,CAAC;GAAE,MAAM;GAAgB,MAAM,EAAE;EAAQ,CAAC,EACrD;EACA,GAAI,EAAE,cAAc,KAAA,KAAa,EAAE,mBAAmB,EAAE,UAAU;CACpE;AACF,CAAC,CACH;;AAGF,MAAa,gBACX,UAC2C,MAAM,IAAI,WAAW;AAMlE,MAAM,4BAA4B,MAAM,KAAqC,CAAC,CAAC,KAC7E,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,cAAc,OAAqB;EACjC,MAAM;EACN,MAAM,EAAE;EACR,GAAI,EAAE,gBAAgB,KAAA,KAAa,EAAE,gBAAgB,QAAQ,EAAE,aAAa,EAAE,YAAY;CAC5F;CACA,UAAU,OAAqB;EAAE,MAAM;EAAW,MAAM,EAAE;CAAQ;AACpE,CAAC,CACH;AAEA,MAAa,kBAAkB,SAC7B,MAAM,MAAM,IAAI,CAAC,CAAC,KAChB,MAAM,yBAAyB,MAAM,CAAC,CAAC;CACrC,UAAU,MAAkC,CAC1C;EACE,MAAM;EACN,MAAM,EAAE;EACR,SAAS,EAAE,QAAQ,IAAI,yBAAyB;EAChD,cAAc,EAAE,WAAW,EAAE;CAC/B,CACF;CACA,gBAAgB,MAAkC,CAChD;EACE,MAAM;EACN,SAAS,EAAE;EACX,MAAM,EAAE;EACR,WAAW,EAAE;EACb,cAAc,EAAE,WAAW,EAAE;CAC/B,CACF;CACA,YAAY,MAAkC,CAC5C;EACE,MAAM;EACN,GAAI,EAAE,OAAO,KAAA,KAAa,EAAE,IAAI,EAAE,GAAG;EACrC,GAAI,EAAE,YAAY,KAAA,KAAa,EAC7B,SAAS,EAAE,QAAQ,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,KAAK,IAAI,EACjD;EACA,GAAI,EAAE,sBAAsB,KAAA,KAAa,EAAE,WAAW,EAAE,kBAAkB;EAC1E,cAAc,EAAE,WAAW,EAAE;CAC/B,CACF;CAEA,uBAAmD,CAAC;CACpD,6BAAyD,CAAC;CAC1D,wBAAoD,CAAC;AACvD,CAAC,CACH;AAMF,MAAM,eAAe,MAAM,KAAa,CAAC,CAAC,KACxC,MAAM,KAAK,2BAA2B,YAAqB,GAC3D,MAAM,KAAK,iBAAiB,SAAkB,GAC9C,MAAM,KAAK,wBAAwB,gBAAyB,GAC5D,MAAM,KAAK,wBAAwB,gBAAyB,GAC5D,MAAM,MACR;AAEA,MAAM,qBAAqB,aACxB,QAAQ,UAAU,CAAC,EAAA,CAAG,MACpB,MAAM,EAAE,SAAS,aAAa,EAAE,QAAQ,MAAM,MAAM,EAAE,SAAS,SAAS,CAC3E;AAEF,MAAM,2BAA2B,YAC/B,OAAO,MAAM,aAAa,QAAQ,oBAAoB,UAAU,EAAE,GAAG;CACnE,SAAS,MAAM;CACf,cACE,kBAAkB,OAAO,IACpB,aACA,QAAQ,UAAU,CAAC,EAAA,CAAG,MAAM,MAAM,EAAE,SAAS,eAAe,IAC1D,eACA;AACX,CAAC;AAEH,MAAM,gBAAgB,YACpB,QAAQ,OAAO,sBAAsB,iBAAiB,KAAA;AAExD,MAAM,mBAAmB,YACvB,QAAQ,OAAO,uBAAuB,oBAAoB,KAAA;AAE5D,MAAa,qBAAqB,YAAyC;CACzE,MAAM,SAAS,aAAa,OAAO;CACnC,MAAM,YAAY,gBAAgB,OAAO;CACzC,OAAO;EACL,QAAQ,QAAQ,UAAU,CAAC,EAAA,CAAG,QAAQ,cAAc;EACpD,OAAO;GACL,GAAI,QAAQ,OAAO,iBAAiB,KAAA,KAAa,EAC/C,cAAc,QAAQ,MAAM,aAC9B;GACA,GAAI,QAAQ,OAAO,kBAAkB,KAAA,KAAa,EAChD,eAAe,QAAQ,MAAM,cAC/B;GACA,GAAI,QAAQ,OAAO,iBAAiB,KAAA,KAAa,EAC/C,cAAc,QAAQ,MAAM,aAC9B;GACA,GAAI,WAAW,KAAA,KAAa,EAC1B,sBAAsB,EAAE,eAAe,OAAO,EAChD;GACA,GAAI,cAAc,KAAA,KAAa,EAC7B,uBAAuB,EAAE,kBAAkB,UAAU,EACvD;EACF;EACA,aAAa,wBAAwB,OAAO;CAC9C;AACF"}
1
+ {"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Match, Option, Result, Schema, pipe } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport type {\n ContentBlock,\n HistoryItem,\n InputFile,\n InputImage,\n ToolOutputContent,\n} from \"@effect-uai/core/Items\"\nimport * as ToolOutput from \"@effect-uai/core/ToolOutput\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\n\n// ---------------------------------------------------------------------------\n// Wire schemas - minimal subset of the Responses API output we consume.\n// Reference: https://www.openresponses.org/specification\n// ---------------------------------------------------------------------------\n\nconst WireUrlCitation = Schema.Struct({\n type: Schema.Literal(\"url_citation\"),\n url: Schema.String,\n start_index: Schema.Number,\n end_index: Schema.Number,\n title: Schema.String,\n})\n\nconst WireFileCitation = Schema.Struct({\n type: Schema.Literal(\"file_citation\"),\n file_id: Schema.String,\n index: Schema.Number,\n})\n\nconst WireContainerFileCitation = Schema.Struct({\n type: Schema.Literal(\"container_file_citation\"),\n container_id: Schema.String,\n file_id: Schema.String,\n start_index: Schema.Number,\n end_index: Schema.Number,\n})\n\nconst WireFilePath = Schema.Struct({\n type: Schema.Literal(\"file_path\"),\n file_id: Schema.String,\n index: Schema.Number,\n})\n\nexport const WireAnnotation = Schema.Union([\n WireUrlCitation,\n WireFileCitation,\n WireContainerFileCitation,\n WireFilePath,\n])\n\nconst WireOutputTextContent = Schema.Struct({\n type: Schema.Literal(\"output_text\"),\n text: Schema.String,\n annotations: Schema.optional(Schema.NullOr(Schema.Array(WireAnnotation))),\n})\n\nconst WireRefusalContent = Schema.Struct({\n type: Schema.Literal(\"refusal\"),\n refusal: Schema.String,\n})\n\nconst WireMessageContent = Schema.Union([WireOutputTextContent, WireRefusalContent])\n\nconst WireSummaryText = Schema.Struct({\n type: Schema.Literal(\"summary_text\"),\n text: Schema.String,\n})\n\nconst WireMessage = Schema.Struct({\n type: Schema.Literal(\"message\"),\n id: Schema.optional(Schema.String),\n role: Schema.Literal(\"assistant\"),\n content: Schema.Array(WireMessageContent),\n})\n\nconst WireFunctionCall = Schema.Struct({\n type: Schema.Literal(\"function_call\"),\n id: Schema.optional(Schema.String),\n call_id: Schema.String,\n name: Schema.String,\n arguments: Schema.String,\n})\n\nconst WireReasoning = Schema.Struct({\n type: Schema.Literal(\"reasoning\"),\n id: Schema.optional(Schema.String),\n summary: Schema.optional(Schema.Array(WireSummaryText)),\n encrypted_content: Schema.optional(Schema.String),\n})\n\n// Provider-hosted tool call items (web search, code interpreter, file search).\n// The provider executes them, not us — we model them only so a grounded\n// `response.completed` still decodes and assembles a Turn; they are dropped\n// from the turn's items in Phase 1. Surfacing them is a follow-up.\nconst WireWebSearchCall = Schema.Struct({\n type: Schema.Literal(\"web_search_call\"),\n id: Schema.optional(Schema.String),\n})\n\nconst WireCodeInterpreterCall = Schema.Struct({\n type: Schema.Literal(\"code_interpreter_call\"),\n id: Schema.optional(Schema.String),\n})\n\nconst WireFileSearchCall = Schema.Struct({\n type: Schema.Literal(\"file_search_call\"),\n id: Schema.optional(Schema.String),\n})\n\nexport const WireOutputItem = Schema.Union([\n WireMessage,\n WireFunctionCall,\n WireReasoning,\n WireWebSearchCall,\n WireCodeInterpreterCall,\n WireFileSearchCall,\n])\nexport type WireOutputItem = typeof WireOutputItem.Type\n\nconst WireInputTokensDetails = Schema.Struct({\n cached_tokens: Schema.optional(Schema.NullOr(Schema.Number)),\n})\n\nconst WireOutputTokensDetails = Schema.Struct({\n reasoning_tokens: Schema.optional(Schema.NullOr(Schema.Number)),\n})\n\nconst WireUsage = Schema.Struct({\n input_tokens: Schema.optional(Schema.Number),\n output_tokens: Schema.optional(Schema.Number),\n total_tokens: Schema.optional(Schema.Number),\n input_tokens_details: Schema.optional(Schema.NullOr(WireInputTokensDetails)),\n output_tokens_details: Schema.optional(Schema.NullOr(WireOutputTokensDetails)),\n})\n\nconst WireResponseError = Schema.Struct({\n code: Schema.optional(Schema.NullOr(Schema.String)),\n message: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\n// Many Responses-API fields are emitted as explicit `null` rather than\n// missing - `Schema.optional` alone (T | undefined) doesn't cover that, so\n// we lift each through `NullOr` first. Used by `response.completed`,\n// `response.incomplete`, and `response.failed` events; not every field is\n// populated on every event (e.g. `error` only on failed, `incomplete_details`\n// only on incomplete).\nexport const WireResponseCompleted = Schema.Struct({\n id: Schema.optional(Schema.NullOr(Schema.String)),\n status: Schema.optional(Schema.NullOr(Schema.String)),\n output: Schema.optional(Schema.NullOr(Schema.Array(WireOutputItem))),\n usage: Schema.optional(Schema.NullOr(WireUsage)),\n incomplete_details: Schema.optional(\n Schema.NullOr(\n Schema.Struct({\n reason: Schema.optional(Schema.NullOr(Schema.String)),\n }),\n ),\n ),\n error: Schema.optional(Schema.NullOr(WireResponseError)),\n})\nexport type WireResponseCompleted = typeof WireResponseCompleted.Type\n\n// ---------------------------------------------------------------------------\n// History → request body input\n// ---------------------------------------------------------------------------\n\n/**\n * The item shapes we stash for round-tripping. Narrower than\n * `WireOutputItem`: the provider-hosted call variants are dropped from the\n * turn, so they are never stored. Composed from the internal `Wire*` structs,\n * which keeps the wire model out of the package's public surface.\n */\nconst StoredItem = Schema.Union([WireMessage, WireFunctionCall, WireReasoning])\ntype StoredItem = typeof StoredItem.Type\n\nconst ProviderDataResponses = Schema.Struct({ responses: StoredItem })\nconst decodeResponsesData = Schema.decodeUnknownResult(ProviderDataResponses)\n\n/**\n * If an item carries `providerData.responses` from a prior turn on this API,\n * re-emit it verbatim - preserves `encrypted_content` and item ids, which a\n * fresh encode from our `HistoryItem` shape cannot reconstruct.\n *\n * Namespaced because `providerData` is a shared slot: an item routed through\n * another provider first (dynamic fallback) carries that provider's key, and\n * re-emitting the whole object would send that provider's data as the wire\n * item and drop the real content. Anything that fails to decode is not ours,\n * so we fall back to encoding the item normally.\n */\nconst passthrough = (item: HistoryItem): StoredItem | undefined =>\n pipe(\n decodeResponsesData(item.providerData),\n Result.match({\n onSuccess: (data) => data.responses,\n onFailure: (): StoredItem | undefined => undefined,\n }),\n )\n\n/**\n * OpenAI's `input_image` content block carries a single `image_url` field;\n * inline bytes get encoded as a `data:` URI. Either form (URL or data URI)\n * is fine - the model just dereferences `image_url`.\n */\nconst imageSourceToUrl = Match.type<InputImage[\"source\"]>().pipe(\n Match.tag(\"url\", (s) => s.url),\n Match.tag(\"base64\", (s) => `data:${s.mimeType};base64,${s.base64}`),\n Match.tag(\"bytes\", (s) => `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`),\n Match.exhaustive,\n)\n\nconst fileSourceToInput = Match.type<InputFile[\"source\"]>().pipe(\n Match.tag(\"url\", (s) => ({ file_url: s.url })),\n Match.tag(\"base64\", (s) => ({ file_data: `data:${s.mimeType};base64,${s.base64}` })),\n Match.tag(\"bytes\", (s) => ({\n file_data: `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`,\n })),\n Match.exhaustive,\n)\n\nconst toolOutputPart = Match.type<ToolOutputContent>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (p) => ({ type: \"input_text\", text: ToolOutput.partText(p) }),\n input_json: (p) => ({ type: \"input_text\", text: ToolOutput.partText(p) }),\n input_image: (p) => ({ type: \"input_image\", image_url: imageSourceToUrl(p.source) }),\n input_file: (p) => ({\n type: \"input_file\",\n ...fileSourceToInput(p.source),\n ...(p.filename !== undefined && { filename: p.filename }),\n }),\n }),\n)\n\n// Text and JSON only: one string (the widest supported form). With media: native parts.\n// No error flag on this wire: a failure is told by its content.\nconst toolOutput = (parts: ReadonlyArray<ToolOutputContent>): string | ReadonlyArray<object> =>\n Option.getOrElse(ToolOutput.text(parts), () => parts.map(toolOutputPart))\n\nconst contentBlockToInput = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => ({ type: \"input_text\", text: b.text }),\n input_image: (b) => ({\n type: \"input_image\",\n image_url: imageSourceToUrl(b.source),\n }),\n output_text: (b) => ({ type: \"output_text\", text: b.text }),\n // No assistant-image content type here. Dropped rather than relocated\n // onto a user message, which is the caller's call: `Turn.imagesAsInput`.\n output_image: () => undefined,\n refusal: (b) => ({ type: \"refusal\", refusal: b.text }),\n }),\n)\n\nconst itemToInput = (item: HistoryItem): Record<string, unknown> =>\n passthrough(item) ??\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m) => ({\n type: \"message\",\n role: m.role,\n content: Arr.filterMap(m.content, (b) => {\n const encoded = contentBlockToInput(b)\n return encoded === undefined ? Result.failVoid : Result.succeed(encoded)\n }),\n }),\n function_call: (f) => ({\n type: \"function_call\",\n call_id: f.call_id,\n name: f.name,\n arguments: f.arguments,\n }),\n function_call_output: (o) => ({\n type: \"function_call_output\",\n call_id: o.call_id,\n output: toolOutput(o.output),\n }),\n reasoning: (r) => ({\n type: \"reasoning\",\n ...(r.id !== undefined && { id: r.id }),\n ...(r.summary !== undefined && {\n summary: [{ type: \"summary_text\", text: r.summary }],\n }),\n ...(r.signature !== undefined && { encrypted_content: r.signature }),\n }),\n }),\n )\n\n/** Convert our `HistoryItem[]` history into the Responses API `input` array. */\nexport const itemsToInput = (\n items: ReadonlyArray<HistoryItem>,\n): ReadonlyArray<Record<string, unknown>> => items.map(itemToInput)\n\n// ---------------------------------------------------------------------------\n// Wire output items → our Items\n// ---------------------------------------------------------------------------\n\nconst wireMessageContentToBlock = Match.type<typeof WireMessageContent.Type>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n output_text: (c): ContentBlock => ({\n type: \"output_text\",\n text: c.text,\n ...(c.annotations !== undefined && c.annotations !== null && { annotations: c.annotations }),\n }),\n refusal: (c): ContentBlock => ({ type: \"refusal\", text: c.refusal }),\n }),\n)\n\nexport const wireItemToItem = (wire: WireOutputItem): ReadonlyArray<HistoryItem> =>\n Match.value(wire).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m): ReadonlyArray<HistoryItem> => [\n {\n type: \"message\" as const,\n role: m.role,\n content: m.content.map(wireMessageContentToBlock),\n providerData: { responses: m },\n },\n ],\n function_call: (f): ReadonlyArray<HistoryItem> => [\n {\n type: \"function_call\" as const,\n call_id: f.call_id,\n name: f.name,\n arguments: f.arguments,\n providerData: { responses: f },\n },\n ],\n reasoning: (r): ReadonlyArray<HistoryItem> => [\n {\n type: \"reasoning\" as const,\n ...(r.id !== undefined && { id: r.id }),\n ...(r.summary !== undefined && {\n summary: r.summary.map((s) => s.text).join(\"\\n\"),\n }),\n ...(r.encrypted_content !== undefined && { signature: r.encrypted_content }),\n providerData: { responses: r },\n },\n ],\n // Hosted-tool calls are provider-executed; drop from the turn (Phase 1).\n web_search_call: (): ReadonlyArray<HistoryItem> => [],\n code_interpreter_call: (): ReadonlyArray<HistoryItem> => [],\n file_search_call: (): ReadonlyArray<HistoryItem> => [],\n }),\n )\n\n// ---------------------------------------------------------------------------\n// response.completed → Turn\n// ---------------------------------------------------------------------------\n\nconst reasonToStop = Match.type<string>().pipe(\n Match.when(\"max_output_tokens\", () => \"max_tokens\" as const),\n Match.when(\"refusal\", () => \"refusal\" as const),\n Match.when(\"content_filter\", () => \"content_filter\" as const),\n Match.when(\"max_tool_calls\", () => \"max_tool_calls\" as const),\n Match.option,\n)\n\nconst hasRefusalContent = (payload: WireResponseCompleted): boolean =>\n (payload.output ?? []).some(\n (i) => i.type === \"message\" && i.content.some((c) => c.type === \"refusal\"),\n )\n\nconst stopReasonFromCompleted = (payload: WireResponseCompleted): Turn[\"stop_reason\"] =>\n Option.match(reasonToStop(payload.incomplete_details?.reason ?? \"\"), {\n onSome: (s) => s,\n onNone: () =>\n hasRefusalContent(payload)\n ? (\"refusal\" as const)\n : (payload.output ?? []).some((i) => i.type === \"function_call\")\n ? (\"tool_calls\" as const)\n : (\"stop\" as const),\n })\n\nconst cachedTokens = (payload: WireResponseCompleted): number | undefined =>\n payload.usage?.input_tokens_details?.cached_tokens ?? undefined\n\nconst reasoningTokens = (payload: WireResponseCompleted): number | undefined =>\n payload.usage?.output_tokens_details?.reasoning_tokens ?? undefined\n\nexport const turnFromCompleted = (payload: WireResponseCompleted): Turn => {\n const cached = cachedTokens(payload)\n const reasoning = reasoningTokens(payload)\n return {\n items: (payload.output ?? []).flatMap(wireItemToItem),\n usage: {\n ...(payload.usage?.input_tokens !== undefined && {\n input_tokens: payload.usage.input_tokens,\n }),\n ...(payload.usage?.output_tokens !== undefined && {\n output_tokens: payload.usage.output_tokens,\n }),\n ...(payload.usage?.total_tokens !== undefined && {\n total_tokens: payload.usage.total_tokens,\n }),\n ...(cached !== undefined && {\n input_tokens_details: { cached_tokens: cached },\n }),\n ...(reasoning !== undefined && {\n output_tokens_details: { reasoning_tokens: reasoning },\n }),\n },\n stop_reason: stopReasonFromCompleted(payload),\n }\n}\n"],"mappings":"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"}
@@ -1,5 +1,8 @@
1
1
  import { itemsToInput, wireItemToItem } from "./codec.mjs";
2
2
  import { n as describe, r as it, t as globalExpect } from "./index.C4tKZ0yW-xEu55bU4.mjs";
3
+ import { fileBase64, fileUrl } from "@effect-uai/core/File";
4
+ import { imageBytes } from "@effect-uai/core/Image";
5
+ import * as Items from "@effect-uai/core/Items";
3
6
  //#region src/codec.test.ts
4
7
  describe("providerData round-trip", () => {
5
8
  it("re-emits a stashed item verbatim, preserving fields our shape drops", () => {
@@ -88,6 +91,58 @@ describe("providerData is a shared slot", () => {
88
91
  }]);
89
92
  });
90
93
  });
94
+ describe("tool output", () => {
95
+ const output = (parts, isError) => itemsToInput([{
96
+ type: "function_call_output",
97
+ call_id: "c1",
98
+ output: parts,
99
+ ...isError !== void 0 && { isError }
100
+ }]);
101
+ it("sends text and JSON as one string, JSON serialized once", () => {
102
+ globalExpect(output([Items.text("found 2"), Items.json({
103
+ hits: [1, 2],
104
+ note: "a \"quote\""
105
+ })], true)).toEqual([{
106
+ type: "function_call_output",
107
+ call_id: "c1",
108
+ output: "found 2\n{\"hits\":[1,2],\"note\":\"a \\\"quote\\\"\"}"
109
+ }]);
110
+ });
111
+ it("sends media as native parts: images as image_url, files as file_data or file_url", () => {
112
+ globalExpect(output([
113
+ Items.json({ title: "Home" }),
114
+ Items.image(imageBytes(new Uint8Array([
115
+ 1,
116
+ 2,
117
+ 3
118
+ ]), "image/png")),
119
+ Items.file(fileBase64("JVBE", "application/pdf"), "report.pdf"),
120
+ Items.file(fileUrl("https://x/a.pdf"))
121
+ ])).toEqual([{
122
+ type: "function_call_output",
123
+ call_id: "c1",
124
+ output: [
125
+ {
126
+ type: "input_text",
127
+ text: "{\"title\":\"Home\"}"
128
+ },
129
+ {
130
+ type: "input_image",
131
+ image_url: "data:image/png;base64,AQID"
132
+ },
133
+ {
134
+ type: "input_file",
135
+ file_data: "data:application/pdf;base64,JVBE",
136
+ filename: "report.pdf"
137
+ },
138
+ {
139
+ type: "input_file",
140
+ file_url: "https://x/a.pdf"
141
+ }
142
+ ]
143
+ }]);
144
+ });
145
+ });
91
146
  //#endregion
92
147
  export {};
93
148
 
@@ -1 +1 @@
1
- {"version":3,"file":"codec.test.mjs","names":[],"sources":["../src/codec.test.ts"],"sourcesContent":["import type { HistoryItem } from \"@effect-uai/core/Items\"\nimport { describe, expect, it } from \"vitest\"\nimport { itemsToInput, wireItemToItem } from \"./codec.js\"\n\ndescribe(\"providerData round-trip\", () => {\n it(\"re-emits a stashed item verbatim, preserving fields our shape drops\", () => {\n // `id` and `encrypted_content` cannot be reconstructed from a Reasoning\n // item, so the round-trip has to go through the stash.\n const items = wireItemToItem({\n type: \"reasoning\",\n id: \"rs_1\",\n encrypted_content: \"opaque-state\",\n })\n expect(itemsToInput(items)).toEqual([\n { type: \"reasoning\", id: \"rs_1\", encrypted_content: \"opaque-state\" },\n ])\n })\n})\n\ndescribe(\"providerData is a shared slot\", () => {\n // Regression: the passthrough used to re-emit the whole `providerData`\n // object as the wire item. An item that had been through another provider\n // first was sent as that provider's data, losing the real content.\n it(\"ignores another provider's data and encodes the item normally\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: \"hello world\" }],\n providerData: { gemini: { thoughtSignature: \"sig-abc\" } },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n { type: \"message\", role: \"user\", content: [{ type: \"input_text\", text: \"hello world\" }] },\n ])\n })\n\n it(\"does not send a foreign function_call as junk\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"function_call\",\n call_id: \"c1\",\n name: \"get_weather\",\n arguments: '{\"city\":\"Lisbon\"}',\n providerData: { gemini: { id: \"g1\", thoughtSignature: \"sig-abc\" } },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n {\n type: \"function_call\",\n call_id: \"c1\",\n name: \"get_weather\",\n arguments: '{\"city\":\"Lisbon\"}',\n },\n ])\n })\n\n it(\"re-emits only our key when an item carries several providers' data\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"reasoning\",\n signature: \"opaque-state\",\n providerData: {\n responses: { type: \"reasoning\", id: \"rs_1\", encrypted_content: \"opaque-state\" },\n gemini: { thoughtSignature: \"sig-abc\" },\n },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n { type: \"reasoning\", id: \"rs_1\", encrypted_content: \"opaque-state\" },\n ])\n })\n\n it(\"falls back to a normal encode when the stashed value is not one of ours\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: \"hi\" }],\n providerData: { responses: { type: \"not_an_item_we_model\" } },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n { type: \"message\", role: \"user\", content: [{ type: \"input_text\", text: \"hi\" }] },\n ])\n })\n})\n"],"mappings":";;;AAIA,SAAS,iCAAiC;CACxC,GAAG,6EAA6E;EAG9E,MAAM,QAAQ,eAAe;GAC3B,MAAM;GACN,IAAI;GACJ,mBAAmB;EACrB,CAAC;EACD,aAAO,aAAa,KAAK,CAAC,CAAC,CAAC,QAAQ,CAClC;GAAE,MAAM;GAAa,IAAI;GAAQ,mBAAmB;EAAe,CACrE,CAAC;CACH,CAAC;AACH,CAAC;AAED,SAAS,uCAAuC;CAI9C,GAAG,uEAAuE;EASxE,aAAO,aAAa,CAPlB;GACE,MAAM;GACN,MAAM;GACN,SAAS,CAAC;IAAE,MAAM;IAAc,MAAM;GAAc,CAAC;GACrD,cAAc,EAAE,QAAQ,EAAE,kBAAkB,UAAU,EAAE;EAC1D,CAEwB,CAAC,CAAC,CAAC,CAAC,QAAQ,CACpC;GAAE,MAAM;GAAW,MAAM;GAAQ,SAAS,CAAC;IAAE,MAAM;IAAc,MAAM;GAAc,CAAC;EAAE,CAC1F,CAAC;CACH,CAAC;CAED,GAAG,uDAAuD;EAUxD,aAAO,aAAa,CARlB;GACE,MAAM;GACN,SAAS;GACT,MAAM;GACN,WAAW;GACX,cAAc,EAAE,QAAQ;IAAE,IAAI;IAAM,kBAAkB;GAAU,EAAE;EACpE,CAEwB,CAAC,CAAC,CAAC,CAAC,QAAQ,CACpC;GACE,MAAM;GACN,SAAS;GACT,MAAM;GACN,WAAW;EACb,CACF,CAAC;CACH,CAAC;CAED,GAAG,4EAA4E;EAW7E,aAAO,aAAa,CATlB;GACE,MAAM;GACN,WAAW;GACX,cAAc;IACZ,WAAW;KAAE,MAAM;KAAa,IAAI;KAAQ,mBAAmB;IAAe;IAC9E,QAAQ,EAAE,kBAAkB,UAAU;GACxC;EACF,CAEwB,CAAC,CAAC,CAAC,CAAC,QAAQ,CACpC;GAAE,MAAM;GAAa,IAAI;GAAQ,mBAAmB;EAAe,CACrE,CAAC;CACH,CAAC;CAED,GAAG,iFAAiF;EASlF,aAAO,aAAa,CAPlB;GACE,MAAM;GACN,MAAM;GACN,SAAS,CAAC;IAAE,MAAM;IAAc,MAAM;GAAK,CAAC;GAC5C,cAAc,EAAE,WAAW,EAAE,MAAM,uBAAuB,EAAE;EAC9D,CAEwB,CAAC,CAAC,CAAC,CAAC,QAAQ,CACpC;GAAE,MAAM;GAAW,MAAM;GAAQ,SAAS,CAAC;IAAE,MAAM;IAAc,MAAM;GAAK,CAAC;EAAE,CACjF,CAAC;CACH,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"codec.test.mjs","names":[],"sources":["../src/codec.test.ts"],"sourcesContent":["import { fileBase64, fileUrl } from \"@effect-uai/core/File\"\nimport { imageBytes } from \"@effect-uai/core/Image\"\nimport * as Items from \"@effect-uai/core/Items\"\nimport type { HistoryItem } from \"@effect-uai/core/Items\"\nimport { describe, expect, it } from \"vitest\"\nimport { itemsToInput, wireItemToItem } from \"./codec.js\"\n\ndescribe(\"providerData round-trip\", () => {\n it(\"re-emits a stashed item verbatim, preserving fields our shape drops\", () => {\n // `id` and `encrypted_content` cannot be reconstructed from a Reasoning\n // item, so the round-trip has to go through the stash.\n const items = wireItemToItem({\n type: \"reasoning\",\n id: \"rs_1\",\n encrypted_content: \"opaque-state\",\n })\n expect(itemsToInput(items)).toEqual([\n { type: \"reasoning\", id: \"rs_1\", encrypted_content: \"opaque-state\" },\n ])\n })\n})\n\ndescribe(\"providerData is a shared slot\", () => {\n // Regression: the passthrough used to re-emit the whole `providerData`\n // object as the wire item. An item that had been through another provider\n // first was sent as that provider's data, losing the real content.\n it(\"ignores another provider's data and encodes the item normally\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: \"hello world\" }],\n providerData: { gemini: { thoughtSignature: \"sig-abc\" } },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n { type: \"message\", role: \"user\", content: [{ type: \"input_text\", text: \"hello world\" }] },\n ])\n })\n\n it(\"does not send a foreign function_call as junk\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"function_call\",\n call_id: \"c1\",\n name: \"get_weather\",\n arguments: '{\"city\":\"Lisbon\"}',\n providerData: { gemini: { id: \"g1\", thoughtSignature: \"sig-abc\" } },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n {\n type: \"function_call\",\n call_id: \"c1\",\n name: \"get_weather\",\n arguments: '{\"city\":\"Lisbon\"}',\n },\n ])\n })\n\n it(\"re-emits only our key when an item carries several providers' data\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"reasoning\",\n signature: \"opaque-state\",\n providerData: {\n responses: { type: \"reasoning\", id: \"rs_1\", encrypted_content: \"opaque-state\" },\n gemini: { thoughtSignature: \"sig-abc\" },\n },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n { type: \"reasoning\", id: \"rs_1\", encrypted_content: \"opaque-state\" },\n ])\n })\n\n it(\"falls back to a normal encode when the stashed value is not one of ours\", () => {\n const history: ReadonlyArray<HistoryItem> = [\n {\n type: \"message\",\n role: \"user\",\n content: [{ type: \"input_text\", text: \"hi\" }],\n providerData: { responses: { type: \"not_an_item_we_model\" } },\n },\n ]\n expect(itemsToInput(history)).toEqual([\n { type: \"message\", role: \"user\", content: [{ type: \"input_text\", text: \"hi\" }] },\n ])\n })\n})\n\ndescribe(\"tool output\", () => {\n const output = (parts: Items.ToolCallOutput[\"output\"], isError?: boolean) =>\n itemsToInput([\n {\n type: \"function_call_output\",\n call_id: \"c1\",\n output: parts,\n ...(isError !== undefined && { isError }),\n },\n ])\n\n it(\"sends text and JSON as one string, JSON serialized once\", () => {\n expect(\n output([Items.text(\"found 2\"), Items.json({ hits: [1, 2], note: 'a \"quote\"' })], true),\n ).toEqual([\n {\n type: \"function_call_output\",\n call_id: \"c1\",\n output: 'found 2\\n{\"hits\":[1,2],\"note\":\"a \\\\\"quote\\\\\"\"}',\n },\n ])\n })\n\n it(\"sends media as native parts: images as image_url, files as file_data or file_url\", () => {\n expect(\n output([\n Items.json({ title: \"Home\" }),\n Items.image(imageBytes(new Uint8Array([1, 2, 3]), \"image/png\")),\n Items.file(fileBase64(\"JVBE\", \"application/pdf\"), \"report.pdf\"),\n Items.file(fileUrl(\"https://x/a.pdf\")),\n ]),\n ).toEqual([\n {\n type: \"function_call_output\",\n call_id: \"c1\",\n output: [\n { type: \"input_text\", text: '{\"title\":\"Home\"}' },\n { type: \"input_image\", image_url: \"data:image/png;base64,AQID\" },\n {\n type: \"input_file\",\n file_data: \"data:application/pdf;base64,JVBE\",\n filename: \"report.pdf\",\n },\n { type: \"input_file\", file_url: \"https://x/a.pdf\" },\n ],\n },\n ])\n })\n})\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/responses",
3
- "version": "0.18.0",
3
+ "version": "0.19.0-dev-20261110065930",
4
4
  "description": "OpenAI Responses API provider for @effect-uai/core.",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,12 +55,12 @@
55
55
  "access": "public"
56
56
  },
57
57
  "devDependencies": {
58
- "effect": "4.0.1",
58
+ "effect": "4.1.2",
59
59
  "typescript": "^7.0.2",
60
- "@effect-uai/core": "0.18.0"
60
+ "@effect-uai/core": "0.19.0-dev-20261110065930"
61
61
  },
62
62
  "peerDependencies": {
63
- "@effect-uai/core": ">=0.2.0 <1",
63
+ "@effect-uai/core": "0.19.0-dev-20261110065930",
64
64
  "effect": ">=4.0.1 <5.0.0"
65
65
  },
66
66
  "scripts": {
package/src/codec.test.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { fileBase64, fileUrl } from "@effect-uai/core/File"
2
+ import { imageBytes } from "@effect-uai/core/Image"
3
+ import * as Items from "@effect-uai/core/Items"
1
4
  import type { HistoryItem } from "@effect-uai/core/Items"
2
5
  import { describe, expect, it } from "vitest"
3
6
  import { itemsToInput, wireItemToItem } from "./codec.js"
@@ -85,3 +88,53 @@ describe("providerData is a shared slot", () => {
85
88
  ])
86
89
  })
87
90
  })
91
+
92
+ describe("tool output", () => {
93
+ const output = (parts: Items.ToolCallOutput["output"], isError?: boolean) =>
94
+ itemsToInput([
95
+ {
96
+ type: "function_call_output",
97
+ call_id: "c1",
98
+ output: parts,
99
+ ...(isError !== undefined && { isError }),
100
+ },
101
+ ])
102
+
103
+ it("sends text and JSON as one string, JSON serialized once", () => {
104
+ expect(
105
+ output([Items.text("found 2"), Items.json({ hits: [1, 2], note: 'a "quote"' })], true),
106
+ ).toEqual([
107
+ {
108
+ type: "function_call_output",
109
+ call_id: "c1",
110
+ output: 'found 2\n{"hits":[1,2],"note":"a \\"quote\\""}',
111
+ },
112
+ ])
113
+ })
114
+
115
+ it("sends media as native parts: images as image_url, files as file_data or file_url", () => {
116
+ expect(
117
+ output([
118
+ Items.json({ title: "Home" }),
119
+ Items.image(imageBytes(new Uint8Array([1, 2, 3]), "image/png")),
120
+ Items.file(fileBase64("JVBE", "application/pdf"), "report.pdf"),
121
+ Items.file(fileUrl("https://x/a.pdf")),
122
+ ]),
123
+ ).toEqual([
124
+ {
125
+ type: "function_call_output",
126
+ call_id: "c1",
127
+ output: [
128
+ { type: "input_text", text: '{"title":"Home"}' },
129
+ { type: "input_image", image_url: "data:image/png;base64,AQID" },
130
+ {
131
+ type: "input_file",
132
+ file_data: "data:application/pdf;base64,JVBE",
133
+ filename: "report.pdf",
134
+ },
135
+ { type: "input_file", file_url: "https://x/a.pdf" },
136
+ ],
137
+ },
138
+ ])
139
+ })
140
+ })
package/src/codec.ts CHANGED
@@ -1,6 +1,13 @@
1
1
  import { Array as Arr, Match, Option, Result, Schema, pipe } from "effect"
2
2
  import { Base64 } from "effect/encoding"
3
- import type { ContentBlock, InputImage, HistoryItem } from "@effect-uai/core/Items"
3
+ import type {
4
+ ContentBlock,
5
+ HistoryItem,
6
+ InputFile,
7
+ InputImage,
8
+ ToolOutputContent,
9
+ } from "@effect-uai/core/Items"
10
+ import * as ToolOutput from "@effect-uai/core/ToolOutput"
4
11
  import type { Turn } from "@effect-uai/core/Turn"
5
12
 
6
13
  // ---------------------------------------------------------------------------
@@ -203,6 +210,33 @@ const imageSourceToUrl = Match.type<InputImage["source"]>().pipe(
203
210
  Match.exhaustive,
204
211
  )
205
212
 
213
+ const fileSourceToInput = Match.type<InputFile["source"]>().pipe(
214
+ Match.tag("url", (s) => ({ file_url: s.url })),
215
+ Match.tag("base64", (s) => ({ file_data: `data:${s.mimeType};base64,${s.base64}` })),
216
+ Match.tag("bytes", (s) => ({
217
+ file_data: `data:${s.mimeType};base64,${Base64.encode(s.bytes)}`,
218
+ })),
219
+ Match.exhaustive,
220
+ )
221
+
222
+ const toolOutputPart = Match.type<ToolOutputContent>().pipe(
223
+ Match.discriminatorsExhaustive("type")({
224
+ input_text: (p) => ({ type: "input_text", text: ToolOutput.partText(p) }),
225
+ input_json: (p) => ({ type: "input_text", text: ToolOutput.partText(p) }),
226
+ input_image: (p) => ({ type: "input_image", image_url: imageSourceToUrl(p.source) }),
227
+ input_file: (p) => ({
228
+ type: "input_file",
229
+ ...fileSourceToInput(p.source),
230
+ ...(p.filename !== undefined && { filename: p.filename }),
231
+ }),
232
+ }),
233
+ )
234
+
235
+ // Text and JSON only: one string (the widest supported form). With media: native parts.
236
+ // No error flag on this wire: a failure is told by its content.
237
+ const toolOutput = (parts: ReadonlyArray<ToolOutputContent>): string | ReadonlyArray<object> =>
238
+ Option.getOrElse(ToolOutput.text(parts), () => parts.map(toolOutputPart))
239
+
206
240
  const contentBlockToInput = Match.type<ContentBlock>().pipe(
207
241
  Match.discriminatorsExhaustive("type")({
208
242
  input_text: (b) => ({ type: "input_text", text: b.text }),
@@ -239,7 +273,7 @@ const itemToInput = (item: HistoryItem): Record<string, unknown> =>
239
273
  function_call_output: (o) => ({
240
274
  type: "function_call_output",
241
275
  call_id: o.call_id,
242
- output: o.output,
276
+ output: toolOutput(o.output),
243
277
  }),
244
278
  reasoning: (r) => ({
245
279
  type: "reasoning",