@effect-uai/chat-completions 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
@@ -50,7 +50,7 @@ for endpoints that diverge from the OpenRouter defaults.
50
50
 
51
51
  ## Docs
52
52
 
53
- <https://effect-uai.betalyra.com/providers/>
53
+ <https://effect-uai.com/providers/>
54
54
 
55
55
  ## License
56
56
 
@@ -1 +1 @@
1
- {"version":3,"file":"ChatCompletions.d.mts","names":[],"sources":["../src/ChatCompletions.ts"],"mappings":";;;;;;KA+BY;WACD,QAAQ,SAAS;;WAEjB;;WAEA;;WAEA;;;;;WAKA,cACP,SAAS,kBAAkB,sBACxB,kBAAkB;;WAEd,eAAe;;WAEf,aAAa,SAAS,kBAAkB;;;cAsGtC,OAAI,KACV,eACJ,OAAO,OAAO,6BAA6B,WAAW;;cAW5C,QAAK,KAAS,eAAa,MAAM,MAAM,sBAAsB,WAAW"}
1
+ {"version":3,"file":"ChatCompletions.d.mts","names":[],"sources":["../src/ChatCompletions.ts"],"mappings":";;;;;;KAgCY;WACD,QAAQ,SAAS;;WAEjB;;WAEA;;WAEA;;;;;WAKA,cACP,SAAS,kBAAkB,sBACxB,kBAAkB;;WAEd,eAAe;;WAEf,aAAa,SAAS,kBAAkB;;;cAuGtC,OAAI,KACV,eACJ,OAAO,OAAO,6BAA6B,WAAW;;cAW5C,QAAK,KAAS,eAAa,MAAM,MAAM,sBAAsB,WAAW"}
@@ -1,5 +1,5 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, toolChoiceWire, toolsWire } from "./codec.mjs";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
+ import { accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, responseFormatWire, toolChoiceWire, toolsWire } from "./codec.mjs";
3
3
  import { httpStatusError, transportFailure } from "./http.mjs";
4
4
  import { Effect, Layer, Option, Result, Schema, Stream } from "effect";
5
5
  import { HttpClient, HttpClientRequest } from "effect/http";
@@ -46,6 +46,7 @@ const buildStream = (cfg) => {
46
46
  capability: "output_image",
47
47
  reason: "Assistant messages carry no image on this wire. Use `Turn.imagesAsInput` to resend it as user content."
48
48
  });
49
+ yield* rejectToolOutputMedia(cfg.provider, request.history);
49
50
  const httpRequest = HttpClientRequest.post(url).pipe(auth, HttpClientRequest.setHeaders(cfg.extraHeaders ?? {}), HttpClientRequest.bodyJsonUnsafe(buildRequestBody(cfg, request)), HttpClientRequest.accept("text/event-stream"));
50
51
  const response = yield* client.execute(httpRequest).pipe(Effect.mapError((cause) => transportFailure(cfg.provider, cause)));
