| 1 | /** |
| 2 | * The `OpenAiClient` module provides an Effect service for OpenAI-compatible |
| 3 | * chat completions and embeddings APIs. It builds on the Effect HTTP client, |
| 4 | * adds authentication and OpenAI organization or project headers, and exposes |
| 5 | * typed helpers for non-streaming chat completions, streaming chat completions, |
| 6 | * and embedding requests. |
| 7 | * |
| 8 | * @stability unstable |
| 9 | * @since 4.0.0 |
| 10 | */ |
| 11 | import type * as AiError from "effect/ai/AiError" |
| 12 | import * as Array from "effect/Array" |
| 13 | import type * as Config from "effect/Config" |
| 14 | import * as Context from "effect/Context" |
| 15 | import * as Effect from "effect/Effect" |
| 16 | import * as Sse from "effect/encoding/Sse" |
| 17 | import { identity } from "effect/Function" |
| 18 | import * as Headers from "effect/http/Headers" |
| 19 | import * as HttpClient from "effect/http/HttpClient" |
| 20 | import * as HttpClientRequest from "effect/http/HttpClientRequest" |
| 21 | import * as HttpClientResponse from "effect/http/HttpClientResponse" |
| 22 | import * as Layer from "effect/Layer" |
| 23 | import * as Redacted from "effect/Redacted" |
| 24 | import * as Schema from "effect/Schema" |
| 25 | import * as Stream from "effect/Stream" |
| 26 | import * as Errors from "./internal/errors.ts" |
| 27 | import { OpenAiConfig } from "./OpenAiConfig.ts" |
| 28 | |
| 29 | /** |
| 30 | * Effect service interface for OpenAI-compatible chat completions and embeddings. |
| 31 | * |
| 32 | * **Details** |
| 33 | * |
| 34 | * Exposes the configured HTTP client plus helpers for non-streaming chat |
| 35 | * completions, streaming chat completions, and embeddings. Transport and |
| 36 | * schema decoding failures are mapped to `AiError`. |
| 37 | * |
| 38 | * @stability unstable |
| 39 | * @category services |
| 40 | * @since 4.0.0 |
| 41 | */ |
| 42 | export interface Service { |
| 43 | readonly client: HttpClient.HttpClient |
| 44 | readonly createResponse: ( |
| 45 | options: CreateResponseRequestJson |
| 46 | ) => Effect.Effect< |
| 47 | [body: CreateResponse200, response: HttpClientResponse.HttpClientResponse], |
| 48 | AiError.AiError |
| 49 | > |
| 50 | readonly createResponseStream: ( |
| 51 | options: Omit<CreateResponseRequestJson, "stream" | "stream_options"> |
| 52 | ) => Effect.Effect< |
| 53 | [ |
| 54 | response: HttpClientResponse.HttpClientResponse, |
| 55 | stream: Stream.Stream<CreateResponse200Sse, AiError.AiError> |
| 56 | ], |
| 57 | AiError.AiError |
| 58 | > |
| 59 | readonly createEmbedding: ( |
| 60 | options: CreateEmbeddingRequestJson |
| 61 | ) => Effect.Effect<CreateEmbedding200, AiError.AiError> |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * Service tag for the OpenAI-compatible chat completions and embeddings client. |
| 66 | * |
| 67 | * **When to use** |
| 68 | * |
| 69 | * Use when building effects that depend on the low-level OpenAI-compatible |
| 70 | * client through context rather than receiving the client as a value. |
| 71 | * |
| 72 | * **Details** |
| 73 | * |
| 74 | * The tagged service is the `Service` interface produced by `make` and provided |
| 75 | * by `layer` or `layerConfig`. |
| 76 | * |
| 77 | * @see {@link Service} for the operations provided by the service |
| 78 | * @see {@link make} for constructing the service from explicit options |
| 79 | * @see {@link layer} for providing the service from explicit options |
| 80 | * @see {@link layerConfig} for loading client settings from `Config` |
| 81 | * |
| 82 | * @stability unstable |
| 83 | * @category services |
| 84 | * @since 4.0.0 |
| 85 | */ |
| 86 | export class OpenAiClient extends Context.Service<OpenAiClient, Service>()( |
| 87 | "@effect/ai-openai-compat/OpenAiClient" |
| 88 | ) {} |
| 89 | |
| 90 | /** |
| 91 | * Configuration options used to construct an OpenAI-compatible client. |
| 92 | * |
| 93 | * @stability unstable |
| 94 | * @category options |
| 95 | * @since 4.0.0 |
| 96 | */ |
| 97 | export type Options = { |
| 98 | readonly apiKey?: Redacted.Redacted<string> | undefined |
| 99 | readonly apiUrl?: string | undefined |
| 100 | readonly organizationId?: Redacted.Redacted<string> | undefined |
| 101 | readonly projectId?: Redacted.Redacted<string> | undefined |
| 102 | readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined |
| 103 | } |
| 104 | |
| 105 | const RedactedOpenAiHeaders = { |
| 106 | OpenAiOrganization: "openai-organization", |
| 107 | OpenAiProject: "openai-project" |
| 108 | } |
| 109 | |
| 110 | const withRedactedHeaders = Effect.updateService( |
| 111 | Headers.CurrentRedactedNames, |
| 112 | Array.appendAll(Object.values(RedactedOpenAiHeaders)) |
| 113 | ) |
| 114 | |
| 115 | /** |
| 116 | * Constructs an OpenAI-compatible client service from explicit options. |
| 117 | * |
| 118 | * **When to use** |
| 119 | * |
| 120 | * Use when you need the OpenAI-compatible client service value inside an effect. |
| 121 | * |
| 122 | * **Details** |
| 123 | * |
| 124 | * The returned service uses the current `HttpClient`, prepends `apiUrl` or |
| 125 | * `https://api.openai.com/v1`, adds authentication and OpenAI |
| 126 | * organization/project headers, accepts JSON responses, and applies |
| 127 | * `transformClient` when provided. |
| 128 | * |
| 129 | * **Gotchas** |
| 130 | * |
| 131 | * A scoped `OpenAiConfig.withClientTransform` is applied when request helpers |
| 132 | * run, after the `transformClient` option supplied to `make`. |
| 133 | * |
| 134 | * @see {@link layer} for providing this client from explicit options |
| 135 | * @see {@link layerConfig} for loading client settings from `Config` |
| 136 | * |
| 137 | * @stability unstable |
| 138 | * @category constructors |
| 139 | * @since 4.0.0 |
| 140 | */ |
| 141 | export const make = Effect.fnUntraced( |
| 142 | function*(options: Options): Effect.fn.Return<Service, never, HttpClient.HttpClient> { |
| 143 | const baseClient = yield* HttpClient.HttpClient |
| 144 | |
| 145 | const httpClient = baseClient.pipe( |
| 146 | HttpClient.mapRequest((request) => |
| 147 | request.pipe( |
| 148 | HttpClientRequest.prependUrl(options.apiUrl ?? "https://api.openai.com/v1"), |
| 149 | options.apiKey !== undefined |
| 150 | ? HttpClientRequest.bearerToken(Redacted.value(options.apiKey)) |
| 151 | : identity, |
| 152 | options.organizationId !== undefined |
| 153 | ? HttpClientRequest.setHeader( |
| 154 | RedactedOpenAiHeaders.OpenAiOrganization, |
| 155 | Redacted.value(options.organizationId) |
| 156 | ) |
| 157 | : identity, |
| 158 | options.projectId !== undefined |
| 159 | ? HttpClientRequest.setHeader( |
| 160 | RedactedOpenAiHeaders.OpenAiProject, |
| 161 | Redacted.value(options.projectId) |
| 162 | ) |
| 163 | : identity, |
| 164 | HttpClientRequest.acceptJson |
| 165 | ) |
| 166 | ), |
| 167 | options.transformClient !== undefined |
| 168 | ? options.transformClient |
| 169 | : identity |
| 170 | ) |
| 171 | |
| 172 | const resolveHttpClient = Effect.map( |
| 173 | OpenAiConfig.getOrUndefined, |
| 174 | (config) => |
| 175 | config?.transformClient !== undefined |
| 176 | ? config.transformClient(httpClient) |
| 177 | : httpClient |
| 178 | ) |
| 179 | |
| 180 | const decodeResponse = HttpClientResponse.schemaBodyJson(ChatCompletionResponse) |
| 181 | |
| 182 | const createResponse = ( |
| 183 | payload: CreateResponseRequestJson |
| 184 | ): Effect.Effect< |
| 185 | [body: CreateResponse200, response: HttpClientResponse.HttpClientResponse], |
| 186 | AiError.AiError |
| 187 | > => |
| 188 | resolveHttpClient.pipe( |
| 189 | Effect.flatMap((client) => |
| 190 | HttpClientRequest.post("/chat/completions").pipe( |
| 191 | HttpClientRequest.bodyJsonUnsafe(payload), |
| 192 | HttpClient.filterStatusOk(client).execute, |
| 193 | Effect.flatMap((response) => |
| 194 | Effect.map(decodeResponse(response), ( |
| 195 | body |
| 196 | ): [CreateResponse200, HttpClientResponse.HttpClientResponse] => [ |
| 197 | body, |
| 198 | response |
| 199 | ]) |
| 200 | ), |
| 201 | Effect.catchTags({ |
| 202 | HttpClientError: (error) => Errors.mapHttpClientError(error, "createResponse"), |
| 203 | SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createResponse")) |
| 204 | }) |
| 205 | ) |
| 206 | ), |
| 207 | withRedactedHeaders |
| 208 | ) |
| 209 | |
| 210 | const buildResponseStream = ( |
| 211 | response: HttpClientResponse.HttpClientResponse |
| 212 | ): [ |
| 213 | HttpClientResponse.HttpClientResponse, |
| 214 | Stream.Stream<CreateResponse200Sse, AiError.AiError> |
| 215 | ] => { |
| 216 | const stream = response.stream.pipe( |
| 217 | Stream.decodeText(), |
| 218 | Stream.pipeThroughChannel(Sse.decode()), |
| 219 | Stream.flatMap((event) => { |
| 220 | const data = decodeChatCompletionSseData(event.data) |
| 221 | return Stream.fromIterable(data !== undefined ? [data] : []) |
| 222 | }), |
| 223 | Stream.takeUntil((event) => event === "[DONE]"), |
| 224 | Stream.catchTags({ |
| 225 | Retry: (error) => Stream.die(error), |
| 226 | SseError: (error) => Stream.fail(Errors.mapSseError(error, "createResponseStream")), |
| 227 | HttpClientError: (error) => Stream.fromEffect(Errors.mapHttpClientError(error, "createResponseStream")) |
| 228 | }) |
| 229 | ) as any |
| 230 | return [response, stream] |
| 231 | } |
| 232 | |
| 233 | const createResponseStream: Service["createResponseStream"] = (payload) => |
| 234 | resolveHttpClient.pipe( |
| 235 | Effect.flatMap((client) => |
| 236 | HttpClientRequest.post("/chat/completions").pipe( |
| 237 | HttpClientRequest.bodyJsonUnsafe({ |
| 238 | ...payload, |
| 239 | stream: true, |
| 240 | stream_options: { |
| 241 | include_usage: true |
| 242 | } |
| 243 | }), |
| 244 | HttpClient.filterStatusOk(client).execute, |
| 245 | Effect.map(buildResponseStream), |
| 246 | Effect.catchTag( |
| 247 | "HttpClientError", |
| 248 | (error) => Errors.mapHttpClientError(error, "createResponseStream") |
| 249 | ) |
| 250 | ) |
| 251 | ), |
| 252 | withRedactedHeaders |
| 253 | ) |
| 254 | |
| 255 | const decodeEmbedding = HttpClientResponse.schemaBodyJson(CreateEmbeddingResponseSchema) |
| 256 | |
| 257 | const createEmbedding = ( |
| 258 | payload: CreateEmbeddingRequestJson |
| 259 | ): Effect.Effect<CreateEmbedding200, AiError.AiError> => |
| 260 | resolveHttpClient.pipe( |
| 261 | Effect.flatMap((client) => |
| 262 | HttpClientRequest.post("/embeddings").pipe( |
| 263 | HttpClientRequest.bodyJsonUnsafe(payload), |
| 264 | HttpClient.filterStatusOk(client).execute, |
| 265 | Effect.flatMap(decodeEmbedding), |
| 266 | Effect.catchTags({ |
| 267 | HttpClientError: (error) => Errors.mapHttpClientError(error, "createEmbedding"), |
| 268 | SchemaError: (error) => Effect.fail(Errors.mapSchemaError(error, "createEmbedding")) |
| 269 | }) |
| 270 | ) |
| 271 | ), |
| 272 | withRedactedHeaders |
| 273 | ) |
| 274 | |
| 275 | return OpenAiClient.of({ |
| 276 | client: httpClient, |
| 277 | createResponse, |
| 278 | createResponseStream, |
| 279 | createEmbedding |
| 280 | }) |
| 281 | }, |
| 282 | withRedactedHeaders |
| 283 | ) |
| 284 | |
| 285 | /** |
| 286 | * Creates a layer that provides an OpenAI-compatible client from explicit options. |
| 287 | * |
| 288 | * **When to use** |
| 289 | * |
| 290 | * Use to install `OpenAiClient` in an application layer when the client options |
| 291 | * are already available as values rather than loaded from `Config`. |
| 292 | * |
| 293 | * @see {@link make} for constructing the client service effectfully |
| 294 | * @see {@link layerConfig} for loading client settings from `Config` |
| 295 | * |
| 296 | * @stability unstable |
| 297 | * @category layers |
| 298 | * @since 4.0.0 |
| 299 | */ |
| 300 | export const layer = (options: Options): Layer.Layer<OpenAiClient, never, HttpClient.HttpClient> => |
| 301 | Layer.effect(OpenAiClient, make(options)) |
| 302 | |
| 303 | /** |
| 304 | * Creates a layer that loads OpenAI-compatible client settings from `Config` |
| 305 | * values before constructing the service. |
| 306 | * |
| 307 | * **When to use** |
| 308 | * |
| 309 | * Use when you need client settings for OpenAI-compatible APIs to be read from |
| 310 | * Effect `Config` values while providing `OpenAiClient` as a layer. |
| 311 | * |
| 312 | * **Details** |
| 313 | * |
| 314 | * Only config values supplied in `options` are loaded. Omitted fields are |
| 315 | * passed to `make` as `undefined`, and `transformClient` is forwarded as a |
| 316 | * plain option. |
| 317 | * |
| 318 | * @see {@link make} for constructing the client service effectfully |
| 319 | * @see {@link layer} for providing the client from already-resolved options |
| 320 | * |
| 321 | * @stability unstable |
| 322 | * @category layers |
| 323 | * @since 4.0.0 |
| 324 | */ |
| 325 | export const layerConfig = (options?: { |
| 326 | readonly apiKey?: Config.Config<Redacted.Redacted<string> | undefined> | undefined |
| 327 | readonly apiUrl?: Config.Config<string> | undefined |
| 328 | readonly organizationId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined |
| 329 | readonly projectId?: Config.Config<Redacted.Redacted<string> | undefined> | undefined |
| 330 | readonly transformClient?: ((client: HttpClient.HttpClient) => HttpClient.HttpClient) | undefined |
| 331 | }): Layer.Layer<OpenAiClient, Config.ConfigError, HttpClient.HttpClient> => |
| 332 | Layer.effect( |
| 333 | OpenAiClient, |
| 334 | Effect.gen(function*() { |
| 335 | const apiKey = options?.apiKey !== undefined |
| 336 | ? yield* options.apiKey : |
| 337 | undefined |
| 338 | const apiUrl = options?.apiUrl !== undefined |
| 339 | ? yield* options.apiUrl : |
| 340 | undefined |
| 341 | const organizationId = options?.organizationId !== undefined |
| 342 | ? yield* options.organizationId |
| 343 | : undefined |
| 344 | const projectId = options?.projectId !== undefined |
| 345 | ? yield* options.projectId : |
| 346 | undefined |
| 347 | return yield* make({ |
| 348 | apiKey, |
| 349 | apiUrl, |
| 350 | organizationId, |
| 351 | projectId, |
| 352 | transformClient: options?.transformClient |
| 353 | }) |
| 354 | }) |
| 355 | ) |
| 356 | |
| 357 | type JsonObject = { readonly [x: string]: Schema.Json } |
| 358 | |
| 359 | /** |
| 360 | * Optional response fields that can be requested with the `include` parameter. |
| 361 | * |
| 362 | * @stability unstable |
| 363 | * @category models |
| 364 | * @since 4.0.0 |
| 365 | */ |
| 366 | export type IncludeEnum = |
| 367 | | "message.input_image.image_url" |
| 368 | | "reasoning.encrypted_content" |
| 369 | | "message.output_text.logprobs" |
| 370 | |
| 371 | /** |
| 372 | * Lifecycle status shared by message, reasoning, and tool-call items. |
| 373 | * |
| 374 | * @stability unstable |
| 375 | * @category models |
| 376 | * @since 4.0.0 |
| 377 | */ |
| 378 | export type MessageStatus = "in_progress" | "completed" | "incomplete" |
| 379 | |
| 380 | type InputTextContent = { |
| 381 | readonly type: "input_text" |
| 382 | readonly text: string |
| 383 | } |
| 384 | |
| 385 | type InputImageContent = { |
| 386 | readonly type: "input_image" |
| 387 | readonly image_url?: string | null | undefined |
| 388 | readonly file_id?: string | null | undefined |
| 389 | readonly detail?: "low" | "high" | "auto" | null | undefined |
| 390 | } |
| 391 | |
| 392 | type InputFileContent = { |
| 393 | readonly type: "input_file" |
| 394 | readonly file_id?: string | null | undefined |
| 395 | readonly filename?: string | undefined |
| 396 | readonly file_url?: string | undefined |
| 397 | readonly file_data?: string | undefined |
| 398 | } |
| 399 | |
| 400 | /** |
| 401 | * Content blocks accepted in input messages. |
| 402 | * |
| 403 | * @stability unstable |
| 404 | * @category models |
| 405 | * @since 4.0.0 |
| 406 | */ |
| 407 | export type InputContent = InputTextContent | InputImageContent | InputFileContent |
| 408 | |
| 409 | /** |
| 410 | * Text content block used for model-provided reasoning summaries. |
| 411 | * |
| 412 | * @stability unstable |
| 413 | * @category models |
| 414 | * @since 4.0.0 |
| 415 | */ |
| 416 | export type SummaryTextContent = { |
| 417 | readonly type: "summary_text" |
| 418 | readonly text: string |
| 419 | } |
| 420 | |
| 421 | type ReasoningTextContent = { |
| 422 | readonly type: "reasoning_text" |
| 423 | readonly text: string |
| 424 | } |
| 425 | |
| 426 | type RefusalContent = { |
| 427 | readonly type: "refusal" |
| 428 | readonly refusal: string |
| 429 | } |
| 430 | |
| 431 | type TextContent = { |
| 432 | readonly type: "text" |
| 433 | readonly text: string |
| 434 | } |
| 435 | |
| 436 | type ComputerScreenshotContent = { |
| 437 | readonly type: "computer_screenshot" |
| 438 | readonly image_url: string | null |
| 439 | readonly file_id: string | null |
| 440 | } |
| 441 | |
| 442 | type FileCitationAnnotation = { |
| 443 | readonly type: "file_citation" |
| 444 | readonly file_id: string |
| 445 | readonly index: number |
| 446 | readonly filename: string |
| 447 | } |
| 448 | |
| 449 | type UrlCitationAnnotation = { |
| 450 | readonly type: "url_citation" |
| 451 | readonly url: string |
| 452 | readonly start_index: number |
| 453 | readonly end_index: number |
| 454 | readonly title: string |
| 455 | } |
| 456 | |
| 457 | type ContainerFileCitationAnnotation = { |
| 458 | readonly type: "container_file_citation" |
| 459 | readonly container_id: string |
| 460 | readonly file_id: string |
| 461 | readonly start_index: number |
| 462 | readonly end_index: number |
| 463 | readonly filename: string |
| 464 | } |
| 465 | |
| 466 | type FilePathAnnotation = { |
| 467 | readonly type: "file_path" |
| 468 | readonly file_id: string |
| 469 | readonly index: number |
| 470 | } |
| 471 | |
| 472 | /** |
| 473 | * Citation and file-path annotations attached to output text content. |
| 474 | * |
| 475 | * @stability unstable |
| 476 | * @category models |
| 477 | * @since 4.0.0 |
| 478 | */ |
| 479 | export type Annotation = |
| 480 | | FileCitationAnnotation |
| 481 | | UrlCitationAnnotation |
| 482 | | ContainerFileCitationAnnotation |
| 483 | | FilePathAnnotation |
| 484 | |
| 485 | type OutputTextContent = { |
| 486 | readonly type: "output_text" |
| 487 | readonly text: string |
| 488 | readonly annotations?: ReadonlyArray<Annotation> | undefined |
| 489 | readonly logprobs?: ReadonlyArray<unknown> | undefined |
| 490 | } |
| 491 | |
| 492 | type OutputMessageContent = |
| 493 | | InputTextContent |
| 494 | | OutputTextContent |
| 495 | | TextContent |
| 496 | | SummaryTextContent |
| 497 | | ReasoningTextContent |
| 498 | | RefusalContent |
| 499 | | InputImageContent |
| 500 | | ComputerScreenshotContent |
| 501 | | InputFileContent |
| 502 | |
| 503 | type OutputMessage = { |
| 504 | readonly id: string |
| 505 | readonly type: "message" |
| 506 | readonly role: "assistant" |
| 507 | readonly content: ReadonlyArray<OutputMessageContent> |
| 508 | readonly status: MessageStatus |
| 509 | } |
| 510 | |
| 511 | /** |
| 512 | * Reasoning output item containing encrypted reasoning content, summaries, and |
| 513 | * optional reasoning text. |
| 514 | * |
| 515 | * @stability unstable |
| 516 | * @category models |
| 517 | * @since 4.0.0 |
| 518 | */ |
| 519 | export type ReasoningItem = { |
| 520 | readonly type: "reasoning" |
| 521 | readonly id: string |
| 522 | readonly encrypted_content?: string | null | undefined |
| 523 | readonly summary: ReadonlyArray<SummaryTextContent> |
| 524 | readonly content?: ReadonlyArray<ReasoningTextContent> | undefined |
| 525 | readonly status?: MessageStatus | undefined |
| 526 | } |
| 527 | |
| 528 | type FunctionCall = { |
| 529 | readonly id?: string | undefined |
| 530 | readonly type: "function_call" |
| 531 | readonly call_id: string |
| 532 | readonly name: string |
| 533 | readonly arguments: string |
| 534 | readonly status?: MessageStatus | undefined |
| 535 | } |
| 536 | |
| 537 | type FunctionCallOutput = { |
| 538 | readonly id?: string | null | undefined |
| 539 | readonly call_id: string |
| 540 | readonly type: "function_call_output" |
| 541 | readonly output: string | ReadonlyArray<InputTextContent | InputImageContent | InputFileContent> |
| 542 | readonly status?: MessageStatus | null | undefined |
| 543 | } |
| 544 | |
| 545 | type CustomToolCall = { |
| 546 | readonly type: "custom_tool_call" |
| 547 | readonly id?: string | undefined |
| 548 | readonly call_id: string |
| 549 | readonly name: string |
| 550 | readonly input: string |
| 551 | } |
| 552 | |
| 553 | type CustomToolCallOutput = { |
| 554 | readonly type: "custom_tool_call_output" |
| 555 | readonly id?: string | undefined |
| 556 | readonly call_id: string |
| 557 | readonly output: string | ReadonlyArray<InputTextContent | InputImageContent | InputFileContent> |
| 558 | } |
| 559 | |
| 560 | type ItemReference = { |
| 561 | readonly type?: "item_reference" | null | undefined |
| 562 | readonly id: string |
| 563 | } |
| 564 | |
| 565 | /** |
| 566 | * Item shapes accepted by a Responses-style `input` field. |
| 567 | * |
| 568 | * **Details** |
| 569 | * |
| 570 | * Supports input messages, output messages, tool calls, tool outputs, reasoning |
| 571 | * items, custom tool interactions, and item references. |
| 572 | * |
| 573 | * @stability unstable |
| 574 | * @category models |
| 575 | * @since 4.0.0 |
| 576 | */ |
| 577 | export type InputItem = |
| 578 | | { |
| 579 | readonly role: "user" | "assistant" | "system" | "developer" |
| 580 | readonly content: string | ReadonlyArray<InputContent> |
| 581 | readonly type?: "message" | undefined |
| 582 | } |
| 583 | | { |
| 584 | readonly type?: "message" | undefined |
| 585 | readonly role: "user" | "system" | "developer" |
| 586 | readonly status?: MessageStatus | undefined |
| 587 | readonly content: ReadonlyArray<InputContent> |
| 588 | } |
| 589 | | OutputMessage |
| 590 | | FunctionCall |
| 591 | | FunctionCallOutput |
| 592 | | ReasoningItem |
| 593 | | CustomToolCallOutput |
| 594 | | CustomToolCall |
| 595 | | ItemReference |
| 596 | |
| 597 | type FunctionTool = { |
| 598 | readonly type: "function" |
| 599 | readonly name: string |
| 600 | readonly description?: string | null | undefined |
| 601 | readonly parameters?: JsonObject | null | undefined |
| 602 | readonly strict?: boolean | null | undefined |
| 603 | } |
| 604 | |
| 605 | type CustomToolParam = { |
| 606 | readonly type: "custom" |
| 607 | readonly name: string |
| 608 | readonly description?: string | undefined |
| 609 | readonly format?: unknown |
| 610 | } |
| 611 | |
| 612 | /** |
| 613 | * Tool definitions that can be supplied to a Responses-style request. |
| 614 | * |
| 615 | * @stability unstable |
| 616 | * @category models |
| 617 | * @since 4.0.0 |
| 618 | */ |
| 619 | export type Tool = |
| 620 | | FunctionTool |
| 621 | | CustomToolParam |
| 622 | |
| 623 | type ToolChoice = |
| 624 | | "none" |
| 625 | | "auto" |
| 626 | | "required" |
| 627 | | { |
| 628 | readonly type: "allowed_tools" |
| 629 | readonly mode: "auto" | "required" |
| 630 | readonly tools: ReadonlyArray<JsonObject> |
| 631 | } |
| 632 | | { |
| 633 | readonly type: "function" |
| 634 | readonly name: string |
| 635 | } |
| 636 | | { |
| 637 | readonly type: "custom" |
| 638 | readonly name: string |
| 639 | } |
| 640 | |
| 641 | /** |
| 642 | * Text output format configuration for plain text, JSON object, or JSON Schema |
| 643 | * responses. |
| 644 | * |
| 645 | * @stability unstable |
| 646 | * @category configuration |
| 647 | * @since 4.0.0 |
| 648 | */ |
| 649 | export type TextResponseFormatConfiguration = |
| 650 | | { |
| 651 | readonly type: "text" |
| 652 | } |
| 653 | | { |
| 654 | readonly type: "json_schema" |
| 655 | readonly description?: string | undefined |
| 656 | readonly name: string |
| 657 | readonly schema: JsonObject |
| 658 | readonly strict?: boolean | null | undefined |
| 659 | } |
| 660 | | { |
| 661 | readonly type: "json_object" |
| 662 | } |
| 663 | |
| 664 | /** |
| 665 | * Request options for creating a Responses-style response with an |
| 666 | * OpenAI-compatible provider. |
| 667 | * |
| 668 | * @stability unstable |
| 669 | * @category models |
| 670 | * @since 4.0.0 |
| 671 | */ |
| 672 | export type CreateResponse = { |
| 673 | readonly metadata?: Readonly<Record<string, string>> | null | undefined |
| 674 | readonly top_logprobs?: number | undefined |
| 675 | readonly temperature?: number | null | undefined |
| 676 | readonly top_p?: number | null | undefined |
| 677 | readonly user?: string | null | undefined |
| 678 | readonly safety_identifier?: string | null | undefined |
| 679 | readonly prompt_cache_key?: string | null | undefined |
| 680 | readonly service_tier?: string | undefined |
| 681 | readonly prompt_cache_retention?: "in-memory" | "24h" | null | undefined |
| 682 | readonly previous_response_id?: string | null | undefined |
| 683 | readonly model?: string | undefined |
| 684 | readonly reasoning?: unknown |
| 685 | readonly background?: boolean | null | undefined |
| 686 | readonly max_output_tokens?: number | null | undefined |
| 687 | readonly max_tool_calls?: number | null | undefined |
| 688 | readonly text?: { |
| 689 | readonly format?: TextResponseFormatConfiguration | undefined |
| 690 | readonly verbosity?: "low" | "medium" | "high" | null | undefined |
| 691 | } | undefined |
| 692 | readonly tools?: ReadonlyArray<Tool> | undefined |
| 693 | readonly tool_choice?: ToolChoice | undefined |
| 694 | readonly truncation?: "auto" | "disabled" | null | undefined |
| 695 | readonly input?: string | ReadonlyArray<InputItem> | undefined |
| 696 | readonly include?: ReadonlyArray<IncludeEnum> | null | undefined |
| 697 | readonly parallel_tool_calls?: boolean | null | undefined |
| 698 | readonly store?: boolean | null | undefined |
| 699 | readonly instructions?: string | null | undefined |
| 700 | readonly stream?: boolean | null | undefined |
| 701 | readonly conversation?: string | null | undefined |
| 702 | readonly modalities?: ReadonlyArray<"text" | "audio"> | undefined |
| 703 | readonly seed?: number | undefined |
| 704 | } |
| 705 | |
| 706 | /** |
| 707 | * Token accounting reported on Responses-style response objects. |
| 708 | * |
| 709 | * @stability unstable |
| 710 | * @category models |
| 711 | * @since 4.0.0 |
| 712 | */ |
| 713 | export type ResponseUsage = { |
| 714 | readonly input_tokens: number |
| 715 | readonly output_tokens: number |
| 716 | readonly total_tokens: number |
| 717 | readonly input_tokens_details?: unknown |
| 718 | readonly output_tokens_details?: unknown |
| 719 | } |
| 720 | |
| 721 | type OutputItem = |
| 722 | | OutputMessage |
| 723 | | FunctionCall |
| 724 | | ReasoningItem |
| 725 | | CustomToolCall |
| 726 | |
| 727 | /** |
| 728 | * Responses-style response object returned by compatible providers or embedded |
| 729 | * in response stream lifecycle events. |
| 730 | * |
| 731 | * @stability unstable |
| 732 | * @category models |
| 733 | * @since 4.0.0 |
| 734 | */ |
| 735 | export type Response = { |
| 736 | readonly id: string |
| 737 | readonly object?: "response" | undefined |
| 738 | readonly model: string |
| 739 | readonly status?: "completed" | "failed" | "in_progress" | "cancelled" | "queued" | "incomplete" | undefined |
| 740 | readonly created_at: number |
| 741 | readonly output: ReadonlyArray<OutputItem> |
| 742 | readonly usage?: ResponseUsage | null | undefined |
| 743 | readonly incomplete_details?: |
| 744 | | { |
| 745 | readonly reason?: "max_output_tokens" | "content_filter" | undefined |
| 746 | } |
| 747 | | null |
| 748 | | undefined |
| 749 | readonly service_tier?: string | undefined |
| 750 | } |
| 751 | |
| 752 | type ResponseCreatedEvent = { |
| 753 | readonly type: "response.created" |
| 754 | readonly response: Response |
| 755 | readonly sequence_number: number |
| 756 | } |
| 757 | |
| 758 | type ResponseCompletedEvent = { |
| 759 | readonly type: "response.completed" |
| 760 | readonly response: Response |
| 761 | readonly sequence_number: number |
| 762 | } |
| 763 | |
| 764 | type ResponseIncompleteEvent = { |
| 765 | readonly type: "response.incomplete" |
| 766 | readonly response: Response |
| 767 | readonly sequence_number: number |
| 768 | } |
| 769 | |
| 770 | type ResponseFailedEvent = { |
| 771 | readonly type: "response.failed" |
| 772 | readonly response: Response |
| 773 | readonly sequence_number: number |
| 774 | } |
| 775 | |
| 776 | type ResponseOutputItemAddedEvent = { |
| 777 | readonly type: "response.output_item.added" |
| 778 | readonly output_index: number |
| 779 | readonly sequence_number: number |
| 780 | readonly item: OutputItem |
| 781 | } |
| 782 | |
| 783 | type ResponseOutputItemDoneEvent = { |
| 784 | readonly type: "response.output_item.done" |
| 785 | readonly output_index: number |
| 786 | readonly sequence_number: number |
| 787 | readonly item: OutputItem |
| 788 | } |
| 789 | |
| 790 | type ResponseTextDeltaEvent = { |
| 791 | readonly type: "response.output_text.delta" |
| 792 | readonly item_id: string |
| 793 | readonly output_index: number |
| 794 | readonly content_index: number |
| 795 | readonly delta: string |
| 796 | readonly sequence_number: number |
| 797 | readonly logprobs?: ReadonlyArray<unknown> | undefined |
| 798 | } |
| 799 | |
| 800 | type ResponseOutputTextAnnotationAddedEvent = { |
| 801 | readonly type: "response.output_text.annotation.added" |
| 802 | readonly item_id: string |
| 803 | readonly output_index: number |
| 804 | readonly content_index: number |
| 805 | readonly annotation_index: number |
| 806 | readonly sequence_number: number |
| 807 | readonly annotation: Annotation |
| 808 | } |
| 809 | |
| 810 | type ResponseFunctionCallArgumentsDeltaEvent = { |
| 811 | readonly type: "response.function_call_arguments.delta" |
| 812 | readonly item_id: string |
| 813 | readonly output_index: number |
| 814 | readonly sequence_number: number |
| 815 | readonly delta: string |
| 816 | } |
| 817 | |
| 818 | type ResponseReasoningSummaryPartAddedEvent = { |
| 819 | readonly type: "response.reasoning_summary_part.added" |
| 820 | readonly item_id: string |
| 821 | readonly output_index: number |
| 822 | readonly summary_index: number |
| 823 | readonly sequence_number: number |
| 824 | readonly part: SummaryTextContent |
| 825 | } |
| 826 | |
| 827 | type ResponseReasoningSummaryPartDoneEvent = { |
| 828 | readonly type: "response.reasoning_summary_part.done" |
| 829 | readonly item_id: string |
| 830 | readonly output_index: number |
| 831 | readonly summary_index: number |
| 832 | readonly sequence_number: number |
| 833 | readonly part: SummaryTextContent |
| 834 | } |
| 835 | |
| 836 | type ResponseReasoningSummaryTextDeltaEvent = { |
| 837 | readonly type: "response.reasoning_summary_text.delta" |
| 838 | readonly item_id: string |
| 839 | readonly output_index: number |
| 840 | readonly summary_index: number |
| 841 | readonly delta: string |
| 842 | readonly sequence_number: number |
| 843 | } |
| 844 | |
| 845 | type ResponseErrorEvent = { |
| 846 | readonly type: "error" |
| 847 | readonly code: string | null |
| 848 | readonly message: string |
| 849 | readonly param: string | null |
| 850 | readonly sequence_number: number |
| 851 | } |
| 852 | |
| 853 | type UnknownResponseStreamEvent = { |
| 854 | readonly type: string |
| 855 | readonly [key: string]: unknown |
| 856 | } |
| 857 | |
| 858 | /** |
| 859 | * Server-sent event shapes emitted by Responses-style response streams. |
| 860 | * |
| 861 | * @stability unstable |
| 862 | * @category streaming |
| 863 | * @since 4.0.0 |
| 864 | */ |
| 865 | export type ResponseStreamEvent = |
| 866 | | ResponseCreatedEvent |
| 867 | | ResponseCompletedEvent |
| 868 | | ResponseIncompleteEvent |
| 869 | | ResponseFailedEvent |
| 870 | | ResponseOutputItemAddedEvent |
| 871 | | ResponseOutputItemDoneEvent |
| 872 | | ResponseTextDeltaEvent |
| 873 | | ResponseOutputTextAnnotationAddedEvent |
| 874 | | ResponseFunctionCallArgumentsDeltaEvent |
| 875 | | ResponseReasoningSummaryPartAddedEvent |
| 876 | | ResponseReasoningSummaryPartDoneEvent |
| 877 | | ResponseReasoningSummaryTextDeltaEvent |
| 878 | | ResponseErrorEvent |
| 879 | | UnknownResponseStreamEvent |
| 880 | |
| 881 | /** |
| 882 | * Represents one embedding item returned by an OpenAI-compatible embeddings API. |
| 883 | * |
| 884 | * **Details** |
| 885 | * |
| 886 | * The embedding can be returned either as a numeric vector or as a base64-encoded |
| 887 | * string. The `index` field identifies the input item that produced this |
| 888 | * embedding. |
| 889 | * |
| 890 | * @stability unstable |
| 891 | * @category models |
| 892 | * @since 4.0.0 |
| 893 | */ |
| 894 | export type Embedding = { |
| 895 | readonly embedding: ReadonlyArray<number> | string |
| 896 | readonly index: number |
| 897 | readonly object?: string | undefined |
| 898 | } |
| 899 | |
| 900 | /** |
| 901 | * Request payload for the embeddings endpoint. |
| 902 | * |
| 903 | * @stability unstable |
| 904 | * @category models |
| 905 | * @since 4.0.0 |
| 906 | */ |
| 907 | export type CreateEmbeddingRequest = { |
| 908 | readonly input: string | ReadonlyArray<string> | ReadonlyArray<number> | ReadonlyArray<ReadonlyArray<number>> |
| 909 | readonly model: string |
| 910 | readonly encoding_format?: "float" | "base64" | undefined |
| 911 | readonly dimensions?: number | undefined |
| 912 | readonly user?: string | undefined |
| 913 | } |
| 914 | |
| 915 | /** |
| 916 | * Successful response payload returned by the embeddings endpoint. |
| 917 | * |
| 918 | * @stability unstable |
| 919 | * @category models |
| 920 | * @since 4.0.0 |
| 921 | */ |
| 922 | export type CreateEmbeddingResponse = { |
| 923 | readonly data: ReadonlyArray<Embedding> |
| 924 | readonly model: string |
| 925 | readonly object?: "list" | undefined |
| 926 | readonly usage?: { |
| 927 | readonly prompt_tokens: number |
| 928 | readonly total_tokens: number |
| 929 | } | undefined |
| 930 | } |
| 931 | |
| 932 | /** |
| 933 | * JSON request body accepted by the embeddings endpoint. |
| 934 | * |
| 935 | * @stability unstable |
| 936 | * @category models |
| 937 | * @since 4.0.0 |
| 938 | */ |
| 939 | export type CreateEmbeddingRequestJson = CreateEmbeddingRequest |
| 940 | /** |
| 941 | * Decoded successful embeddings response body. |
| 942 | * |
| 943 | * @stability unstable |
| 944 | * @category models |
| 945 | * @since 4.0.0 |
| 946 | */ |
| 947 | export type CreateEmbedding200 = CreateEmbeddingResponse |
| 948 | /** |
| 949 | * Structured content parts accepted in chat completion messages. |
| 950 | * |
| 951 | * @stability unstable |
| 952 | * @category models |
| 953 | * @since 4.0.0 |
| 954 | */ |
| 955 | export type ChatCompletionContentPart = |
| 956 | | { |
| 957 | readonly type: "text" |
| 958 | readonly text: string |
| 959 | } |
| 960 | | { |
| 961 | readonly type: "image_url" |
| 962 | readonly image_url: { |
| 963 | readonly url: string |
| 964 | readonly detail?: "low" | "high" | "auto" | undefined |
| 965 | } |
| 966 | } |
| 967 | /** |
| 968 | * Tool call data attached to an assistant chat completion message. |
| 969 | * |
| 970 | * @stability unstable |
| 971 | * @category models |
| 972 | * @since 4.0.0 |
| 973 | */ |
| 974 | export type ChatCompletionRequestToolCall = { |
| 975 | readonly id: string |
| 976 | readonly type: "function" |
| 977 | readonly function: { |
| 978 | readonly name: string |
| 979 | readonly arguments: string |
| 980 | } |
| 981 | } |
| 982 | /** |
| 983 | * Message shapes accepted by the chat completions endpoint. |
| 984 | * |
| 985 | * @stability unstable |
| 986 | * @category models |
| 987 | * @since 4.0.0 |
| 988 | */ |
| 989 | export type ChatCompletionRequestMessage = |
| 990 | | { |
| 991 | readonly role: "system" | "developer" | "user" | "assistant" |
| 992 | readonly content: string | ReadonlyArray<ChatCompletionContentPart> | null |
| 993 | readonly tool_calls?: ReadonlyArray<ChatCompletionRequestToolCall> | undefined |
| 994 | } |
| 995 | | { |
| 996 | readonly role: "tool" |
| 997 | readonly tool_call_id: string |
| 998 | readonly content: string |
| 999 | } |
| 1000 | /** |
| 1001 | * Function tool definition accepted by the chat completions endpoint. |
| 1002 | * |
| 1003 | * @stability unstable |
| 1004 | * @category models |
| 1005 | * @since 4.0.0 |
| 1006 | */ |
| 1007 | export type ChatCompletionTool = { |
| 1008 | readonly type: "function" |
| 1009 | readonly function: { |
| 1010 | readonly name: string |
| 1011 | readonly description?: string | null | undefined |
| 1012 | readonly parameters?: JsonObject | undefined |
| 1013 | readonly strict?: boolean | undefined |
| 1014 | } |
| 1015 | } |
| 1016 | /** |
| 1017 | * Controls whether the model may call tools and can force a specific function. |
| 1018 | * |
| 1019 | * @stability unstable |
| 1020 | * @category configuration |
| 1021 | * @since 4.0.0 |
| 1022 | */ |
| 1023 | export type ChatCompletionToolChoice = |
| 1024 | | "none" |
| 1025 | | "auto" |
| 1026 | | "required" |
| 1027 | | { |
| 1028 | readonly type: "function" |
| 1029 | readonly function: { |
| 1030 | readonly name: string |
| 1031 | } |
| 1032 | } |
| 1033 | /** |
| 1034 | * JSON response format configuration for chat completion requests. |
| 1035 | * |
| 1036 | * @stability unstable |
| 1037 | * @category configuration |
| 1038 | * @since 4.0.0 |
| 1039 | */ |
| 1040 | export type ChatCompletionResponseFormat = |
| 1041 | | { |
| 1042 | readonly type: "json_object" |
| 1043 | } |
| 1044 | | { |
| 1045 | readonly type: "json_schema" |
| 1046 | readonly json_schema: { |
| 1047 | readonly name: string |
| 1048 | readonly schema: JsonObject |
| 1049 | readonly description?: string | undefined |
| 1050 | readonly strict?: boolean | undefined |
| 1051 | } |
| 1052 | } |
| 1053 | /** |
| 1054 | * Request payload for the OpenAI-compatible chat completions endpoint. |
| 1055 | * |
| 1056 | * @stability unstable |
| 1057 | * @category models |
| 1058 | * @since 4.0.0 |
| 1059 | */ |
| 1060 | export type ChatCompletionRequest = { |
| 1061 | readonly model: string |
| 1062 | readonly messages: ReadonlyArray<ChatCompletionRequestMessage> |
| 1063 | readonly temperature?: number | null | undefined |
| 1064 | readonly top_p?: number | null | undefined |
| 1065 | readonly max_tokens?: number | null | undefined |
| 1066 | readonly user?: string | null | undefined |
| 1067 | readonly seed?: number | undefined |
| 1068 | readonly parallel_tool_calls?: boolean | null | undefined |
| 1069 | readonly response_format?: ChatCompletionResponseFormat | undefined |
| 1070 | readonly tools?: ReadonlyArray<ChatCompletionTool> | undefined |
| 1071 | readonly tool_choice?: ChatCompletionToolChoice | undefined |
| 1072 | readonly service_tier?: string | undefined |
| 1073 | readonly reasoning?: unknown |
| 1074 | readonly stream?: boolean | undefined |
| 1075 | readonly stream_options?: { |
| 1076 | readonly include_usage?: boolean | undefined |
| 1077 | } | undefined |
| 1078 | readonly [x: string]: unknown |
| 1079 | } |
| 1080 | /** |
| 1081 | * JSON request body used by this client when creating a chat completion response. |
| 1082 | * |
| 1083 | * @stability unstable |
| 1084 | * @category models |
| 1085 | * @since 4.0.0 |
| 1086 | */ |
| 1087 | export type CreateResponseRequestJson = ChatCompletionRequest |
| 1088 | /** |
| 1089 | * Decoded successful chat completion response body returned by `createResponse`. |
| 1090 | * |
| 1091 | * @stability unstable |
| 1092 | * @category models |
| 1093 | * @since 4.0.0 |
| 1094 | */ |
| 1095 | export type CreateResponse200 = ChatCompletionResponse |
| 1096 | /** |
| 1097 | * Decoded server-sent event payload emitted by `createResponseStream`. |
| 1098 | * |
| 1099 | * @stability unstable |
| 1100 | * @category streaming |
| 1101 | * @since 4.0.0 |
| 1102 | */ |
| 1103 | export type CreateResponse200Sse = ChatCompletionStreamEvent |
| 1104 | |
| 1105 | const EmbeddingSchema = Schema.Struct({ |
| 1106 | embedding: Schema.Union([Schema.Array(Schema.Finite), Schema.String]), |
| 1107 | index: Schema.Int, |
| 1108 | object: Schema.optionalKey(Schema.String) |
| 1109 | }) |
| 1110 | |
| 1111 | const CreateEmbeddingResponseSchema = Schema.Struct({ |
| 1112 | data: Schema.Array(EmbeddingSchema), |
| 1113 | model: Schema.String, |
| 1114 | object: Schema.optionalKey(Schema.Literal("list")), |
| 1115 | usage: Schema.optionalKey(Schema.Struct({ |
| 1116 | prompt_tokens: Schema.Int, |
| 1117 | total_tokens: Schema.Int |
| 1118 | })) |
| 1119 | }) |
| 1120 | |
| 1121 | const ChatCompletionToolFunction = Schema.Struct({ |
| 1122 | name: Schema.String, |
| 1123 | arguments: Schema.optionalKey(Schema.String) |
| 1124 | }) |
| 1125 | |
| 1126 | const ChatCompletionToolFunctionDelta = Schema.Struct({ |
| 1127 | // Some OpenAI-compatible providers (e.g. Fireworks) send `name: null` on |
| 1128 | // streamed tool-call continuation fragments. `name` must be nullable, else |
| 1129 | // the whole chunk fails validation and its argument delta is dropped. |
| 1130 | name: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1131 | arguments: Schema.optionalKey(Schema.String) |
| 1132 | }) |
| 1133 | |
| 1134 | const ChatCompletionToolCall = Schema.Struct({ |
| 1135 | id: Schema.optionalKey(Schema.String), |
| 1136 | index: Schema.optionalKey(Schema.Int), |
| 1137 | type: Schema.optionalKey(Schema.String), |
| 1138 | function: Schema.optionalKey(ChatCompletionToolFunction) |
| 1139 | }) |
| 1140 | |
| 1141 | const ChatCompletionToolCallDelta = Schema.Struct({ |
| 1142 | // Some providers send `id: null` on tool-call continuation fragments. |
| 1143 | id: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1144 | index: Schema.optionalKey(Schema.Int), |
| 1145 | type: Schema.optionalKey(Schema.String), |
| 1146 | function: Schema.optionalKey(ChatCompletionToolFunctionDelta) |
| 1147 | }) |
| 1148 | |
| 1149 | const ChatCompletionMessage = Schema.Struct({ |
| 1150 | role: Schema.optionalKey(Schema.String), |
| 1151 | content: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1152 | reasoning: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1153 | reasoning_content: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1154 | tool_calls: Schema.optionalKey(Schema.Array(ChatCompletionToolCall)) |
| 1155 | }) |
| 1156 | |
| 1157 | const ChatCompletionDelta = Schema.Struct({ |
| 1158 | // Some providers send `role: null` on streamed text deltas. |
| 1159 | role: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1160 | content: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1161 | reasoning: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1162 | reasoning_content: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1163 | // Some OpenAI-compatible providers send `tool_calls: null` when a streamed |
| 1164 | // chunk contains only text. Accepting null keeps the text-bearing chunk from |
| 1165 | // being classified as an unknown event. |
| 1166 | tool_calls: Schema.optionalKey(Schema.NullOr(Schema.Array(ChatCompletionToolCallDelta))) |
| 1167 | }) |
| 1168 | |
| 1169 | const ChatCompletionChoice = Schema.Struct({ |
| 1170 | index: Schema.Int, |
| 1171 | finish_reason: Schema.optionalKey(Schema.NullOr(Schema.String)), |
| 1172 | message: Schema.optionalKey(ChatCompletionMessage), |
| 1173 | delta: Schema.optionalKey(ChatCompletionDelta) |
| 1174 | }) |
| 1175 | |
| 1176 | const ChatCompletionUsage = Schema.Struct({ |
| 1177 | prompt_tokens: Schema.Int, |
| 1178 | completion_tokens: Schema.Int, |
| 1179 | total_tokens: Schema.Int, |
| 1180 | prompt_tokens_details: Schema.optionalKey(Schema.Any), |
| 1181 | completion_tokens_details: Schema.optionalKey(Schema.Any) |
| 1182 | }) |
| 1183 | |
| 1184 | const ChatCompletionResponse = Schema.Struct({ |
| 1185 | id: Schema.String, |
| 1186 | model: Schema.String, |
| 1187 | created: Schema.Int, |
| 1188 | choices: Schema.Array(ChatCompletionChoice), |
| 1189 | usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), |
| 1190 | service_tier: Schema.optionalKey(Schema.String) |
| 1191 | }) |
| 1192 | |
| 1193 | const ChatCompletionChunk = Schema.Struct({ |
| 1194 | id: Schema.String, |
| 1195 | model: Schema.String, |
| 1196 | created: Schema.Int, |
| 1197 | choices: Schema.Array(ChatCompletionChoice), |
| 1198 | usage: Schema.optionalKey(Schema.NullOr(ChatCompletionUsage)), |
| 1199 | service_tier: Schema.optionalKey(Schema.String) |
| 1200 | }) |
| 1201 | |
| 1202 | /** |
| 1203 | * Decoded tool-call object from a chat completion response or streaming chunk. |
| 1204 | * |
| 1205 | * @stability unstable |
| 1206 | * @category models |
| 1207 | * @since 4.0.0 |
| 1208 | */ |
| 1209 | export type ChatCompletionToolCall = typeof ChatCompletionToolCall.Type |
| 1210 | /** |
| 1211 | * Decoded message object from a non-streaming chat completion choice. |
| 1212 | * |
| 1213 | * @stability unstable |
| 1214 | * @category models |
| 1215 | * @since 4.0.0 |
| 1216 | */ |
| 1217 | export type ChatCompletionMessage = typeof ChatCompletionMessage.Type |
| 1218 | /** |
| 1219 | * Decoded choice object returned by chat completion responses and chunks. |
| 1220 | * |
| 1221 | * @stability unstable |
| 1222 | * @category models |
| 1223 | * @since 4.0.0 |
| 1224 | */ |
| 1225 | export type ChatCompletionChoice = typeof ChatCompletionChoice.Type |
| 1226 | /** |
| 1227 | * Decoded token usage summary returned by chat completions. |
| 1228 | * |
| 1229 | * @stability unstable |
| 1230 | * @category models |
| 1231 | * @since 4.0.0 |
| 1232 | */ |
| 1233 | export type ChatCompletionUsage = typeof ChatCompletionUsage.Type |
| 1234 | /** |
| 1235 | * Decoded successful response from the chat completions endpoint. |
| 1236 | * |
| 1237 | * @stability unstable |
| 1238 | * @category models |
| 1239 | * @since 4.0.0 |
| 1240 | */ |
| 1241 | export type ChatCompletionResponse = typeof ChatCompletionResponse.Type |
| 1242 | /** |
| 1243 | * Decoded streaming chunk emitted by the chat completions endpoint. |
| 1244 | * |
| 1245 | * @stability unstable |
| 1246 | * @category streaming |
| 1247 | * @since 4.0.0 |
| 1248 | */ |
| 1249 | export type ChatCompletionChunk = typeof ChatCompletionChunk.Type |
| 1250 | /** |
| 1251 | * A parsed chat completion event that does not match the expected chunk schema. |
| 1252 | * |
| 1253 | * @stability unstable |
| 1254 | * @category streaming |
| 1255 | * @since 4.0.0 |
| 1256 | */ |
| 1257 | export interface UnknownChatCompletionEvent { |
| 1258 | readonly _tag: "UnknownChatCompletionEvent" |
| 1259 | readonly data: unknown |
| 1260 | } |
| 1261 | /** |
| 1262 | * Streaming chat completion event, including decoded chunks, unknown parsed |
| 1263 | * events, and the `[DONE]` sentinel. |
| 1264 | * |
| 1265 | * @stability unstable |
| 1266 | * @category streaming |
| 1267 | * @since 4.0.0 |
| 1268 | */ |
| 1269 | export type ChatCompletionStreamEvent = ChatCompletionChunk | UnknownChatCompletionEvent | "[DONE]" |
| 1270 | |
| 1271 | const parseJson = (value: string): unknown => { |
| 1272 | try { |
| 1273 | return JSON.parse(value) |
| 1274 | } catch { |
| 1275 | return undefined |
| 1276 | } |
| 1277 | } |
| 1278 | |
| 1279 | const isChatCompletionChunk = Schema.is(ChatCompletionChunk) |
| 1280 | |
| 1281 | const decodeChatCompletionSseData = ( |
| 1282 | data: string |
| 1283 | ): ChatCompletionStreamEvent | undefined => { |
| 1284 | if (data === "[DONE]") { |
| 1285 | return data |
| 1286 | } |
| 1287 | const parsed = parseJson(data) |
| 1288 | if (parsed === undefined) { |
| 1289 | return undefined |
| 1290 | } |
| 1291 | return isChatCompletionChunk(parsed) ? parsed : { |
| 1292 | _tag: "UnknownChatCompletionEvent", |
| 1293 | data: parsed |
| 1294 | } |
| 1295 | } |