@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk 0.1.21 → 0.1.23
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.ts +13 -1
- package/dist/index.js +1123 -39
- package/openclaw.plugin.json +6 -12
- package/package.json +5 -5
- package/dist/codex-input-builder.d.ts +0 -5
- package/dist/codex-input-builder.js +0 -82
- package/dist/codex-model-provider.d.ts +0 -14
- package/dist/codex-model-provider.js +0 -46
- package/dist/codex-openai-responses-bridge-request.d.ts +0 -11
- package/dist/codex-openai-responses-bridge-request.js +0 -334
- package/dist/codex-openai-responses-bridge-shared.d.ts +0 -47
- package/dist/codex-openai-responses-bridge-shared.js +0 -50
- package/dist/codex-openai-responses-bridge-stream.d.ts +0 -22
- package/dist/codex-openai-responses-bridge-stream.js +0 -312
- package/dist/codex-openai-responses-bridge.d.ts +0 -5
- package/dist/codex-openai-responses-bridge.js +0 -140
- package/dist/codex-responses-capability.d.ts +0 -12
- package/dist/codex-responses-capability.js +0 -109
- package/dist/codex-session-type.d.ts +0 -20
- package/dist/codex-session-type.js +0 -57
package/openclaw.plugin.json
CHANGED
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
"workingDirectory": {
|
|
24
24
|
"type": "string"
|
|
25
25
|
},
|
|
26
|
-
"
|
|
26
|
+
"accessMode": {
|
|
27
27
|
"type": "string",
|
|
28
|
-
"enum": ["read-only", "workspace-write", "
|
|
28
|
+
"enum": ["read-only", "workspace-write", "full-access"],
|
|
29
|
+
"default": "full-access"
|
|
29
30
|
},
|
|
30
31
|
"skipGitRepoCheck": {
|
|
31
32
|
"type": "boolean"
|
|
@@ -40,10 +41,6 @@
|
|
|
40
41
|
"webSearchEnabled": {
|
|
41
42
|
"type": "boolean"
|
|
42
43
|
},
|
|
43
|
-
"approvalPolicy": {
|
|
44
|
-
"type": "string",
|
|
45
|
-
"enum": ["never", "on-request", "on-failure", "untrusted"]
|
|
46
|
-
},
|
|
47
44
|
"codexPathOverride": {
|
|
48
45
|
"type": "string"
|
|
49
46
|
},
|
|
@@ -86,12 +83,9 @@
|
|
|
86
83
|
"label": "Working Directory",
|
|
87
84
|
"advanced": true
|
|
88
85
|
},
|
|
89
|
-
"
|
|
90
|
-
"label": "
|
|
91
|
-
"
|
|
92
|
-
},
|
|
93
|
-
"approvalPolicy": {
|
|
94
|
-
"label": "Approval Policy",
|
|
86
|
+
"accessMode": {
|
|
87
|
+
"label": "Access Mode",
|
|
88
|
+
"help": "Product-facing permission model. Defaults to full-access; internally this is mapped to the Codex SDK sandbox/approval settings.",
|
|
95
89
|
"advanced": true
|
|
96
90
|
},
|
|
97
91
|
"networkAccessEnabled": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "NextClaw plugin that registers Codex SDK as an optional NCP runtime.",
|
|
6
6
|
"type": "module",
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
]
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@nextclaw/core": "0.11.
|
|
25
|
-
"@nextclaw/
|
|
26
|
-
"@nextclaw/ncp": "0.3
|
|
27
|
-
"@nextclaw/ncp-
|
|
24
|
+
"@nextclaw/core": "0.11.2",
|
|
25
|
+
"@nextclaw/ncp": "0.3.3",
|
|
26
|
+
"@nextclaw/ncp-toolkit": "0.4.3",
|
|
27
|
+
"@nextclaw/nextclaw-ncp-runtime-codex-sdk": "0.1.4"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@types/node": "^20.17.6",
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
buildRequestedSkillsUserPrompt,
|
|
3
|
-
SkillsLoader
|
|
4
|
-
} from "@nextclaw/core";
|
|
5
|
-
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { tmpdir } from "node:os";
|
|
7
|
-
import { join } from "node:path";
|
|
8
|
-
function readString(value) {
|
|
9
|
-
if (typeof value !== "string") {
|
|
10
|
-
return void 0;
|
|
11
|
-
}
|
|
12
|
-
const trimmed = value.trim();
|
|
13
|
-
return trimmed || void 0;
|
|
14
|
-
}
|
|
15
|
-
function readRequestedSkills(metadata) {
|
|
16
|
-
const raw = metadata.requested_skills ?? metadata.requestedSkills;
|
|
17
|
-
if (!Array.isArray(raw)) {
|
|
18
|
-
return [];
|
|
19
|
-
}
|
|
20
|
-
return raw.map((entry) => readString(entry)).filter((entry) => Boolean(entry)).slice(0, 8);
|
|
21
|
-
}
|
|
22
|
-
function readUserText(input) {
|
|
23
|
-
const message = readLatestUserMessage(input);
|
|
24
|
-
if (!message) {
|
|
25
|
-
return "";
|
|
26
|
-
}
|
|
27
|
-
return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
|
|
28
|
-
}
|
|
29
|
-
function readLatestUserMessage(input) {
|
|
30
|
-
for (let index = input.messages.length - 1; index >= 0; index -= 1) {
|
|
31
|
-
const message = input.messages[index];
|
|
32
|
-
if (message?.role !== "user") {
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
return message;
|
|
36
|
-
}
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
function sanitizeFileName(name) {
|
|
40
|
-
const sanitized = name.trim().replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
41
|
-
return sanitized || "attachment";
|
|
42
|
-
}
|
|
43
|
-
function buildAttachmentPrompt(message) {
|
|
44
|
-
if (!message) {
|
|
45
|
-
return "";
|
|
46
|
-
}
|
|
47
|
-
const fileParts = message.parts.filter((part) => part.type === "file");
|
|
48
|
-
if (fileParts.length === 0) {
|
|
49
|
-
return "";
|
|
50
|
-
}
|
|
51
|
-
const tempDir = mkdtempSync(join(tmpdir(), "nextclaw-codex-files-"));
|
|
52
|
-
const lines = fileParts.map((part, index) => {
|
|
53
|
-
const rawName = readString(part.name) ?? `attachment-${index + 1}`;
|
|
54
|
-
const fileName = sanitizeFileName(rawName);
|
|
55
|
-
const mimeSegment = readString(part.mimeType) ? ` (${part.mimeType})` : "";
|
|
56
|
-
const contentBase64 = readString(part.contentBase64);
|
|
57
|
-
if (contentBase64) {
|
|
58
|
-
const filePath = join(tempDir, fileName);
|
|
59
|
-
writeFileSync(filePath, Buffer.from(contentBase64.replace(/\s+/g, ""), "base64"));
|
|
60
|
-
return `- ${fileName}${mimeSegment} -> local file: ${filePath}`;
|
|
61
|
-
}
|
|
62
|
-
if (readString(part.url)) {
|
|
63
|
-
return `- ${fileName}${mimeSegment} -> remote url: ${part.url}`;
|
|
64
|
-
}
|
|
65
|
-
return `- ${fileName}${mimeSegment}`;
|
|
66
|
-
});
|
|
67
|
-
return ["Attached files for this turn:", ...lines].join("\n");
|
|
68
|
-
}
|
|
69
|
-
function buildCodexInputBuilder(workspace) {
|
|
70
|
-
const skillsLoader = new SkillsLoader(workspace);
|
|
71
|
-
return async (input) => {
|
|
72
|
-
const userText = readUserText(input);
|
|
73
|
-
const attachmentPrompt = buildAttachmentPrompt(readLatestUserMessage(input));
|
|
74
|
-
const promptBody = [userText || (attachmentPrompt ? "Please inspect the attached file(s) and respond." : ""), attachmentPrompt].filter(Boolean).join("\n\n");
|
|
75
|
-
const metadata = input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata) ? input.metadata : {};
|
|
76
|
-
const requestedSkills = readRequestedSkills(metadata);
|
|
77
|
-
return buildRequestedSkillsUserPrompt(skillsLoader, requestedSkills, promptBody);
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
export {
|
|
81
|
-
buildCodexInputBuilder
|
|
82
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
declare function resolveExternalModelProvider(params: {
|
|
2
|
-
explicitModelProvider?: unknown;
|
|
3
|
-
providerName?: string | null;
|
|
4
|
-
providerDisplayName?: string | null;
|
|
5
|
-
pluginId: string;
|
|
6
|
-
}): string;
|
|
7
|
-
declare function buildUserFacingModelRoute(params: {
|
|
8
|
-
externalModelProvider: string;
|
|
9
|
-
providerLocalModel: string;
|
|
10
|
-
resolvedModel: string;
|
|
11
|
-
}): string;
|
|
12
|
-
declare function buildCodexBridgeModelProviderId(externalModelProvider: string): string;
|
|
13
|
-
|
|
14
|
-
export { buildCodexBridgeModelProviderId, buildUserFacingModelRoute, resolveExternalModelProvider };
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
function readOptionalString(value) {
|
|
2
|
-
if (typeof value !== "string") {
|
|
3
|
-
return void 0;
|
|
4
|
-
}
|
|
5
|
-
const trimmed = value.trim();
|
|
6
|
-
return trimmed || void 0;
|
|
7
|
-
}
|
|
8
|
-
function isValidExternalModelProvider(value) {
|
|
9
|
-
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(value);
|
|
10
|
-
}
|
|
11
|
-
function resolveExternalModelProvider(params) {
|
|
12
|
-
const explicitModelProvider = readOptionalString(params.explicitModelProvider);
|
|
13
|
-
if (explicitModelProvider) {
|
|
14
|
-
return explicitModelProvider;
|
|
15
|
-
}
|
|
16
|
-
const providerName = readOptionalString(params.providerName);
|
|
17
|
-
if (providerName && !providerName.startsWith("custom-")) {
|
|
18
|
-
return providerName;
|
|
19
|
-
}
|
|
20
|
-
const providerDisplayName = readOptionalString(params.providerDisplayName);
|
|
21
|
-
if (providerDisplayName && isValidExternalModelProvider(providerDisplayName)) {
|
|
22
|
-
return providerDisplayName;
|
|
23
|
-
}
|
|
24
|
-
throw new Error(
|
|
25
|
-
`[codex] custom provider "${providerName ?? "unknown"}" requires an external model provider id. Set plugins.entries.${params.pluginId}.config.modelProvider or use a provider display name with only letters, numbers, ".", "_" or "-".`
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
function buildUserFacingModelRoute(params) {
|
|
29
|
-
const providerLocalModel = params.providerLocalModel.trim();
|
|
30
|
-
if (!providerLocalModel) {
|
|
31
|
-
return params.resolvedModel.trim();
|
|
32
|
-
}
|
|
33
|
-
return `${params.externalModelProvider}/${providerLocalModel}`;
|
|
34
|
-
}
|
|
35
|
-
function buildCodexBridgeModelProviderId(externalModelProvider) {
|
|
36
|
-
const normalized = externalModelProvider.trim();
|
|
37
|
-
if (!normalized) {
|
|
38
|
-
return "nextclaw-codex-bridge";
|
|
39
|
-
}
|
|
40
|
-
return `nextclaw-codex-bridge-${normalized}`;
|
|
41
|
-
}
|
|
42
|
-
export {
|
|
43
|
-
buildCodexBridgeModelProviderId,
|
|
44
|
-
buildUserFacingModelRoute,
|
|
45
|
-
resolveExternalModelProvider
|
|
46
|
-
};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { CodexOpenAiResponsesBridgeConfig, OpenAiChatCompletionResponse } from './codex-openai-responses-bridge-shared.js';
|
|
2
|
-
|
|
3
|
-
declare function callOpenAiCompatibleUpstream(params: {
|
|
4
|
-
config: CodexOpenAiResponsesBridgeConfig;
|
|
5
|
-
body: Record<string, unknown>;
|
|
6
|
-
}): Promise<{
|
|
7
|
-
model: string;
|
|
8
|
-
response: OpenAiChatCompletionResponse;
|
|
9
|
-
}>;
|
|
10
|
-
|
|
11
|
-
export { callOpenAiCompatibleUpstream };
|
|
@@ -1,334 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
readArray,
|
|
3
|
-
readBoolean,
|
|
4
|
-
readNumber,
|
|
5
|
-
readRecord,
|
|
6
|
-
readString,
|
|
7
|
-
withTrailingSlash
|
|
8
|
-
} from "./codex-openai-responses-bridge-shared.js";
|
|
9
|
-
function stripModelPrefix(model, prefixes) {
|
|
10
|
-
const normalizedModel = model.trim();
|
|
11
|
-
for (const prefix of prefixes) {
|
|
12
|
-
const normalizedPrefix = prefix.trim().toLowerCase();
|
|
13
|
-
if (!normalizedPrefix) {
|
|
14
|
-
continue;
|
|
15
|
-
}
|
|
16
|
-
const candidatePrefix = `${normalizedPrefix}/`;
|
|
17
|
-
if (normalizedModel.toLowerCase().startsWith(candidatePrefix)) {
|
|
18
|
-
return normalizedModel.slice(candidatePrefix.length);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
return normalizedModel;
|
|
22
|
-
}
|
|
23
|
-
function resolveUpstreamModel(requestedModel, config) {
|
|
24
|
-
const prefixes = (config.modelPrefixes ?? []).filter((value) => value.trim().length > 0);
|
|
25
|
-
const model = stripModelPrefix(readString(requestedModel) ?? "", prefixes) || stripModelPrefix(config.defaultModel ?? "", prefixes);
|
|
26
|
-
if (!model) {
|
|
27
|
-
throw new Error("Codex bridge could not resolve an upstream model.");
|
|
28
|
-
}
|
|
29
|
-
return model;
|
|
30
|
-
}
|
|
31
|
-
function normalizeTextPart(value) {
|
|
32
|
-
const record = readRecord(value);
|
|
33
|
-
if (!record) {
|
|
34
|
-
return "";
|
|
35
|
-
}
|
|
36
|
-
const type = readString(record.type);
|
|
37
|
-
if (type !== "input_text" && type !== "output_text") {
|
|
38
|
-
return "";
|
|
39
|
-
}
|
|
40
|
-
return readString(record.text) ?? "";
|
|
41
|
-
}
|
|
42
|
-
function normalizeImageUrl(value) {
|
|
43
|
-
const record = readRecord(value);
|
|
44
|
-
if (!record || readString(record.type) !== "input_image") {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
const source = readRecord(record.source);
|
|
48
|
-
if (!source) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
if (readString(source.type) === "url") {
|
|
52
|
-
return readString(source.url) ?? null;
|
|
53
|
-
}
|
|
54
|
-
if (readString(source.type) === "base64") {
|
|
55
|
-
const mediaType = readString(source.media_type) ?? "application/octet-stream";
|
|
56
|
-
const data = readString(source.data);
|
|
57
|
-
if (!data) {
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
return `data:${mediaType};base64,${data}`;
|
|
61
|
-
}
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
function normalizeToolOutput(value) {
|
|
65
|
-
if (typeof value === "string") {
|
|
66
|
-
return value;
|
|
67
|
-
}
|
|
68
|
-
if (Array.isArray(value)) {
|
|
69
|
-
const text = value.map((entry) => normalizeTextPart(entry)).filter(Boolean).join("");
|
|
70
|
-
if (text) {
|
|
71
|
-
return text;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
return JSON.stringify(value ?? "");
|
|
76
|
-
} catch {
|
|
77
|
-
return String(value ?? "");
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
function buildChatContent(content) {
|
|
81
|
-
if (typeof content === "string") {
|
|
82
|
-
return content;
|
|
83
|
-
}
|
|
84
|
-
if (!Array.isArray(content)) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const chatContent = [];
|
|
88
|
-
for (const entry of content) {
|
|
89
|
-
const text = normalizeTextPart(entry);
|
|
90
|
-
if (text) {
|
|
91
|
-
chatContent.push({
|
|
92
|
-
type: "text",
|
|
93
|
-
text
|
|
94
|
-
});
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
const imageUrl = normalizeImageUrl(entry);
|
|
98
|
-
if (imageUrl) {
|
|
99
|
-
chatContent.push({
|
|
100
|
-
type: "image_url",
|
|
101
|
-
image_url: {
|
|
102
|
-
url: imageUrl
|
|
103
|
-
}
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (chatContent.length === 0) {
|
|
108
|
-
return null;
|
|
109
|
-
}
|
|
110
|
-
const textOnly = chatContent.every((entry) => entry.type === "text");
|
|
111
|
-
if (textOnly) {
|
|
112
|
-
return chatContent.map((entry) => readString(entry.text) ?? "").join("\n");
|
|
113
|
-
}
|
|
114
|
-
return chatContent;
|
|
115
|
-
}
|
|
116
|
-
function readAssistantMessageText(content) {
|
|
117
|
-
if (typeof content === "string") {
|
|
118
|
-
return content;
|
|
119
|
-
}
|
|
120
|
-
if (!Array.isArray(content)) {
|
|
121
|
-
return "";
|
|
122
|
-
}
|
|
123
|
-
return content.filter((entry) => entry.type === "text").map((entry) => readString(entry.text) ?? "").join("\n");
|
|
124
|
-
}
|
|
125
|
-
function appendMessageInputItem(params) {
|
|
126
|
-
const role = readString(params.item.role);
|
|
127
|
-
const content = buildChatContent(params.item.content);
|
|
128
|
-
if (role === "assistant") {
|
|
129
|
-
const text = readAssistantMessageText(content);
|
|
130
|
-
if (text.trim()) {
|
|
131
|
-
params.assistantTextParts.push(text);
|
|
132
|
-
}
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
params.flushAssistant();
|
|
136
|
-
const normalizedRole = role === "developer" ? "system" : role;
|
|
137
|
-
if ((normalizedRole === "system" || normalizedRole === "user") && content !== null) {
|
|
138
|
-
params.messages.push({
|
|
139
|
-
role: normalizedRole,
|
|
140
|
-
content
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function appendFunctionCallItem(params) {
|
|
145
|
-
const name = readString(params.item.name);
|
|
146
|
-
const argumentsText = readString(params.item.arguments) ?? "{}";
|
|
147
|
-
if (!name) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
const callId = readString(params.item.call_id) ?? readString(params.item.id) ?? `call_${params.assistantToolCalls.length}`;
|
|
151
|
-
params.assistantToolCalls.push({
|
|
152
|
-
id: callId,
|
|
153
|
-
type: "function",
|
|
154
|
-
function: {
|
|
155
|
-
name,
|
|
156
|
-
arguments: argumentsText
|
|
157
|
-
}
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
function appendFunctionCallOutputItem(params) {
|
|
161
|
-
params.flushAssistant();
|
|
162
|
-
const callId = readString(params.item.call_id);
|
|
163
|
-
if (!callId) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
params.messages.push({
|
|
167
|
-
role: "tool",
|
|
168
|
-
tool_call_id: callId,
|
|
169
|
-
content: normalizeToolOutput(params.item.output)
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
function buildOpenAiMessages(input, instructions) {
|
|
173
|
-
const messages = [];
|
|
174
|
-
const instructionText = readString(instructions);
|
|
175
|
-
if (instructionText) {
|
|
176
|
-
messages.push({
|
|
177
|
-
role: "system",
|
|
178
|
-
content: instructionText
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
if (typeof input === "string") {
|
|
182
|
-
return [
|
|
183
|
-
...messages,
|
|
184
|
-
{
|
|
185
|
-
role: "user",
|
|
186
|
-
content: input
|
|
187
|
-
}
|
|
188
|
-
];
|
|
189
|
-
}
|
|
190
|
-
const assistantTextParts = [];
|
|
191
|
-
const assistantToolCalls = [];
|
|
192
|
-
const flushAssistant = () => {
|
|
193
|
-
if (assistantTextParts.length === 0 && assistantToolCalls.length === 0) {
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
messages.push({
|
|
197
|
-
role: "assistant",
|
|
198
|
-
content: assistantTextParts.join("\n").trim() || null,
|
|
199
|
-
...assistantToolCalls.length > 0 ? {
|
|
200
|
-
tool_calls: structuredClone(assistantToolCalls)
|
|
201
|
-
} : {}
|
|
202
|
-
});
|
|
203
|
-
assistantTextParts.length = 0;
|
|
204
|
-
assistantToolCalls.length = 0;
|
|
205
|
-
};
|
|
206
|
-
for (const rawItem of readArray(input)) {
|
|
207
|
-
const item = readRecord(rawItem);
|
|
208
|
-
if (!item) {
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
const type = readString(item.type);
|
|
212
|
-
if (type === "message") {
|
|
213
|
-
appendMessageInputItem({
|
|
214
|
-
messages,
|
|
215
|
-
assistantTextParts,
|
|
216
|
-
assistantToolCalls,
|
|
217
|
-
item,
|
|
218
|
-
flushAssistant
|
|
219
|
-
});
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
if (type === "function_call") {
|
|
223
|
-
appendFunctionCallItem({
|
|
224
|
-
assistantToolCalls,
|
|
225
|
-
item
|
|
226
|
-
});
|
|
227
|
-
continue;
|
|
228
|
-
}
|
|
229
|
-
if (type === "function_call_output") {
|
|
230
|
-
appendFunctionCallOutputItem({
|
|
231
|
-
messages,
|
|
232
|
-
item,
|
|
233
|
-
flushAssistant
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
flushAssistant();
|
|
238
|
-
return messages;
|
|
239
|
-
}
|
|
240
|
-
function toOpenAiTools(value) {
|
|
241
|
-
const tools = [];
|
|
242
|
-
for (const entry of readArray(value)) {
|
|
243
|
-
const tool = readRecord(entry);
|
|
244
|
-
const type = readString(tool?.type);
|
|
245
|
-
const fn = readRecord(tool?.function);
|
|
246
|
-
const name = readString(fn?.name) ?? readString(tool?.name);
|
|
247
|
-
if (type !== "function" || !name) {
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
const description = (fn ? readString(fn.description) : void 0) ?? readString(tool?.description);
|
|
251
|
-
const parameters = (fn ? readRecord(fn.parameters) : void 0) ?? readRecord(tool?.parameters);
|
|
252
|
-
const strict = (fn ? readBoolean(fn.strict) : void 0) ?? readBoolean(tool?.strict);
|
|
253
|
-
tools.push({
|
|
254
|
-
type: "function",
|
|
255
|
-
function: {
|
|
256
|
-
name,
|
|
257
|
-
...description ? { description } : {},
|
|
258
|
-
parameters: parameters ?? {
|
|
259
|
-
type: "object",
|
|
260
|
-
properties: {}
|
|
261
|
-
},
|
|
262
|
-
...strict !== void 0 ? { strict } : {}
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
return tools.length > 0 ? tools : void 0;
|
|
267
|
-
}
|
|
268
|
-
function toOpenAiToolChoice(value) {
|
|
269
|
-
if (value === "auto" || value === "none" || value === "required") {
|
|
270
|
-
return value;
|
|
271
|
-
}
|
|
272
|
-
const record = readRecord(value);
|
|
273
|
-
const fn = readRecord(record?.function);
|
|
274
|
-
const name = readString(fn?.name) ?? readString(record?.name);
|
|
275
|
-
if (readString(record?.type) === "function" && name) {
|
|
276
|
-
return {
|
|
277
|
-
type: "function",
|
|
278
|
-
function: {
|
|
279
|
-
name
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
}
|
|
283
|
-
return void 0;
|
|
284
|
-
}
|
|
285
|
-
async function callOpenAiCompatibleUpstream(params) {
|
|
286
|
-
const model = resolveUpstreamModel(params.body.model, params.config);
|
|
287
|
-
const upstreamUrl = new URL(
|
|
288
|
-
"chat/completions",
|
|
289
|
-
withTrailingSlash(params.config.upstreamApiBase)
|
|
290
|
-
);
|
|
291
|
-
const tools = toOpenAiTools(params.body.tools);
|
|
292
|
-
const toolChoice = toOpenAiToolChoice(params.body.tool_choice);
|
|
293
|
-
const upstreamResponse = await fetch(upstreamUrl.toString(), {
|
|
294
|
-
method: "POST",
|
|
295
|
-
headers: {
|
|
296
|
-
"Content-Type": "application/json",
|
|
297
|
-
...params.config.upstreamApiKey ? {
|
|
298
|
-
Authorization: `Bearer ${params.config.upstreamApiKey}`
|
|
299
|
-
} : {},
|
|
300
|
-
...params.config.upstreamExtraHeaders ?? {}
|
|
301
|
-
},
|
|
302
|
-
body: JSON.stringify({
|
|
303
|
-
model,
|
|
304
|
-
messages: buildOpenAiMessages(params.body.input, params.body.instructions),
|
|
305
|
-
...tools ? { tools } : {},
|
|
306
|
-
...toolChoice ? { tool_choice: toolChoice } : {},
|
|
307
|
-
...typeof params.body.max_output_tokens === "number" ? {
|
|
308
|
-
max_tokens: Math.max(
|
|
309
|
-
1,
|
|
310
|
-
Math.trunc(readNumber(params.body.max_output_tokens) ?? 1)
|
|
311
|
-
)
|
|
312
|
-
} : {}
|
|
313
|
-
})
|
|
314
|
-
});
|
|
315
|
-
const rawText = await upstreamResponse.text();
|
|
316
|
-
let parsed;
|
|
317
|
-
try {
|
|
318
|
-
parsed = JSON.parse(rawText);
|
|
319
|
-
} catch {
|
|
320
|
-
throw new Error(`Bridge upstream returned invalid JSON: ${rawText.slice(0, 240)}`);
|
|
321
|
-
}
|
|
322
|
-
if (!upstreamResponse.ok) {
|
|
323
|
-
throw new Error(
|
|
324
|
-
readString(parsed.error?.message) ?? rawText.slice(0, 240) ?? `HTTP ${upstreamResponse.status}`
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
return {
|
|
328
|
-
model,
|
|
329
|
-
response: parsed
|
|
330
|
-
};
|
|
331
|
-
}
|
|
332
|
-
export {
|
|
333
|
-
callOpenAiCompatibleUpstream
|
|
334
|
-
};
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
type CodexOpenAiResponsesBridgeConfig = {
|
|
2
|
-
upstreamApiBase: string;
|
|
3
|
-
upstreamApiKey?: string;
|
|
4
|
-
upstreamExtraHeaders?: Record<string, string>;
|
|
5
|
-
defaultModel?: string;
|
|
6
|
-
modelPrefixes?: string[];
|
|
7
|
-
};
|
|
8
|
-
type CodexOpenAiResponsesBridgeResult = {
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
};
|
|
11
|
-
type BridgeEntry = {
|
|
12
|
-
promise: Promise<CodexOpenAiResponsesBridgeResult>;
|
|
13
|
-
};
|
|
14
|
-
type OpenResponsesItemRecord = Record<string, unknown>;
|
|
15
|
-
type OpenAiChatCompletionChoiceMessage = {
|
|
16
|
-
content?: unknown;
|
|
17
|
-
tool_calls?: unknown;
|
|
18
|
-
};
|
|
19
|
-
type OpenAiChatCompletionResponse = {
|
|
20
|
-
choices?: Array<{
|
|
21
|
-
message?: OpenAiChatCompletionChoiceMessage;
|
|
22
|
-
}>;
|
|
23
|
-
usage?: {
|
|
24
|
-
prompt_tokens?: number;
|
|
25
|
-
completion_tokens?: number;
|
|
26
|
-
total_tokens?: number;
|
|
27
|
-
};
|
|
28
|
-
error?: {
|
|
29
|
-
message?: unknown;
|
|
30
|
-
};
|
|
31
|
-
};
|
|
32
|
-
type OpenResponsesOutputItem = Record<string, unknown>;
|
|
33
|
-
type StreamSequenceState = {
|
|
34
|
-
value: number;
|
|
35
|
-
};
|
|
36
|
-
declare function readString(value: unknown): string | undefined;
|
|
37
|
-
declare function readBoolean(value: unknown): boolean | undefined;
|
|
38
|
-
declare function readNumber(value: unknown): number | undefined;
|
|
39
|
-
declare function readRecord(value: unknown): Record<string, unknown> | undefined;
|
|
40
|
-
declare function readArray(value: unknown): unknown[];
|
|
41
|
-
declare function withTrailingSlash(value: string): string;
|
|
42
|
-
declare function writeSseEvent(response: {
|
|
43
|
-
write: (chunk: string) => void;
|
|
44
|
-
}, eventType: string, payload: Record<string, unknown>): void;
|
|
45
|
-
declare function nextSequenceNumber(state: StreamSequenceState): number;
|
|
46
|
-
|
|
47
|
-
export { type BridgeEntry, type CodexOpenAiResponsesBridgeConfig, type CodexOpenAiResponsesBridgeResult, type OpenAiChatCompletionChoiceMessage, type OpenAiChatCompletionResponse, type OpenResponsesItemRecord, type OpenResponsesOutputItem, type StreamSequenceState, nextSequenceNumber, readArray, readBoolean, readNumber, readRecord, readString, withTrailingSlash, writeSseEvent };
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
function readString(value) {
|
|
2
|
-
if (typeof value !== "string") {
|
|
3
|
-
return void 0;
|
|
4
|
-
}
|
|
5
|
-
const trimmed = value.trim();
|
|
6
|
-
return trimmed || void 0;
|
|
7
|
-
}
|
|
8
|
-
function readBoolean(value) {
|
|
9
|
-
return typeof value === "boolean" ? value : void 0;
|
|
10
|
-
}
|
|
11
|
-
function readNumber(value) {
|
|
12
|
-
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
13
|
-
return void 0;
|
|
14
|
-
}
|
|
15
|
-
return value;
|
|
16
|
-
}
|
|
17
|
-
function readRecord(value) {
|
|
18
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
19
|
-
return void 0;
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
function readArray(value) {
|
|
24
|
-
return Array.isArray(value) ? value : [];
|
|
25
|
-
}
|
|
26
|
-
function withTrailingSlash(value) {
|
|
27
|
-
return value.endsWith("/") ? value : `${value}/`;
|
|
28
|
-
}
|
|
29
|
-
function writeSseEvent(response, eventType, payload) {
|
|
30
|
-
response.write(`event: ${eventType}
|
|
31
|
-
`);
|
|
32
|
-
response.write(`data: ${JSON.stringify(payload)}
|
|
33
|
-
|
|
34
|
-
`);
|
|
35
|
-
}
|
|
36
|
-
function nextSequenceNumber(state) {
|
|
37
|
-
const nextValue = state.value;
|
|
38
|
-
state.value += 1;
|
|
39
|
-
return nextValue;
|
|
40
|
-
}
|
|
41
|
-
export {
|
|
42
|
-
nextSequenceNumber,
|
|
43
|
-
readArray,
|
|
44
|
-
readBoolean,
|
|
45
|
-
readNumber,
|
|
46
|
-
readRecord,
|
|
47
|
-
readString,
|
|
48
|
-
withTrailingSlash,
|
|
49
|
-
writeSseEvent
|
|
50
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { ServerResponse } from 'node:http';
|
|
2
|
-
import { OpenAiChatCompletionResponse, OpenResponsesOutputItem } from './codex-openai-responses-bridge-shared.js';
|
|
3
|
-
|
|
4
|
-
declare function writeStreamError(response: ServerResponse, message: string): void;
|
|
5
|
-
declare function buildBridgeResponsePayload(params: {
|
|
6
|
-
responseId: string;
|
|
7
|
-
model: string;
|
|
8
|
-
response: OpenAiChatCompletionResponse;
|
|
9
|
-
}): {
|
|
10
|
-
outputItems: OpenResponsesOutputItem[];
|
|
11
|
-
usage: Record<string, unknown>;
|
|
12
|
-
responseResource: Record<string, unknown>;
|
|
13
|
-
};
|
|
14
|
-
declare function writeResponsesStream(params: {
|
|
15
|
-
response: ServerResponse;
|
|
16
|
-
responseId: string;
|
|
17
|
-
model: string;
|
|
18
|
-
outputItems: OpenResponsesOutputItem[];
|
|
19
|
-
responseResource: Record<string, unknown>;
|
|
20
|
-
}): void;
|
|
21
|
-
|
|
22
|
-
export { buildBridgeResponsePayload, writeResponsesStream, writeStreamError };
|