51
52
  if (response.status >= 400) {
@@ -1 +1 @@
1
- {"version":3,"file":"ChatCompletions.mjs","names":[],"sources":["../src/ChatCompletions.ts"],"sourcesContent":["import { Effect, Layer, Option, Redacted, Result, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf } from \"@effect-uai/core/Tool\"\nimport { TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type Accumulator,\n type WireChunk,\n accumulatorToTurn,\n applyChunk,\n decodeChunk,\n emptyAccumulator,\n itemsToMessages,\n responseFormatWire,\n toolChoiceWire,\n toolsWire,\n} from \"./codec.js\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport type ChatConfig = {\n readonly apiKey: Redacted.Redacted\n /** Provider base URL, e.g. `https://openrouter.ai/api/v1`. No trailing slash. */\n readonly baseUrl: string\n /** Name used to tag `AiError`s from this provider. */\n readonly provider: string\n /** Request path appended to `baseUrl`. Defaults to `/chat/completions`. */\n readonly path?: string\n /**\n * Override the auth header. Defaults to `Authorization: Bearer <apiKey>`.\n * Supply this for gateways that authenticate differently (e.g. Azure `api-key`).\n */\n readonly authHeader?: (\n request: HttpClientRequest.HttpClientRequest,\n ) => HttpClientRequest.HttpClientRequest\n /** Extra headers merged into every request (e.g. OpenRouter `HTTP-Referer`). */\n readonly extraHeaders?: Record<string, string>\n /** Provider-specific request-body fields merged into the wire body. */\n readonly extraBody?: (request: CommonRequest) => Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst buildRequestBody = (cfg: ChatConfig, request: CommonRequest): Record<string, unknown> => {\n const tools = toolsWire(descriptorsOf(request.tools))\n return {\n model: request.model,\n stream: true,\n messages: itemsToMessages(request.history),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.topP !== undefined && { top_p: request.topP }),\n ...(request.maxOutputTokens !== undefined && { max_tokens: request.maxOutputTokens }),\n ...(Option.isSome(tools) && { tools: tools.value }),\n ...(request.toolChoice !== undefined && { tool_choice: toolChoiceWire(request.toolChoice) }),\n ...(request.structured !== undefined && {\n response_format: responseFormatWire(request.structured),\n }),\n ...(cfg.extraBody?.(request) ?? {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// SSE → WireChunk\n// ---------------------------------------------------------------------------\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\n/**\n * Decode one SSE event's `data` payload into a `WireChunk`. Returns `None` for\n * the `[DONE]` sentinel and for any payload that isn't a chunk we model, so a\n * stray keep-alive never aborts the turn.\n */\nconst decodeEvent = (data: string): Effect.Effect<Option.Option<WireChunk>> =>\n data.trim() === \"[DONE]\"\n ? Effect.succeedNone\n : parseJsonUnknown(data).pipe(Effect.flatMap(decodeChunk), Effect.option)\n\n// ---------------------------------------------------------------------------\n// Stream\n// ---------------------------------------------------------------------------\n\nconst buildStream = (cfg: ChatConfig) => {\n const url = `${cfg.baseUrl}${cfg.path ?? \"/chat/completions\"}`\n const auth = cfg.authHeader ?? HttpClientRequest.bearerToken(cfg.apiKey)\n return (\n request: CommonRequest,\n ): Stream.Stream<TurnEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n yield* Capabilities.warnDroppedBlocks(request.history, \"output_image\", {\n provider: cfg.provider,\n capability: \"output_image\",\n reason:\n \"Assistant messages carry no image on this wire. Use `Turn.imagesAsInput` to resend it as user content.\",\n })\n const httpRequest = HttpClientRequest.post(url).pipe(\n auth,\n HttpClientRequest.setHeaders(cfg.extraHeaders ?? {}),\n HttpClientRequest.bodyJsonUnsafe(buildRequestBody(cfg, request)),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError((cause) => transportFailure(cfg.provider, cause)))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(cfg.provider, response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: cfg.provider, raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect((ev) => decodeEvent(ev.data)),\n Stream.filterMap((chunk) =>\n Option.isSome(chunk) ? Result.succeed(chunk.value) : Result.failVoid,\n ),\n Stream.mapAccum((): Accumulator => emptyAccumulator, applyChunk, {\n // `onHalt` also fires on upstream failure and truncated streams, so\n // only emit `TurnComplete` once a `finish_reason` was observed.\n onHalt: (acc) =>\n Option.isSome(acc.finishReason)\n ? [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })]\n : [],\n }),\n )\n }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/** Build a generic chat-completions `LanguageModelService`. */\nexport const make = (\n cfg: ChatConfig,\n): Effect.Effect<LanguageModelService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => {\n const streamTurn: LanguageModelService[\"streamTurn\"] = (request) =>\n buildStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamTurn,\n turn: turnFromStream(streamTurn),\n }\n })\n\n/** Layer registering the generic `LanguageModel` tag for a compatible gateway. */\nexport const layer = (cfg: ChatConfig): Layer.Layer<LanguageModel, never, HttpClient.HttpClient> =>\n Layer.effect(LanguageModel, make(cfg))\n"],"mappings":"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"}
1
+ {"version":3,"file":"ChatCompletions.mjs","names":[],"sources":["../src/ChatCompletions.ts"],"sourcesContent":["import { Effect, Layer, Option, Redacted, Result, Schema, Stream } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/http\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport * as Capabilities from \"@effect-uai/core/Capabilities\"\nimport {\n type CommonRequest,\n LanguageModel,\n type LanguageModelService,\n turnFromStream,\n} from \"@effect-uai/core/LanguageModel\"\nimport * as SSE from \"@effect-uai/core/SSE\"\nimport { descriptorsOf } from \"@effect-uai/core/Tool\"\nimport { TurnEvent } from \"@effect-uai/core/Turn\"\nimport {\n type Accumulator,\n type WireChunk,\n accumulatorToTurn,\n applyChunk,\n decodeChunk,\n emptyAccumulator,\n itemsToMessages,\n rejectToolOutputMedia,\n responseFormatWire,\n toolChoiceWire,\n toolsWire,\n} from \"./codec.js\"\nimport { httpStatusError, transportFailure } from \"./http.js\"\n\n// ---------------------------------------------------------------------------\n// Config\n// ---------------------------------------------------------------------------\n\nexport type ChatConfig = {\n readonly apiKey: Redacted.Redacted\n /** Provider base URL, e.g. `https://openrouter.ai/api/v1`. No trailing slash. */\n readonly baseUrl: string\n /** Name used to tag `AiError`s from this provider. */\n readonly provider: string\n /** Request path appended to `baseUrl`. Defaults to `/chat/completions`. */\n readonly path?: string\n /**\n * Override the auth header. Defaults to `Authorization: Bearer <apiKey>`.\n * Supply this for gateways that authenticate differently (e.g. Azure `api-key`).\n */\n readonly authHeader?: (\n request: HttpClientRequest.HttpClientRequest,\n ) => HttpClientRequest.HttpClientRequest\n /** Extra headers merged into every request (e.g. OpenRouter `HTTP-Referer`). */\n readonly extraHeaders?: Record<string, string>\n /** Provider-specific request-body fields merged into the wire body. */\n readonly extraBody?: (request: CommonRequest) => Record<string, unknown>\n}\n\n// ---------------------------------------------------------------------------\n// Request body\n// ---------------------------------------------------------------------------\n\nconst buildRequestBody = (cfg: ChatConfig, request: CommonRequest): Record<string, unknown> => {\n const tools = toolsWire(descriptorsOf(request.tools))\n return {\n model: request.model,\n stream: true,\n messages: itemsToMessages(request.history),\n ...(request.temperature !== undefined && { temperature: request.temperature }),\n ...(request.topP !== undefined && { top_p: request.topP }),\n ...(request.maxOutputTokens !== undefined && { max_tokens: request.maxOutputTokens }),\n ...(Option.isSome(tools) && { tools: tools.value }),\n ...(request.toolChoice !== undefined && { tool_choice: toolChoiceWire(request.toolChoice) }),\n ...(request.structured !== undefined && {\n response_format: responseFormatWire(request.structured),\n }),\n ...(cfg.extraBody?.(request) ?? {}),\n }\n}\n\n// ---------------------------------------------------------------------------\n// SSE → WireChunk\n// ---------------------------------------------------------------------------\n\nconst parseJsonUnknown = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\n\n/**\n * Decode one SSE event's `data` payload into a `WireChunk`. Returns `None` for\n * the `[DONE]` sentinel and for any payload that isn't a chunk we model, so a\n * stray keep-alive never aborts the turn.\n */\nconst decodeEvent = (data: string): Effect.Effect<Option.Option<WireChunk>> =>\n data.trim() === \"[DONE]\"\n ? Effect.succeedNone\n : parseJsonUnknown(data).pipe(Effect.flatMap(decodeChunk), Effect.option)\n\n// ---------------------------------------------------------------------------\n// Stream\n// ---------------------------------------------------------------------------\n\nconst buildStream = (cfg: ChatConfig) => {\n const url = `${cfg.baseUrl}${cfg.path ?? \"/chat/completions\"}`\n const auth = cfg.authHeader ?? HttpClientRequest.bearerToken(cfg.apiKey)\n return (\n request: CommonRequest,\n ): Stream.Stream<TurnEvent, AiError.AiError, HttpClient.HttpClient> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n yield* Capabilities.warnDroppedBlocks(request.history, \"output_image\", {\n provider: cfg.provider,\n capability: \"output_image\",\n reason:\n \"Assistant messages carry no image on this wire. Use `Turn.imagesAsInput` to resend it as user content.\",\n })\n yield* rejectToolOutputMedia(cfg.provider, request.history)\n const httpRequest = HttpClientRequest.post(url).pipe(\n auth,\n HttpClientRequest.setHeaders(cfg.extraHeaders ?? {}),\n HttpClientRequest.bodyJsonUnsafe(buildRequestBody(cfg, request)),\n HttpClientRequest.accept(\"text/event-stream\"),\n )\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError((cause) => transportFailure(cfg.provider, cause)))\n if (response.status >= 400) {\n const text = yield* response.text.pipe(Effect.orElseSucceed(() => \"\"))\n return Stream.fail(httpStatusError(cfg.provider, response.status, text))\n }\n\n return response.stream.pipe(\n Stream.mapError(\n (cause): AiError.AiError =>\n new AiError.Unavailable({ provider: cfg.provider, raw: cause }),\n ),\n SSE.fromBytes,\n Stream.mapEffect((ev) => decodeEvent(ev.data)),\n Stream.filterMap((chunk) =>\n Option.isSome(chunk) ? Result.succeed(chunk.value) : Result.failVoid,\n ),\n Stream.mapAccum((): Accumulator => emptyAccumulator, applyChunk, {\n // `onHalt` also fires on upstream failure and truncated streams, so\n // only emit `TurnComplete` once a `finish_reason` was observed.\n onHalt: (acc) =>\n Option.isSome(acc.finishReason)\n ? [TurnEvent.TurnComplete({ turn: accumulatorToTurn(acc) })]\n : [],\n }),\n )\n }),\n )\n}\n\n// ---------------------------------------------------------------------------\n// Constructors\n// ---------------------------------------------------------------------------\n\n/** Build a generic chat-completions `LanguageModelService`. */\nexport const make = (\n cfg: ChatConfig,\n): Effect.Effect<LanguageModelService, never, HttpClient.HttpClient> =>\n Effect.map(HttpClient.HttpClient, (client) => {\n const streamTurn: LanguageModelService[\"streamTurn\"] = (request) =>\n buildStream(cfg)(request).pipe(Stream.provideService(HttpClient.HttpClient, client))\n return {\n streamTurn,\n turn: turnFromStream(streamTurn),\n }\n })\n\n/** Layer registering the generic `LanguageModel` tag for a compatible gateway. */\nexport const layer = (cfg: ChatConfig): Layer.Layer<LanguageModel, never, HttpClient.HttpClient> =>\n Layer.effect(LanguageModel, make(cfg))\n"],"mappings":"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"}
package/dist/codec.d.mts CHANGED
@@ -1,10 +1,11 @@
1
- import { Option, Schema } from "effect";
1
+ import { Effect, Option, Schema } from "effect";
2
+ import * as AiError from "@effect-uai/core/AiError";
2
3
  import { ToolDescriptor } from "@effect-uai/core/Tool";
3
4
  import { Turn, TurnEvent } from "@effect-uai/core/Turn";
4
- import { HistoryItem, Usage } from "@effect-uai/core/Items";
5
+ import { HistoryItem, ToolCallOutput, Usage } from "@effect-uai/core/Items";
5
6
  import { StructuredFormat } from "@effect-uai/core/StructuredFormat";
6
7
  declare namespace codec_d_exports {
7
- export { Accumulator, WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, toolChoiceWire, toolsWire };
8
+ export { Accumulator, WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, responseFormatWire, toolChoiceWire, toolOutputText, toolsWire };
8
9
  }
9
10
  type WireMessage = {
10
11
  role: string;
@@ -12,6 +13,13 @@ type WireMessage = {
12
13
  tool_calls?: ReadonlyArray<Record<string, unknown>>;
13
14
  tool_call_id?: string;
14
15
  };
16
+ /**
17
+ * A tool message's content: text and JSON, joined. Tool messages carry text only
18
+ * on this dialect; `rejectToolOutputMedia` fails a request with media first.
19
+ */
20
+ declare const toolOutputText: (output: ToolCallOutput) => string;
21
+ /** Fails when a tool output in `history` carries an image or file, rather than drop it. */
22
+ declare const rejectToolOutputMedia: (provider: string, history: ReadonlyArray<HistoryItem>) => Effect.Effect<void, AiError.Unsupported>;
15
23
  /** Convert our `HistoryItem[]` history into chat `messages`. */
16
24
  declare const itemsToMessages: (items: ReadonlyArray<HistoryItem>) => ReadonlyArray<WireMessage>;
17
25
  declare const toolsWire: (descriptors: ReadonlyArray<ToolDescriptor>) => Option.Option<ReadonlyArray<Record<string, unknown>>>;
@@ -45,7 +53,7 @@ declare const WireChunk: Schema.Struct<{
45
53
  }>>>;
46
54
  }>;
47
55
  type WireChunk = typeof WireChunk.Type;
48
- declare const decodeChunk: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => import("effect/Effect").Effect<{
56
+ declare const decodeChunk: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => Effect.Effect<{
49
57
  readonly choices?: readonly {
50
58
  readonly index?: number | undefined;
51
59
  readonly delta?: {
@@ -92,5 +100,5 @@ declare const applyChunk: (acc: Accumulator, chunk: WireChunk) => Step;
92
100
  /** Assemble the terminal `Turn` from the accumulated state. */
93
101
  declare const accumulatorToTurn: (acc: Accumulator) => Turn;
94
102
  //#endregion
95
- export { Accumulator, WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, codec_d_exports as t, toolChoiceWire, toolsWire };
103
+ export { Accumulator, WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, responseFormatWire, codec_d_exports as t, toolChoiceWire, toolOutputText, toolsWire };
96
104
  //# sourceMappingURL=codec.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;KAyBK;EACH;EACA,kBAAkB,cAAc;EAChC,aAAa,cAAc;EAC3B;;;cAgFW,kBAAe,OAAW,cAAc,iBAAe,cAAc;cAOrE,YAAS,aACP,cAAc,oBAC1B,OAAO,OAAO,cAAc;KAe1B;WAIU;WAA2B;;cAE7B,iBAAc,QAAY,wBAAsB;cAQhD,qBAAkB,YACjB,8BACX;cA0CU,WAAS,OAAA;;;;;;;;;;;;;;;;;;;;;;;KAIV,mBAAmB,UAAU;cAE5B,cAAW,gBAAA,qCAAA,yCAAA;;;;;;;;;;;;;;;;;;;;;;GAAwC,OAAA;KAM3D;WACM;WACA;WACA;;KAGC;WACD;;WAEA,OAAO,oBAAoB;WAC3B,OAAO;WACP,OAAO;WACP,cAAc,OAAO;;cAGnB,kBAAkB;;KAS1B,iBAAiB,aAAa,cAAc;;;;;cAqFpC,aAAU,KAAS,aAAW,OAAS,cAAY;;cAYnD,oBAAiB,KAAS,gBAAc"}
1
+ {"version":3,"file":"codec.d.mts","names":[],"sources":["../src/codec.ts"],"mappings":";;;;;;;;;KA4BK;EACH;EACA,kBAAkB,cAAc;EAChC,aAAa,cAAc;EAC3B;;;;;;cAkDW,iBAAc,QAAY;;cAI1B,wBAAqB,kBAChB,SACP,cAAc,iBACtB,OAAO,aAAa,QAAQ;;cAgDlB,kBAAe,OAAW,cAAc,iBAAe,cAAc;cAOrE,YAAS,aACP,cAAc,oBAC1B,OAAO,OAAO,cAAc;KAe1B;WAIU;WAA2B;;cAE7B,iBAAc,QAAY,wBAAsB;cAQhD,qBAAkB,YACjB,8BACX;cA0CU,WAAS,OAAA;;;;;;;;;;;;;;;;;;;;;;;KAIV,mBAAmB,UAAU;cAE5B,cAAW,gBAAA,qCAAA,iBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;GAAwC,OAAA;KAM3D;WACM;WACA;WACA;;KAGC;WACD;;WAEA,OAAO,oBAAoB;WAC3B,OAAO;WACP,OAAO;WACP,cAAc,OAAO;;cAGnB,kBAAkB;;KAS1B,iBAAiB,aAAa,cAAc;;;;;cAqFpC,aAAU,KAAS,aAAW,OAAS,cAAY;;cAYnD,oBAAiB,KAAS,gBAAc"}
package/dist/codec.mjs CHANGED
@@ -1,7 +1,9 @@
1
- import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { Array, Match, Option, Result, Schema } from "effect";
1
+ import { n as __exportAll } from "./rolldown-runtime-EAsCxrDo.mjs";
2
+ import { Array, Effect, Match, Option, Result, Schema } from "effect";
3
+ import * as AiError from "@effect-uai/core/AiError";
3
4
  import { TurnEvent } from "@effect-uai/core/Turn";
4
5
  import { Base64 } from "effect/encoding";
6
+ import * as ToolOutput from "@effect-uai/core/ToolOutput";
5
7
  //#region src/codec.ts
6
8
  var codec_exports = /* @__PURE__ */ __exportAll({
7
9
  WireChunk: () => WireChunk,
@@ -10,8 +12,10 @@ var codec_exports = /* @__PURE__ */ __exportAll({
10
12
  decodeChunk: () => decodeChunk,
11
13
  emptyAccumulator: () => emptyAccumulator,
12
14
  itemsToMessages: () => itemsToMessages,
15
+ rejectToolOutputMedia: () => rejectToolOutputMedia,
13
16
  responseFormatWire: () => responseFormatWire,
14
17
  toolChoiceWire: () => toolChoiceWire,
18
+ toolOutputText: () => toolOutputText,
15
19
  toolsWire: () => toolsWire
16
20
  });
17
21
  /** A `data:` URI for inline image bytes; URLs pass through untouched. */
@@ -45,6 +49,18 @@ const encodeContent = (content) => Array.every(content, isTextBlock) ? content.m
45
49
  const part = blockToPart(b);
46
50
  return part === void 0 ? Result.failVoid : Result.succeed(part);
47
51
  });
52
+ const hasMedia = (item) => item.type === "function_call_output" && !Array.every(item.output, ToolOutput.isTextual);
53
+ /**
54
+ * A tool message's content: text and JSON, joined. Tool messages carry text only
55
+ * on this dialect; `rejectToolOutputMedia` fails a request with media first.
56
+ */
57
+ const toolOutputText = (output) => output.output.filter(ToolOutput.isTextual).map(ToolOutput.partText).join("\n");
58
+ /** Fails when a tool output in `history` carries an image or file, rather than drop it. */
59
+ const rejectToolOutputMedia = (provider, history) => Array.some(history, hasMedia) ? Effect.fail(new AiError.Unsupported({
60
+ provider,
61
+ capability: "tool_output_media",
62
+ reason: "Tool messages carry text only on the chat-completions wire."
63
+ })) : Effect.void;
48
64
  const toolCallWire = (call_id, name, args) => ({
49
65
  id: call_id,
50
66
  type: "function",
@@ -75,7 +91,7 @@ const foldItem = (acc, item) => Match.value(item).pipe(Match.discriminatorsExhau
75
91
  function_call_output: (o) => [...acc, {
76
92
  role: "tool",
77
93
  tool_call_id: o.call_id,
78
- content: o.output
94
+ content: toolOutputText(o)
79
95
  }],
80
96
  reasoning: () => acc
81
97
  }));
@@ -242,6 +258,6 @@ const accumulatorToTurn = (acc) => {
242
258
  };
243
259
  };
244
260
  //#endregion
245
- export { WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, responseFormatWire, codec_exports as t, toolChoiceWire, toolsWire };
261
+ export { WireChunk, accumulatorToTurn, applyChunk, decodeChunk, emptyAccumulator, itemsToMessages, rejectToolOutputMedia, responseFormatWire, codec_exports as t, toolChoiceWire, toolOutputText, toolsWire };
246
262
 
247
263
  //# sourceMappingURL=codec.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Match, Option, Result, Schema } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport type {\n ContentBlock,\n HistoryItem,\n InputImage,\n StopReason,\n Usage,\n} from \"@effect-uai/core/Items\"\nimport type { StructuredFormat } from \"@effect-uai/core/StructuredFormat\"\nimport type { ToolDescriptor } from \"@effect-uai/core/Tool\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\nimport { TurnEvent } from \"@effect-uai/core/Turn\"\n\n// ---------------------------------------------------------------------------\n// History → chat `messages`\n//\n// The OpenAI Chat Completions dialect: a flat list of `{ role, content, ... }`\n// objects where an assistant turn's tool calls live on the assistant message\n// (`tool_calls`) and each tool result is its own\n// `{ role: \"tool\", tool_call_id, content }` message. Our history is a flatter\n// item list (assistant message, then separate `function_call` items), so we\n// fold consecutive `function_call`s onto the preceding assistant message.\n// ---------------------------------------------------------------------------\n\ntype WireMessage = {\n role: string\n content: string | ReadonlyArray<Record<string, unknown>>\n tool_calls?: ReadonlyArray<Record<string, unknown>>\n tool_call_id?: string\n}\n\n/** A `data:` URI for inline image bytes; URLs pass through untouched. */\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 blockToPart = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => ({ type: \"text\", text: b.text }),\n output_text: (b) => ({ type: \"text\", text: b.text }),\n refusal: (b) => ({ type: \"text\", text: b.text }),\n input_image: (b) => ({ type: \"image_url\", image_url: { url: imageSourceToUrl(b.source) } }),\n // No assistant-image part here. Dropped rather than relocated onto a\n // user message, which is the caller's call: `Turn.imagesAsInput`.\n output_image: () => undefined,\n }),\n)\n\nconst isTextBlock = (b: ContentBlock): boolean =>\n b.type === \"input_text\" || b.type === \"output_text\" || b.type === \"refusal\"\n\nconst textOfBlock = (b: ContentBlock): string =>\n b.type === \"input_image\" || b.type === \"output_image\" ? \"\" : b.text\n\n/**\n * Collapse a message's content to a plain string when it is text-only (the\n * common case), otherwise emit the multimodal parts array.\n */\nconst encodeContent = (\n content: ReadonlyArray<ContentBlock>,\n): string | ReadonlyArray<Record<string, unknown>> =>\n Arr.every(content, isTextBlock)\n ? content.map(textOfBlock).join(\"\")\n : Arr.filterMap(content, (b) => {\n const part = blockToPart(b)\n return part === undefined ? Result.failVoid : Result.succeed(part)\n })\n\nconst toolCallWire = (call_id: string, name: string, args: string): Record<string, unknown> => ({\n id: call_id,\n type: \"function\",\n function: { name, arguments: args },\n})\n\nconst appendToolCall = (\n acc: ReadonlyArray<WireMessage>,\n call_id: string,\n name: string,\n args: string,\n): ReadonlyArray<WireMessage> => {\n const last = acc[acc.length - 1]\n const wire = toolCallWire(call_id, name, args)\n // Attach to the immediately-preceding assistant message (not a tool result),\n // matching the shape where tool calls ride the assistant turn.\n if (last !== undefined && last.role === \"assistant\" && last.tool_call_id === undefined) {\n return [...acc.slice(0, -1), { ...last, tool_calls: [...(last.tool_calls ?? []), wire] }]\n }\n return [...acc, { role: \"assistant\", content: \"\", tool_calls: [wire] }]\n}\n\nconst foldItem = (acc: ReadonlyArray<WireMessage>, item: HistoryItem): ReadonlyArray<WireMessage> =>\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m) => [...acc, { role: m.role, content: encodeContent(m.content) }],\n function_call: (f) => appendToolCall(acc, f.call_id, f.name, f.arguments),\n function_call_output: (o) => [\n ...acc,\n { role: \"tool\", tool_call_id: o.call_id, content: o.output },\n ],\n // Reasoning items are not replayed to the chat-completions endpoint.\n reasoning: () => acc,\n }),\n )\n\n/** Convert our `HistoryItem[]` history into chat `messages`. */\nexport const itemsToMessages = (items: ReadonlyArray<HistoryItem>): ReadonlyArray<WireMessage> =>\n Arr.reduce(items, [] as ReadonlyArray<WireMessage>, foldItem)\n\n// ---------------------------------------------------------------------------\n// tools / tool_choice / response_format\n// ---------------------------------------------------------------------------\n\nexport const toolsWire = (\n descriptors: ReadonlyArray<ToolDescriptor>,\n): Option.Option<ReadonlyArray<Record<string, unknown>>> =>\n descriptors.length > 0\n ? Option.some(\n descriptors.map((t) => ({\n type: \"function\",\n function: {\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n ...(t.strict !== undefined && { strict: t.strict }),\n },\n })),\n )\n : Option.none()\n\ntype ToolChoice =\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n\nexport const toolChoiceWire = (choice: ToolChoice): string | Record<string, unknown> =>\n Match.value(choice).pipe(\n Match.when(\"auto\", () => \"auto\" as const),\n Match.when(\"required\", () => \"required\" as const),\n Match.when(\"none\", () => \"none\" as const),\n Match.orElse((c) => ({ type: \"function\", function: { name: c.name } })),\n )\n\nexport const responseFormatWire = (\n structured: StructuredFormat<unknown>,\n): Record<string, unknown> => ({\n type: \"json_schema\",\n json_schema: {\n name: structured.name,\n schema: structured.schema[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" }),\n ...(structured.strict !== undefined && { strict: structured.strict }),\n },\n})\n\n// ---------------------------------------------------------------------------\n// Streaming decode: chat.completion.chunk → TurnEvent\n// ---------------------------------------------------------------------------\n\nconst WireFunction = Schema.Struct({\n name: Schema.optional(Schema.NullOr(Schema.String)),\n arguments: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\nconst WireToolCall = Schema.Struct({\n index: Schema.optional(Schema.Number),\n id: Schema.optional(Schema.NullOr(Schema.String)),\n function: Schema.optional(WireFunction),\n})\n\nconst WireDelta = Schema.Struct({\n role: Schema.optional(Schema.NullOr(Schema.String)),\n content: Schema.optional(Schema.NullOr(Schema.String)),\n tool_calls: Schema.optional(Schema.NullOr(Schema.Array(WireToolCall))),\n})\n\nconst WireChoice = Schema.Struct({\n index: Schema.optional(Schema.Number),\n delta: Schema.optional(WireDelta),\n finish_reason: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\nconst WireUsage = Schema.Struct({\n prompt_tokens: Schema.optional(Schema.Number),\n completion_tokens: Schema.optional(Schema.Number),\n total_tokens: Schema.optional(Schema.Number),\n})\n\nexport const WireChunk = Schema.Struct({\n choices: Schema.optional(Schema.Array(WireChoice)),\n usage: Schema.optional(Schema.NullOr(WireUsage)),\n})\nexport type WireChunk = typeof WireChunk.Type\n\nexport const decodeChunk = Schema.decodeUnknownEffect(WireChunk)\n\n// ---------------------------------------------------------------------------\n// Accumulator\n// ---------------------------------------------------------------------------\n\ntype ToolAcc = {\n readonly call_id: string\n readonly name: string\n readonly arguments: string\n}\n\nexport type Accumulator = {\n readonly text: string\n /** Tool calls keyed by their wire `index` (defaults to position when absent). */\n readonly tools: ReadonlyMap<number, ToolAcc>\n readonly order: ReadonlyArray<number>\n readonly usage: Usage\n readonly finishReason: Option.Option<string>\n}\n\nexport const emptyAccumulator: Accumulator = {\n text: \"\",\n tools: new Map(),\n order: [],\n usage: {},\n finishReason: Option.none(),\n}\n\n/** State threaded through the fold plus the events the step produced. */\ntype Step = readonly [Accumulator, ReadonlyArray<TurnEvent>]\n\nconst usageFrom = (wire: typeof WireUsage.Type): Usage => ({\n ...(wire.prompt_tokens !== undefined && { input_tokens: wire.prompt_tokens }),\n ...(wire.completion_tokens !== undefined && { output_tokens: wire.completion_tokens }),\n ...(wire.total_tokens !== undefined && { total_tokens: wire.total_tokens }),\n})\n\nconst reasonToStop = Match.type<string>().pipe(\n Match.when(\"stop\", () => \"stop\" as const),\n Match.when(\"length\", () => \"max_tokens\" as const),\n Match.when(\"tool_calls\", () => \"tool_calls\" as const),\n Match.when(\"content_filter\", () => \"content_filter\" as const),\n Match.orElse(() => \"stop\" as const),\n)\n\nconst stopReasonOf = (acc: Accumulator): StopReason =>\n Option.match(acc.finishReason, {\n onNone: () => (acc.tools.size > 0 ? \"tool_calls\" : \"stop\"),\n onSome: reasonToStop,\n })\n\nconst withTool = (acc: Accumulator, index: number, tool: ToolAcc, isNew: boolean): Accumulator => ({\n ...acc,\n tools: new Map(acc.tools).set(index, tool),\n order: isNew ? [...acc.order, index] : acc.order,\n})\n\nconst applyContent = (acc: Accumulator, delta: typeof WireDelta.Type | undefined): Step => {\n const content = delta?.content\n return content !== undefined && content !== null && content.length > 0\n ? [{ ...acc, text: acc.text + content }, [TurnEvent.TextDelta({ text: content })]]\n : [acc, []]\n}\n\nconst applyToolCall = (acc: Accumulator, tc: typeof WireToolCall.Type, position: number): Step => {\n const index = tc.index ?? position\n const argsDelta = tc.function?.arguments ?? \"\"\n const existing = acc.tools.get(index)\n if (existing === undefined) {\n // Only `index` is guaranteed on a streaming tool-call chunk, so the id may\n // be absent. The synthesised fallback is 9-char alphanumeric: valid on every\n // chat-completions wire (Mistral additionally requires `^[a-zA-Z0-9]{9}$`),\n // and it is replayed on the next turn.\n const call_id = tc.id ?? String(index).padStart(9, \"0\")\n const name = tc.function?.name ?? \"\"\n return [\n withTool(acc, index, { call_id, name, arguments: argsDelta }, true),\n [\n TurnEvent.ToolCallStart({ call_id, name }),\n ...(argsDelta.length > 0\n ? [TurnEvent.ToolCallArgsDelta({ call_id, delta: argsDelta })]\n : []),\n ],\n ]\n }\n return argsDelta.length > 0\n ? [\n withTool(acc, index, { ...existing, arguments: existing.arguments + argsDelta }, false),\n [TurnEvent.ToolCallArgsDelta({ call_id: existing.call_id, delta: argsDelta })],\n ]\n : [acc, []]\n}\n\nconst chain = (step: Step, next: (acc: Accumulator) => Step): Step => {\n const [acc, events] = step\n const [acc2, more] = next(acc)\n return [acc2, [...events, ...more]]\n}\n\nconst applyChoice = (step: Step, choice: typeof WireChoice.Type): Step => {\n const withContent = chain(step, (acc) => applyContent(acc, choice.delta))\n const withTools = Arr.reduce(choice.delta?.tool_calls ?? [], withContent, (s: Step, tc, i) =>\n chain(s, (acc) => applyToolCall(acc, tc, i)),\n )\n const reason = choice.finish_reason\n return reason !== undefined && reason !== null\n ? [{ ...withTools[0], finishReason: Option.some(reason) }, withTools[1]]\n : withTools\n}\n\n/**\n * Fold one decoded chunk into the accumulator, returning the next state and the\n * `TurnEvent`s the chunk produced.\n */\nexport const applyChunk = (acc: Accumulator, chunk: WireChunk): Step => {\n const afterChoices = Arr.reduce(chunk.choices ?? [], [acc, []] as Step, applyChoice)\n const usage = chunk.usage\n return usage !== undefined && usage !== null\n ? chain(afterChoices, (a) => {\n const u = usageFrom(usage)\n return [{ ...a, usage: u }, [TurnEvent.UsageUpdate({ usage: u })]]\n })\n : afterChoices\n}\n\n/** Assemble the terminal `Turn` from the accumulated state. */\nexport const accumulatorToTurn = (acc: Accumulator): Turn => {\n const message: ReadonlyArray<HistoryItem> =\n acc.text.length > 0\n ? [{ type: \"message\", role: \"assistant\", content: [{ type: \"output_text\", text: acc.text }] }]\n : []\n const toolCalls: ReadonlyArray<HistoryItem> = acc.order.flatMap((index) => {\n const t = acc.tools.get(index)\n return t === undefined\n ? []\n : [\n {\n type: \"function_call\" as const,\n call_id: t.call_id,\n name: t.name,\n arguments: t.arguments,\n },\n ]\n })\n return {\n items: [...message, ...toolCalls],\n usage: acc.usage,\n stop_reason: stopReasonOf(acc),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Match, Option, Result, Schema } from \"effect\"\nimport { Base64 } from \"effect/encoding\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type {\n ContentBlock,\n HistoryItem,\n InputImage,\n StopReason,\n ToolCallOutput,\n Usage,\n} from \"@effect-uai/core/Items\"\nimport type { StructuredFormat } from \"@effect-uai/core/StructuredFormat\"\nimport type { ToolDescriptor } from \"@effect-uai/core/Tool\"\nimport * as ToolOutput from \"@effect-uai/core/ToolOutput\"\nimport type { Turn } from \"@effect-uai/core/Turn\"\nimport { TurnEvent } from \"@effect-uai/core/Turn\"\n\n// ---------------------------------------------------------------------------\n// History → chat `messages`\n//\n// The OpenAI Chat Completions dialect: a flat list of `{ role, content, ... }`\n// objects where an assistant turn's tool calls live on the assistant message\n// (`tool_calls`) and each tool result is its own\n// `{ role: \"tool\", tool_call_id, content }` message. Our history is a flatter\n// item list (assistant message, then separate `function_call` items), so we\n// fold consecutive `function_call`s onto the preceding assistant message.\n// ---------------------------------------------------------------------------\n\ntype WireMessage = {\n role: string\n content: string | ReadonlyArray<Record<string, unknown>>\n tool_calls?: ReadonlyArray<Record<string, unknown>>\n tool_call_id?: string\n}\n\n/** A `data:` URI for inline image bytes; URLs pass through untouched. */\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 blockToPart = Match.type<ContentBlock>().pipe(\n Match.discriminatorsExhaustive(\"type\")({\n input_text: (b) => ({ type: \"text\", text: b.text }),\n output_text: (b) => ({ type: \"text\", text: b.text }),\n refusal: (b) => ({ type: \"text\", text: b.text }),\n input_image: (b) => ({ type: \"image_url\", image_url: { url: imageSourceToUrl(b.source) } }),\n // No assistant-image part here. Dropped rather than relocated onto a\n // user message, which is the caller's call: `Turn.imagesAsInput`.\n output_image: () => undefined,\n }),\n)\n\nconst isTextBlock = (b: ContentBlock): boolean =>\n b.type === \"input_text\" || b.type === \"output_text\" || b.type === \"refusal\"\n\nconst textOfBlock = (b: ContentBlock): string =>\n b.type === \"input_image\" || b.type === \"output_image\" ? \"\" : b.text\n\n/**\n * Collapse a message's content to a plain string when it is text-only (the\n * common case), otherwise emit the multimodal parts array.\n */\nconst encodeContent = (\n content: ReadonlyArray<ContentBlock>,\n): string | ReadonlyArray<Record<string, unknown>> =>\n Arr.every(content, isTextBlock)\n ? content.map(textOfBlock).join(\"\")\n : Arr.filterMap(content, (b) => {\n const part = blockToPart(b)\n return part === undefined ? Result.failVoid : Result.succeed(part)\n })\n\nconst hasMedia = (item: HistoryItem): boolean =>\n item.type === \"function_call_output\" && !Arr.every(item.output, ToolOutput.isTextual)\n\n/**\n * A tool message's content: text and JSON, joined. Tool messages carry text only\n * on this dialect; `rejectToolOutputMedia` fails a request with media first.\n */\nexport const toolOutputText = (output: ToolCallOutput): string =>\n output.output.filter(ToolOutput.isTextual).map(ToolOutput.partText).join(\"\\n\")\n\n/** Fails when a tool output in `history` carries an image or file, rather than drop it. */\nexport const rejectToolOutputMedia = (\n provider: string,\n history: ReadonlyArray<HistoryItem>,\n): Effect.Effect<void, AiError.Unsupported> =>\n Arr.some(history, hasMedia)\n ? Effect.fail(\n new AiError.Unsupported({\n provider,\n capability: \"tool_output_media\",\n reason: \"Tool messages carry text only on the chat-completions wire.\",\n }),\n )\n : Effect.void\n\nconst toolCallWire = (call_id: string, name: string, args: string): Record<string, unknown> => ({\n id: call_id,\n type: \"function\",\n function: { name, arguments: args },\n})\n\nconst appendToolCall = (\n acc: ReadonlyArray<WireMessage>,\n call_id: string,\n name: string,\n args: string,\n): ReadonlyArray<WireMessage> => {\n const last = acc[acc.length - 1]\n const wire = toolCallWire(call_id, name, args)\n // Attach to the immediately-preceding assistant message (not a tool result),\n // matching the shape where tool calls ride the assistant turn.\n if (last !== undefined && last.role === \"assistant\" && last.tool_call_id === undefined) {\n return [...acc.slice(0, -1), { ...last, tool_calls: [...(last.tool_calls ?? []), wire] }]\n }\n return [...acc, { role: \"assistant\", content: \"\", tool_calls: [wire] }]\n}\n\nconst foldItem = (acc: ReadonlyArray<WireMessage>, item: HistoryItem): ReadonlyArray<WireMessage> =>\n Match.value(item).pipe(\n Match.discriminatorsExhaustive(\"type\")({\n message: (m) => [...acc, { role: m.role, content: encodeContent(m.content) }],\n function_call: (f) => appendToolCall(acc, f.call_id, f.name, f.arguments),\n function_call_output: (o) => [\n ...acc,\n { role: \"tool\", tool_call_id: o.call_id, content: toolOutputText(o) },\n ],\n // Reasoning items are not replayed to the chat-completions endpoint.\n reasoning: () => acc,\n }),\n )\n\n/** Convert our `HistoryItem[]` history into chat `messages`. */\nexport const itemsToMessages = (items: ReadonlyArray<HistoryItem>): ReadonlyArray<WireMessage> =>\n Arr.reduce(items, [] as ReadonlyArray<WireMessage>, foldItem)\n\n// ---------------------------------------------------------------------------\n// tools / tool_choice / response_format\n// ---------------------------------------------------------------------------\n\nexport const toolsWire = (\n descriptors: ReadonlyArray<ToolDescriptor>,\n): Option.Option<ReadonlyArray<Record<string, unknown>>> =>\n descriptors.length > 0\n ? Option.some(\n descriptors.map((t) => ({\n type: \"function\",\n function: {\n name: t.name,\n description: t.description,\n parameters: t.inputSchema,\n ...(t.strict !== undefined && { strict: t.strict }),\n },\n })),\n )\n : Option.none()\n\ntype ToolChoice =\n | \"auto\"\n | \"required\"\n | \"none\"\n | { readonly type: \"function\"; readonly name: string }\n\nexport const toolChoiceWire = (choice: ToolChoice): string | Record<string, unknown> =>\n Match.value(choice).pipe(\n Match.when(\"auto\", () => \"auto\" as const),\n Match.when(\"required\", () => \"required\" as const),\n Match.when(\"none\", () => \"none\" as const),\n Match.orElse((c) => ({ type: \"function\", function: { name: c.name } })),\n )\n\nexport const responseFormatWire = (\n structured: StructuredFormat<unknown>,\n): Record<string, unknown> => ({\n type: \"json_schema\",\n json_schema: {\n name: structured.name,\n schema: structured.schema[\"~standard\"].jsonSchema.input({ target: \"draft-2020-12\" }),\n ...(structured.strict !== undefined && { strict: structured.strict }),\n },\n})\n\n// ---------------------------------------------------------------------------\n// Streaming decode: chat.completion.chunk → TurnEvent\n// ---------------------------------------------------------------------------\n\nconst WireFunction = Schema.Struct({\n name: Schema.optional(Schema.NullOr(Schema.String)),\n arguments: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\nconst WireToolCall = Schema.Struct({\n index: Schema.optional(Schema.Number),\n id: Schema.optional(Schema.NullOr(Schema.String)),\n function: Schema.optional(WireFunction),\n})\n\nconst WireDelta = Schema.Struct({\n role: Schema.optional(Schema.NullOr(Schema.String)),\n content: Schema.optional(Schema.NullOr(Schema.String)),\n tool_calls: Schema.optional(Schema.NullOr(Schema.Array(WireToolCall))),\n})\n\nconst WireChoice = Schema.Struct({\n index: Schema.optional(Schema.Number),\n delta: Schema.optional(WireDelta),\n finish_reason: Schema.optional(Schema.NullOr(Schema.String)),\n})\n\nconst WireUsage = Schema.Struct({\n prompt_tokens: Schema.optional(Schema.Number),\n completion_tokens: Schema.optional(Schema.Number),\n total_tokens: Schema.optional(Schema.Number),\n})\n\nexport const WireChunk = Schema.Struct({\n choices: Schema.optional(Schema.Array(WireChoice)),\n usage: Schema.optional(Schema.NullOr(WireUsage)),\n})\nexport type WireChunk = typeof WireChunk.Type\n\nexport const decodeChunk = Schema.decodeUnknownEffect(WireChunk)\n\n// ---------------------------------------------------------------------------\n// Accumulator\n// ---------------------------------------------------------------------------\n\ntype ToolAcc = {\n readonly call_id: string\n readonly name: string\n readonly arguments: string\n}\n\nexport type Accumulator = {\n readonly text: string\n /** Tool calls keyed by their wire `index` (defaults to position when absent). */\n readonly tools: ReadonlyMap<number, ToolAcc>\n readonly order: ReadonlyArray<number>\n readonly usage: Usage\n readonly finishReason: Option.Option<string>\n}\n\nexport const emptyAccumulator: Accumulator = {\n text: \"\",\n tools: new Map(),\n order: [],\n usage: {},\n finishReason: Option.none(),\n}\n\n/** State threaded through the fold plus the events the step produced. */\ntype Step = readonly [Accumulator, ReadonlyArray<TurnEvent>]\n\nconst usageFrom = (wire: typeof WireUsage.Type): Usage => ({\n ...(wire.prompt_tokens !== undefined && { input_tokens: wire.prompt_tokens }),\n ...(wire.completion_tokens !== undefined && { output_tokens: wire.completion_tokens }),\n ...(wire.total_tokens !== undefined && { total_tokens: wire.total_tokens }),\n})\n\nconst reasonToStop = Match.type<string>().pipe(\n Match.when(\"stop\", () => \"stop\" as const),\n Match.when(\"length\", () => \"max_tokens\" as const),\n Match.when(\"tool_calls\", () => \"tool_calls\" as const),\n Match.when(\"content_filter\", () => \"content_filter\" as const),\n Match.orElse(() => \"stop\" as const),\n)\n\nconst stopReasonOf = (acc: Accumulator): StopReason =>\n Option.match(acc.finishReason, {\n onNone: () => (acc.tools.size > 0 ? \"tool_calls\" : \"stop\"),\n onSome: reasonToStop,\n })\n\nconst withTool = (acc: Accumulator, index: number, tool: ToolAcc, isNew: boolean): Accumulator => ({\n ...acc,\n tools: new Map(acc.tools).set(index, tool),\n order: isNew ? [...acc.order, index] : acc.order,\n})\n\nconst applyContent = (acc: Accumulator, delta: typeof WireDelta.Type | undefined): Step => {\n const content = delta?.content\n return content !== undefined && content !== null && content.length > 0\n ? [{ ...acc, text: acc.text + content }, [TurnEvent.TextDelta({ text: content })]]\n : [acc, []]\n}\n\nconst applyToolCall = (acc: Accumulator, tc: typeof WireToolCall.Type, position: number): Step => {\n const index = tc.index ?? position\n const argsDelta = tc.function?.arguments ?? \"\"\n const existing = acc.tools.get(index)\n if (existing === undefined) {\n // Only `index` is guaranteed on a streaming tool-call chunk, so the id may\n // be absent. The synthesised fallback is 9-char alphanumeric: valid on every\n // chat-completions wire (Mistral additionally requires `^[a-zA-Z0-9]{9}$`),\n // and it is replayed on the next turn.\n const call_id = tc.id ?? String(index).padStart(9, \"0\")\n const name = tc.function?.name ?? \"\"\n return [\n withTool(acc, index, { call_id, name, arguments: argsDelta }, true),\n [\n TurnEvent.ToolCallStart({ call_id, name }),\n ...(argsDelta.length > 0\n ? [TurnEvent.ToolCallArgsDelta({ call_id, delta: argsDelta })]\n : []),\n ],\n ]\n }\n return argsDelta.length > 0\n ? [\n withTool(acc, index, { ...existing, arguments: existing.arguments + argsDelta }, false),\n [TurnEvent.ToolCallArgsDelta({ call_id: existing.call_id, delta: argsDelta })],\n ]\n : [acc, []]\n}\n\nconst chain = (step: Step, next: (acc: Accumulator) => Step): Step => {\n const [acc, events] = step\n const [acc2, more] = next(acc)\n return [acc2, [...events, ...more]]\n}\n\nconst applyChoice = (step: Step, choice: typeof WireChoice.Type): Step => {\n const withContent = chain(step, (acc) => applyContent(acc, choice.delta))\n const withTools = Arr.reduce(choice.delta?.tool_calls ?? [], withContent, (s: Step, tc, i) =>\n chain(s, (acc) => applyToolCall(acc, tc, i)),\n )\n const reason = choice.finish_reason\n return reason !== undefined && reason !== null\n ? [{ ...withTools[0], finishReason: Option.some(reason) }, withTools[1]]\n : withTools\n}\n\n/**\n * Fold one decoded chunk into the accumulator, returning the next state and the\n * `TurnEvent`s the chunk produced.\n */\nexport const applyChunk = (acc: Accumulator, chunk: WireChunk): Step => {\n const afterChoices = Arr.reduce(chunk.choices ?? [], [acc, []] as Step, applyChoice)\n const usage = chunk.usage\n return usage !== undefined && usage !== null\n ? chain(afterChoices, (a) => {\n const u = usageFrom(usage)\n return [{ ...a, usage: u }, [TurnEvent.UsageUpdate({ usage: u })]]\n })\n : afterChoices\n}\n\n/** Assemble the terminal `Turn` from the accumulated state. */\nexport const accumulatorToTurn = (acc: Accumulator): Turn => {\n const message: ReadonlyArray<HistoryItem> =\n acc.text.length > 0\n ? [{ type: \"message\", role: \"assistant\", content: [{ type: \"output_text\", text: acc.text }] }]\n : []\n const toolCalls: ReadonlyArray<HistoryItem> = acc.order.flatMap((index) => {\n const t = acc.tools.get(index)\n return t === undefined\n ? []\n : [\n {\n type: \"function_call\" as const,\n call_id: t.call_id,\n name: t.name,\n arguments: t.arguments,\n },\n ]\n })\n return {\n items: [...message, ...toolCalls],\n usage: acc.usage,\n stop_reason: stopReasonOf(acc),\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {}