@demicodes/provider-openai-api 0.10.3 → 0.11.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 +13 -1
- package/dist/index.mjs +56 -25
- package/package.json +4 -4
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
|
|
507
|
+
function inferenceItemsToOpenAIMessages(systemPrompt, items, passBackReasoningContent) {
|
|
502
508
|
const messages = [];
|
|
503
509
|
let assistant = null;
|
|
504
|
-
let
|
|
510
|
+
let pendingReasoningContent = "";
|
|
505
511
|
const flushAssistant = () => {
|
|
506
|
-
|
|
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.
|
|
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
|
-
|
|
539
|
+
reasoningContent: pendingReasoningContent
|
|
534
540
|
};
|
|
535
|
-
|
|
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
|
-
|
|
548
|
+
reasoningContent: pendingReasoningContent
|
|
543
549
|
};
|
|
544
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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.
|
|
4
|
+
"version": "0.11.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.
|
|
15
|
-
"@demicodes/provider": "^0.
|
|
16
|
-
"@demicodes/utils": "^0.
|
|
14
|
+
"@demicodes/core": "^0.11.0",
|
|
15
|
+
"@demicodes/provider": "^0.11.0",
|
|
16
|
+
"@demicodes/utils": "^0.11.0"
|
|
17
17
|
},
|
|
18
18
|
"license": "Apache-2.0",
|
|
19
19
|
"main": "./dist/index.mjs",
|