@demicodes/provider-openai-api 0.10.4 → 0.12.0

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/dist/index.d.mts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { Provider, ProviderModel, ProviderModelList } from "@demicodes/provider";
2
-
3
2
  //#region src/models.d.ts
4
3
  interface OpenAIApiModelOptions {
5
4
  id: string;
@@ -26,7 +25,20 @@ interface OpenAIApiRequestOptions {
26
25
  maxRetries?: number;
27
26
  streamOptions?: Record<string, unknown> | null;
28
27
  extraBody?: Record<string, unknown>;
28
+ /**
29
+ * Chat Completions only: replay model thinking as `reasoning_content` on
30
+ * assistant messages. DeepSeek thinking mode requires the field on tool-call
31
+ * continuations, including an empty string when a tool round had no thinking.
32
+ * OpenAI rejects this compatible extension, so it is opt-in.
33
+ */
29
34
  passBackReasoningContent?: boolean;
35
+ /**
36
+ * Emit `status: 'completed'` on replayed assistant messages. Gateways that validate
37
+ * input against the full Responses item schema require it — Volcengine Ark rejects the
38
+ * item with `missing input.status` — while relay bridges reject it as an unknown
39
+ * parameter, so it stays off unless the provider opts in.
40
+ */
41
+ replayAssistantStatus?: boolean;
30
42
  }
31
43
  type OpenAIApiWireApi = 'responses' | 'chat-completions';
32
44
  interface OpenAIApiProviderOptions {
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import { isAbortError, isRecord, normalizeBaseUrl, numberOrZero, parseJsonObject
2
2
  import { Buffer } from "node:buffer";
3
3
  import process from "node:process";
4
4
  import { zeroUsage } from "@demicodes/core";
5
- import { authStatusFromKey, clampPromptCacheKey, defineProvider, httpRequestFailedEvent, normalizeErrorCode, providerErrorFromUnknown, withProviderId } from "@demicodes/provider";
5
+ import { authStatusFromKey, clampPromptCacheKey, defineProvider, httpRequestFailedEvent, normalizeErrorCode, providerErrorFromUnknown, toolResultContentToText, withProviderId } from "@demicodes/provider";
6
6
  //#region src/models.ts
7
7
  const SOURCE_FETCHED_AT = "1970-01-01T00:00:00.000Z";
8
8
  function openAIApiDefaultModels(providerId = "openai") {
@@ -122,11 +122,14 @@ function positiveInteger(value, field) {
122
122
  //#endregion
123
123
  //#region src/provider.ts
124
124
  const DEFAULT_OPENAI_API_BASE_URL = "https://api.openai.com/v1";
125
- var OpenAIChatCompletionsProvider = class {
125
+ var OpenAIChatCompletionsProvider = class OpenAIChatCompletionsProvider {
126
126
  options;
127
127
  constructor(options) {
128
128
  this.options = options;
129
129
  }
130
+ clone() {
131
+ return new OpenAIChatCompletionsProvider(this.options);
132
+ }
130
133
  async *run(request) {
131
134
  if (request.cancel.aborted) {
132
135
  yield { type: "abort" };
@@ -177,11 +180,14 @@ var OpenAIChatCompletionsProvider = class {
177
180
  return headers;
178
181
  }
179
182
  };
180
- var OpenAIResponsesProvider = class {
183
+ var OpenAIResponsesProvider = class OpenAIResponsesProvider {
181
184
  options;
182
185
  constructor(options) {
183
186
  this.options = options;
184
187
  }
188
+ clone() {
189
+ return new OpenAIResponsesProvider(this.options);
190
+ }
185
191
  async *run(request) {
186
192
  if (request.cancel.aborted) {
187
193
  yield { type: "abort" };
@@ -266,7 +272,7 @@ function createOpenAIApiProvider(options = {}) {
266
272
  function buildOpenAIResponsesBody(request, options) {
267
273
  const body = {
268
274
  model: request.modelId,
269
- input: request.items.flatMap((item, index) => inferenceItemToOpenAIResponseInput(item, index)),
275
+ input: request.items.flatMap((item, index) => inferenceItemToOpenAIResponseInput(item, index, options)),
270
276
  stream: true,
271
277
  store: false,
272
278
  include: ["reasoning.encrypted_content"],
@@ -498,18 +504,18 @@ async function* mapOpenAIChatCompletionStream(events, signal) {
498
504
  usage
499
505
  };
500
506
  }
501
- function inferenceItemsToOpenAIMessages(systemPrompt, items, passBackReasoningContent = false) {
507
+ function inferenceItemsToOpenAIMessages(systemPrompt, items, passBackReasoningContent) {
502
508
  const messages = [];
503
509
  let assistant = null;
504
- let pendingThinking = "";
510
+ let pendingReasoningContent = "";
505
511
  const flushAssistant = () => {
506
- pendingThinking = "";
512
+ pendingReasoningContent = "";
507
513
  if (!assistant) return;
508
514
  messages.push({
509
515
  role: "assistant",
510
516
  content: assistant.content || null,
511
517
  ...assistant.toolCalls.length > 0 ? { tool_calls: assistant.toolCalls } : {},
512
- ...passBackReasoningContent && (assistant.thinking || assistant.toolCalls.length > 0) ? { reasoning_content: assistant.thinking } : {}
518
+ ...passBackReasoningContent && (assistant.reasoningContent || assistant.toolCalls.length > 0) ? { reasoning_content: assistant.reasoningContent } : {}
513
519
  });
514
520
  assistant = null;
515
521
  };
@@ -530,18 +536,18 @@ function inferenceItemsToOpenAIMessages(systemPrompt, items, passBackReasoningCo
530
536
  assistant ??= {
531
537
  content: "",
532
538
  toolCalls: [],
533
- thinking: pendingThinking
539
+ reasoningContent: pendingReasoningContent
534
540
  };
535
- pendingThinking = "";
541
+ pendingReasoningContent = "";
536
542
  assistant.content += item.text;
537
543
  break;
538
544
  case "tool_use":
539
545
  assistant ??= {
540
546
  content: "",
541
547
  toolCalls: [],
542
- thinking: pendingThinking
548
+ reasoningContent: pendingReasoningContent
543
549
  };
544
- pendingThinking = "";
550
+ pendingReasoningContent = "";
545
551
  assistant.toolCalls.push({
546
552
  id: item.toolUseId,
547
553
  type: "function",
@@ -551,19 +557,31 @@ function inferenceItemsToOpenAIMessages(systemPrompt, items, passBackReasoningCo
551
557
  }
552
558
  });
553
559
  break;
554
- case "tool_result":
560
+ case "tool_result": {
555
561
  flushAssistant();
556
562
  messages.push({
557
563
  role: "tool",
558
564
  tool_call_id: item.toolUseId,
559
565
  content: toolResultContentToText(item.output)
560
566
  });
567
+ const media = item.output.filter((block) => block.type !== "text");
568
+ if (media.length > 0) messages.push({
569
+ role: "user",
570
+ content: [{
571
+ type: "text",
572
+ text: `[media returned by tool call ${item.toolUseId}]`
573
+ }, ...media.map((block) => ({
574
+ type: "image_url",
575
+ image_url: {
576
+ url: `data:${block.source.mediaType};base64,${block.source.data}`,
577
+ detail: "auto"
578
+ }
579
+ }))]
580
+ });
561
581
  break;
562
- case "assistant_thinking":
563
- if (passBackReasoningContent) if (assistant) assistant.thinking += item.text;
564
- else pendingThinking += item.text;
565
- break;
566
- case "assistant_redacted_thinking": break;
582
+ }
583
+ case "assistant_thinking": if (passBackReasoningContent) if (assistant) assistant.reasoningContent += item.text;
584
+ else pendingReasoningContent += item.text;
567
585
  }
568
586
  flushAssistant();
569
587
  return messages;
@@ -599,9 +617,6 @@ function userContentToOpenAI(content) {
599
617
  if (parts.every((part) => part.type === "text")) return parts.map((part) => part.text).join("\n");
600
618
  return parts;
601
619
  }
602
- function toolResultContentToText(output) {
603
- return output.map((block) => block.type === "text" ? block.text : `[image:${block.source.mediaType}]`).join("\n");
604
- }
605
620
  function toolToOpenAITool(tool) {
606
621
  return {
607
622
  type: "function",
@@ -643,7 +658,7 @@ function* flushOpenAIToolCalls(toolCalls) {
643
658
  }
644
659
  toolCalls.clear();
645
660
  }
646
- function inferenceItemToOpenAIResponseInput(item, index) {
661
+ function inferenceItemToOpenAIResponseInput(item, index, options) {
647
662
  switch (item.type) {
648
663
  case "user_message":
649
664
  case "user_steer": return [{
@@ -653,6 +668,7 @@ function inferenceItemToOpenAIResponseInput(item, index) {
653
668
  case "assistant_text": return [{
654
669
  type: "message",
655
670
  role: "assistant",
671
+ ...options?.replayAssistantStatus ? { status: "completed" } : {},
656
672
  content: [{
657
673
  type: "output_text",
658
674
  text: item.text,
@@ -676,11 +692,24 @@ function inferenceItemToOpenAIResponseInput(item, index) {
676
692
  }
677
693
  case "tool_result": {
678
694
  const { callId } = splitOpenAIResponseToolUseId(item.toolUseId);
679
- return [{
695
+ const items = [{
680
696
  type: "function_call_output",
681
697
  call_id: callId,
682
698
  output: toolResultContentToText(item.output)
683
699
  }];
700
+ const images = item.output.filter((block) => block.type !== "text");
701
+ if (images.length > 0) items.push({
702
+ role: "user",
703
+ content: [{
704
+ type: "input_text",
705
+ text: `[media returned by tool call ${callId}]`
706
+ }, ...images.map((block) => ({
707
+ type: "input_image",
708
+ image_url: `data:${block.source.mediaType};base64,${block.source.data}`,
709
+ detail: "auto"
710
+ }))]
711
+ });
712
+ return items;
684
713
  }
685
714
  }
686
715
  }
@@ -726,9 +755,10 @@ function thinkingToOpenAIReasoning(thinking) {
726
755
  summary: "auto"
727
756
  };
728
757
  if (thinking.effort === "none") return { effort: "none" };
758
+ if (thinking.summary === "off") return { effort: thinking.effort };
729
759
  return {
730
760
  effort: thinking.effort,
731
- summary: thinking.summary && thinking.summary !== "off" ? thinking.summary : "auto"
761
+ summary: thinking.summary ?? "auto"
732
762
  };
733
763
  }
734
764
  function splitOpenAIResponseToolUseId(toolUseId) {
@@ -770,10 +800,11 @@ function openAIResponsesUsage(response) {
770
800
  function openAIResponseErrorEvent(response) {
771
801
  const error = isRecord(response) && isRecord(response.error) ? response.error : null;
772
802
  const message = stringOrNull(error?.message) ?? "OpenAI response failed";
803
+ const rawCode = stringOrNull(error?.code) ?? stringOrNull(error?.type);
773
804
  return {
774
805
  type: "error",
775
806
  message,
776
- code: normalizeErrorCode(stringOrNull(error?.code) ?? stringOrNull(error?.type), message)
807
+ code: normalizeErrorCode(rawCode, message)
777
808
  };
778
809
  }
779
810
  function openAIIncompleteReason(response) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@demicodes/provider-openai-api",
3
3
  "description": "OpenAI API provider adapter for Demi.",
4
- "version": "0.10.4",
4
+ "version": "0.12.0",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -11,9 +11,9 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@demicodes/core": "^0.10.3",
15
- "@demicodes/provider": "^0.10.3",
16
- "@demicodes/utils": "^0.10.3"
14
+ "@demicodes/core": "^0.12.0",
15
+ "@demicodes/provider": "^0.12.0",
16
+ "@demicodes/utils": "^0.12.0"
17
17
  },
18
18
  "license": "Apache-2.0",
19
19
  "main": "./dist/index.mjs",