mimo2codex 0.1.15 → 0.1.16
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/AGENTS.md +24 -5
- package/README.md +46 -5
- package/README.zh.md +46 -5
- package/dist/admin/router.js +117 -2
- package/dist/admin/router.js.map +1 -1
- package/dist/cli.js +67 -147
- package/dist/cli.js.map +1 -1
- package/dist/config.js +16 -10
- package/dist/config.js.map +1 -1
- package/dist/db/logs.js +80 -0
- package/dist/db/logs.js.map +1 -1
- package/dist/providers/generic.js +96 -0
- package/dist/providers/generic.js.map +1 -0
- package/dist/providers/genericLoader.js +229 -0
- package/dist/providers/genericLoader.js.map +1 -0
- package/dist/providers/registry.js +48 -10
- package/dist/providers/registry.js.map +1 -1
- package/dist/server.js +201 -1
- package/dist/server.js.map +1 -1
- package/dist/setup/snippets.js +187 -0
- package/dist/setup/snippets.js.map +1 -0
- package/dist/translate/reqToChat.js +1 -1
- package/dist/translate/reqToChat.js.map +1 -1
- package/dist/upstream/openaiCompatClient.js +32 -11
- package/dist/upstream/openaiCompatClient.js.map +1 -1
- package/dist/web/assets/index-D19ffnSJ.css +1 -0
- package/dist/web/assets/index-DPLJprJ4.js +67 -0
- package/dist/web/index.html +2 -2
- package/doc/generic-providers.md +399 -0
- package/doc/generic-providers.zh.md +399 -0
- package/mimoskill/SKILL.md +69 -8
- package/mimoskill/references/ocr_workflow.md +216 -0
- package/mimoskill/scripts/generate_image.py +163 -0
- package/mimoskill/scripts/ocr.py +396 -0
- package/package.json +5 -4
- package/dist/web/assets/index-BoykBCnY.js +0 -67
- package/dist/web/assets/index-DAJbSznk.css +0 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { createGenericProvider, GenericProviderSpecError, } from "./generic.js";
|
|
4
|
+
import { log } from "../util/log.js";
|
|
5
|
+
export class GenericLoaderError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "GenericLoaderError";
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
// Resolve which JSON file path applies. Unlike resolveProvidersFile, this
|
|
12
|
+
// returns the path even when the file doesn't exist yet — used by the admin
|
|
13
|
+
// UI to know where a first-time save should go.
|
|
14
|
+
export function locateProvidersFile(env, dataDir) {
|
|
15
|
+
const explicit = env.MIMO2CODEX_PROVIDERS_FILE;
|
|
16
|
+
if (explicit) {
|
|
17
|
+
return { path: explicit, source: "explicit", exists: existsSync(explicit) };
|
|
18
|
+
}
|
|
19
|
+
if (!dataDir)
|
|
20
|
+
return null;
|
|
21
|
+
const defaultPath = join(dataDir, "providers.json");
|
|
22
|
+
return { path: defaultPath, source: "default", exists: existsSync(defaultPath) };
|
|
23
|
+
}
|
|
24
|
+
// Resolve which JSON file (if any) to read. Order:
|
|
25
|
+
// 1. $MIMO2CODEX_PROVIDERS_FILE — explicit override
|
|
26
|
+
// 2. <dataDir>/providers.json — default location, co-located with sqlite
|
|
27
|
+
function resolveProvidersFile(env, dataDir) {
|
|
28
|
+
const explicit = env.MIMO2CODEX_PROVIDERS_FILE;
|
|
29
|
+
if (explicit) {
|
|
30
|
+
if (!existsSync(explicit)) {
|
|
31
|
+
throw new GenericLoaderError(`MIMO2CODEX_PROVIDERS_FILE points to "${explicit}" but the file does not exist`);
|
|
32
|
+
}
|
|
33
|
+
return explicit;
|
|
34
|
+
}
|
|
35
|
+
if (!dataDir)
|
|
36
|
+
return null;
|
|
37
|
+
const defaultPath = join(dataDir, "providers.json");
|
|
38
|
+
return existsSync(defaultPath) ? defaultPath : null;
|
|
39
|
+
}
|
|
40
|
+
function parseModels(raw, providerId) {
|
|
41
|
+
if (raw === undefined || raw === null)
|
|
42
|
+
return [];
|
|
43
|
+
if (!Array.isArray(raw)) {
|
|
44
|
+
throw new GenericLoaderError(`provider "${providerId}" .models must be an array`);
|
|
45
|
+
}
|
|
46
|
+
return raw.map((m, idx) => {
|
|
47
|
+
if (typeof m !== "object" || m === null) {
|
|
48
|
+
throw new GenericLoaderError(`provider "${providerId}" .models[${idx}] must be an object`);
|
|
49
|
+
}
|
|
50
|
+
const obj = m;
|
|
51
|
+
if (typeof obj.id !== "string" || !obj.id) {
|
|
52
|
+
throw new GenericLoaderError(`provider "${providerId}" .models[${idx}].id must be a string`);
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
id: obj.id,
|
|
56
|
+
aliases: Array.isArray(obj.aliases) ? obj.aliases : undefined,
|
|
57
|
+
displayName: typeof obj.displayName === "string" ? obj.displayName : undefined,
|
|
58
|
+
supportsImages: typeof obj.supportsImages === "boolean" ? obj.supportsImages : undefined,
|
|
59
|
+
supportsReasoning: typeof obj.supportsReasoning === "boolean" ? obj.supportsReasoning : undefined,
|
|
60
|
+
supportsWebSearch: typeof obj.supportsWebSearch === "boolean" ? obj.supportsWebSearch : undefined,
|
|
61
|
+
contextWindow: typeof obj.contextWindow === "number" ? obj.contextWindow : undefined,
|
|
62
|
+
maxOutputTokens: typeof obj.maxOutputTokens === "number" ? obj.maxOutputTokens : undefined,
|
|
63
|
+
deprecatedAfter: typeof obj.deprecatedAfter === "string" ? obj.deprecatedAfter : undefined,
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function parseSpec(raw, idx) {
|
|
68
|
+
if (typeof raw !== "object" || raw === null) {
|
|
69
|
+
throw new GenericLoaderError(`providers[${idx}] must be an object`);
|
|
70
|
+
}
|
|
71
|
+
const obj = raw;
|
|
72
|
+
const id = typeof obj.id === "string" ? obj.id : undefined;
|
|
73
|
+
if (!id) {
|
|
74
|
+
throw new GenericLoaderError(`providers[${idx}].id is required`);
|
|
75
|
+
}
|
|
76
|
+
const features = obj.features && typeof obj.features === "object"
|
|
77
|
+
? obj.features
|
|
78
|
+
: undefined;
|
|
79
|
+
return {
|
|
80
|
+
id,
|
|
81
|
+
shortcut: typeof obj.shortcut === "string" ? obj.shortcut : undefined,
|
|
82
|
+
displayName: typeof obj.displayName === "string" ? obj.displayName : undefined,
|
|
83
|
+
baseUrl: typeof obj.baseUrl === "string" ? obj.baseUrl : "",
|
|
84
|
+
envKey: typeof obj.envKey === "string" ? obj.envKey : "",
|
|
85
|
+
defaultModel: typeof obj.defaultModel === "string" ? obj.defaultModel : "",
|
|
86
|
+
wireApi: obj.wireApi === "responses" || obj.wireApi === "chat"
|
|
87
|
+
? obj.wireApi
|
|
88
|
+
: undefined,
|
|
89
|
+
models: parseModels(obj.models, id),
|
|
90
|
+
features: features
|
|
91
|
+
? {
|
|
92
|
+
webSearch: typeof features.webSearch === "boolean" ? features.webSearch : undefined,
|
|
93
|
+
forceParallelToolCalls: typeof features.forceParallelToolCalls === "boolean"
|
|
94
|
+
? features.forceParallelToolCalls
|
|
95
|
+
: undefined,
|
|
96
|
+
}
|
|
97
|
+
: undefined,
|
|
98
|
+
docsUrl: typeof obj.docsUrl === "string" ? obj.docsUrl : undefined,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Read & parse the providers.json at `filePath`. Returns the validated spec
|
|
102
|
+
// list (model entries normalized, types coerced). Throws GenericLoaderError
|
|
103
|
+
// on file/JSON/schema problems. Used both by loadGenericProviders at boot
|
|
104
|
+
// and by the admin UI's GET /admin/api/generic-providers handler.
|
|
105
|
+
export function readSpecsFromFile(filePath) {
|
|
106
|
+
return loadFromFile(filePath);
|
|
107
|
+
}
|
|
108
|
+
// Atomically write a vetted spec list to `filePath`. Creates the parent
|
|
109
|
+
// directory if missing. Throws on validation failure: every spec is run
|
|
110
|
+
// through createGenericProvider first (which is what loadGenericProviders
|
|
111
|
+
// would do at boot), and ids must be unique. The admin UI never persists
|
|
112
|
+
// data that wouldn't load cleanly on restart.
|
|
113
|
+
export function writeSpecsToFile(filePath, specs) {
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
for (const spec of specs) {
|
|
116
|
+
if (!spec || typeof spec !== "object") {
|
|
117
|
+
throw new GenericLoaderError("each provider entry must be an object");
|
|
118
|
+
}
|
|
119
|
+
if (seen.has(spec.id)) {
|
|
120
|
+
throw new GenericLoaderError(`duplicate generic provider id "${spec.id}" — each id must appear once`);
|
|
121
|
+
}
|
|
122
|
+
seen.add(spec.id);
|
|
123
|
+
try {
|
|
124
|
+
// Build the Provider to run the same validation the boot path uses.
|
|
125
|
+
// We discard the result — only the side-effect of validation matters.
|
|
126
|
+
createGenericProvider(spec);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
if (err instanceof GenericProviderSpecError) {
|
|
130
|
+
throw new GenericLoaderError(err.message);
|
|
131
|
+
}
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const dir = dirname(filePath);
|
|
136
|
+
if (dir && !existsSync(dir)) {
|
|
137
|
+
mkdirSync(dir, { recursive: true });
|
|
138
|
+
}
|
|
139
|
+
writeFileSync(filePath, JSON.stringify({ providers: specs }, null, 2) + "\n", "utf-8");
|
|
140
|
+
}
|
|
141
|
+
function loadFromFile(filePath) {
|
|
142
|
+
let text;
|
|
143
|
+
try {
|
|
144
|
+
text = readFileSync(filePath, "utf-8");
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
throw new GenericLoaderError(`failed to read providers file ${filePath}: ${err.message}`);
|
|
148
|
+
}
|
|
149
|
+
let parsed;
|
|
150
|
+
try {
|
|
151
|
+
parsed = JSON.parse(text);
|
|
152
|
+
}
|
|
153
|
+
catch (err) {
|
|
154
|
+
throw new GenericLoaderError(`providers file ${filePath} is not valid JSON: ${err.message}`);
|
|
155
|
+
}
|
|
156
|
+
if (!parsed || typeof parsed !== "object") {
|
|
157
|
+
throw new GenericLoaderError(`providers file ${filePath} must be a JSON object`);
|
|
158
|
+
}
|
|
159
|
+
const raw = parsed.providers;
|
|
160
|
+
if (raw === undefined)
|
|
161
|
+
return [];
|
|
162
|
+
if (!Array.isArray(raw)) {
|
|
163
|
+
throw new GenericLoaderError(`providers file ${filePath}: .providers must be an array`);
|
|
164
|
+
}
|
|
165
|
+
return raw.map((item, idx) => parseSpec(item, idx));
|
|
166
|
+
}
|
|
167
|
+
// Env-only single-instance shortcut. When providers.json is absent but the
|
|
168
|
+
// user has set GENERIC_BASE_URL + GENERIC_API_KEY + GENERIC_DEFAULT_MODEL,
|
|
169
|
+
// we synthesize one provider with id "generic" — the simplest path for "I
|
|
170
|
+
// just want to plug in one OpenAI-compat upstream".
|
|
171
|
+
function loadFromEnv(env) {
|
|
172
|
+
const baseUrl = env.GENERIC_BASE_URL;
|
|
173
|
+
const defaultModel = env.GENERIC_DEFAULT_MODEL;
|
|
174
|
+
if (!baseUrl || !defaultModel)
|
|
175
|
+
return [];
|
|
176
|
+
// We don't require GENERIC_API_KEY to be set right now (it might be set
|
|
177
|
+
// later when the user passes --api-key). But envKey itself is required for
|
|
178
|
+
// resolveProviderRuntime to know which env var to read, so we hardcode
|
|
179
|
+
// "GENERIC_API_KEY" as the convention.
|
|
180
|
+
return [
|
|
181
|
+
{
|
|
182
|
+
id: "generic",
|
|
183
|
+
shortcut: env.GENERIC_SHORTCUT ?? "generic",
|
|
184
|
+
displayName: env.GENERIC_DISPLAY_NAME ?? "Generic (OpenAI-compatible)",
|
|
185
|
+
baseUrl,
|
|
186
|
+
envKey: "GENERIC_API_KEY",
|
|
187
|
+
defaultModel,
|
|
188
|
+
wireApi: env.GENERIC_WIRE_API === "responses" || env.GENERIC_WIRE_API === "chat"
|
|
189
|
+
? env.GENERIC_WIRE_API
|
|
190
|
+
: undefined,
|
|
191
|
+
models: [],
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
}
|
|
195
|
+
// Load all user-declared generic providers. Returns [] when nothing is
|
|
196
|
+
// configured — existing mimo/deepseek behavior stays untouched.
|
|
197
|
+
export function loadGenericProviders(env, dataDir) {
|
|
198
|
+
const filePath = resolveProvidersFile(env, dataDir);
|
|
199
|
+
let specs = [];
|
|
200
|
+
if (filePath) {
|
|
201
|
+
specs = loadFromFile(filePath);
|
|
202
|
+
log.debug(`loaded ${specs.length} generic provider(s) from ${filePath}`);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
specs = loadFromEnv(env);
|
|
206
|
+
if (specs.length > 0) {
|
|
207
|
+
log.debug(`synthesized 1 generic provider from GENERIC_* env vars`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const seen = new Set();
|
|
211
|
+
const result = [];
|
|
212
|
+
for (const spec of specs) {
|
|
213
|
+
if (seen.has(spec.id)) {
|
|
214
|
+
throw new GenericLoaderError(`duplicate generic provider id "${spec.id}" — each id must appear once`);
|
|
215
|
+
}
|
|
216
|
+
seen.add(spec.id);
|
|
217
|
+
try {
|
|
218
|
+
result.push(createGenericProvider(spec));
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
if (err instanceof GenericProviderSpecError) {
|
|
222
|
+
throw new GenericLoaderError(err.message);
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=genericLoader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"genericLoader.js","sourceRoot":"","sources":["../../src/providers/genericLoader.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,25 +1,63 @@
|
|
|
1
1
|
import { mimo } from "./mimo.js";
|
|
2
2
|
import { deepseek } from "./deepseek.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
// Built-in providers are always registered first. Generic providers loaded
|
|
4
|
+
// at startup (from providers.json or GENERIC_* env vars) are appended via
|
|
5
|
+
// initRegistry() before buildConfig() runs. The registry is mutable state
|
|
6
|
+
// but transitions only once per process — there is no hot reload.
|
|
7
|
+
const BUILTIN_PROVIDERS = [mimo, deepseek];
|
|
8
|
+
// Single mutable container objects. Importers keep their reference and see
|
|
9
|
+
// the updated contents after initRegistry() runs. This avoids Proxy gymnastics
|
|
10
|
+
// while still letting the registry be populated lazily at startup.
|
|
11
|
+
const providerListMutable = [...BUILTIN_PROVIDERS];
|
|
12
|
+
const providersMapMutable = Object.fromEntries(BUILTIN_PROVIDERS.map((p) => [p.id, p]));
|
|
13
|
+
export const PROVIDER_LIST = providerListMutable;
|
|
14
|
+
export const PROVIDERS = providersMapMutable;
|
|
15
|
+
// Initialize the registry with extra generic providers. Called once from
|
|
16
|
+
// cli.ts main() before buildConfig(). Safe to call zero times — the
|
|
17
|
+
// built-ins are present from module load.
|
|
18
|
+
export function initRegistry(generics) {
|
|
19
|
+
// Reset to built-ins (idempotent — tests may call this multiple times).
|
|
20
|
+
providerListMutable.length = 0;
|
|
21
|
+
providerListMutable.push(...BUILTIN_PROVIDERS);
|
|
22
|
+
for (const key of Object.keys(providersMapMutable)) {
|
|
23
|
+
delete providersMapMutable[key];
|
|
24
|
+
}
|
|
25
|
+
for (const p of BUILTIN_PROVIDERS) {
|
|
26
|
+
providersMapMutable[p.id] = p;
|
|
27
|
+
}
|
|
28
|
+
for (const p of generics) {
|
|
29
|
+
if (providersMapMutable[p.id]) {
|
|
30
|
+
throw new Error(`provider id "${p.id}" collides with an already-registered provider — pick a different id`);
|
|
31
|
+
}
|
|
32
|
+
providerListMutable.push(p);
|
|
33
|
+
providersMapMutable[p.id] = p;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
8
36
|
export function byShortcut(s) {
|
|
9
37
|
const norm = s.toLowerCase();
|
|
10
|
-
return
|
|
38
|
+
return providerListMutable.find((p) => p.shortcut === norm || p.id === norm);
|
|
11
39
|
}
|
|
12
40
|
// Find which provider owns a given client-supplied model id (or alias).
|
|
13
|
-
// Used in
|
|
14
|
-
//
|
|
41
|
+
// Used by selectProvider() in server.ts for per-request routing.
|
|
42
|
+
//
|
|
43
|
+
// Note: generic providers with empty `builtinModels` accept any model id via
|
|
44
|
+
// their `resolveModel()` (untyped passthrough). To prevent such a generic
|
|
45
|
+
// from "swallowing" every unknown id (which would defeat the configured
|
|
46
|
+
// default provider's routing), we skip open-catalog generics in this lookup.
|
|
47
|
+
// Routing to an open-catalog generic happens via `selectProvider` only when
|
|
48
|
+
// it is the configured default provider, or when the user has declared
|
|
49
|
+
// matching `models[]` in the spec.
|
|
15
50
|
export function byClientModel(model) {
|
|
16
|
-
for (const p of
|
|
51
|
+
for (const p of providerListMutable) {
|
|
52
|
+
const isOpenCatalog = !p.builtinModels || p.builtinModels.length === 0;
|
|
53
|
+
if (isOpenCatalog)
|
|
54
|
+
continue;
|
|
17
55
|
if (p.resolveModel(model))
|
|
18
56
|
return p;
|
|
19
57
|
}
|
|
20
58
|
return undefined;
|
|
21
59
|
}
|
|
22
60
|
export function isProviderId(s) {
|
|
23
|
-
return
|
|
61
|
+
return Object.prototype.hasOwnProperty.call(providersMapMutable, s);
|
|
24
62
|
}
|
|
25
63
|
//# sourceMappingURL=registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,MAAM,CAAC,MAAM,SAAS,GAA2C;IAC/
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/providers/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAGzC,2EAA2E;AAC3E,0EAA0E;AAC1E,0EAA0E;AAC1E,kEAAkE;AAClE,MAAM,iBAAiB,GAAwB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAEhE,2EAA2E;AAC3E,+EAA+E;AAC/E,mEAAmE;AACnE,MAAM,mBAAmB,GAAe,CAAC,GAAG,iBAAiB,CAAC,CAAC;AAC/D,MAAM,mBAAmB,GAA6B,MAAM,CAAC,WAAW,CACtE,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CACxC,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAwB,mBAAmB,CAAC;AACtE,MAAM,CAAC,MAAM,SAAS,GAA2C,mBAAmB,CAAC;AAErF,yEAAyE;AACzE,oEAAoE;AACpE,0CAA0C;AAC1C,MAAM,UAAU,YAAY,CAAC,QAA6B;IACxD,wEAAwE;IACxE,mBAAmB,CAAC,MAAM,GAAG,CAAC,CAAC;IAC/B,mBAAmB,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACnD,OAAO,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAClC,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,iBAAiB,EAAE,CAAC;QAClC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,gBAAgB,CAAC,CAAC,EAAE,sEAAsE,CAC3F,CAAC;QACJ,CAAC;QACD,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAC5B,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7B,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC;AAC/E,CAAC;AAED,wEAAwE;AACxE,iEAAiE;AACjE,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAC1E,wEAAwE;AACxE,6EAA6E;AAC7E,4EAA4E;AAC5E,uEAAuE;AACvE,mCAAmC;AACnC,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,KAAK,MAAM,CAAC,IAAI,mBAAmB,EAAE,CAAC;QACpC,MAAM,aAAa,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,aAAa,CAAC,MAAM,KAAK,CAAC,CAAC;QACvE,IAAI,aAAa;YAAE,SAAS;QAC5B,IAAI,CAAC,CAAC,YAAY,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,OAAO,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC,CAAC;AACtE,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createServer } from "node:http";
|
|
|
2
2
|
import { respToResponses } from "./translate/respToResponses.js";
|
|
3
3
|
import { pipeChatStreamToResponses } from "./translate/streamToSse.js";
|
|
4
4
|
import { iterChatStreamChunks } from "./upstream/chatStream.js";
|
|
5
|
-
import { callOpenAICompat, UpstreamError } from "./upstream/openaiCompatClient.js";
|
|
5
|
+
import { callOpenAICompat, callResponsesPassthrough, UpstreamError, } from "./upstream/openaiCompatClient.js";
|
|
6
6
|
import { byClientModel, PROVIDER_LIST, PROVIDERS } from "./providers/registry.js";
|
|
7
7
|
import { makeServerResponseSink } from "./util/sse.js";
|
|
8
8
|
import { log } from "./util/log.js";
|
|
@@ -197,6 +197,7 @@ async function handleResponses(cfg, req, res) {
|
|
|
197
197
|
baseUrl: runtime.baseUrl,
|
|
198
198
|
clientModel: payload.model,
|
|
199
199
|
upstreamModel,
|
|
200
|
+
wireApi: provider.wireApi ?? "chat",
|
|
200
201
|
});
|
|
201
202
|
if (rewriteNotice) {
|
|
202
203
|
log.warn("client model rewritten on the way upstream", {
|
|
@@ -206,6 +207,14 @@ async function handleResponses(cfg, req, res) {
|
|
|
206
207
|
reason: rewriteNotice.reason,
|
|
207
208
|
});
|
|
208
209
|
}
|
|
210
|
+
if (provider.wireApi === "responses") {
|
|
211
|
+
return await handleResponsesPassthrough(cfg, req, res, payload, {
|
|
212
|
+
provider,
|
|
213
|
+
runtime,
|
|
214
|
+
upstreamModel,
|
|
215
|
+
rewriteNotice,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
209
218
|
const chat = provider.preprocessResponses(payload, {
|
|
210
219
|
runtime,
|
|
211
220
|
exposeReasoning: cfg.exposeReasoning,
|
|
@@ -373,6 +382,197 @@ async function handleResponses(cfg, req, res) {
|
|
|
373
382
|
});
|
|
374
383
|
}
|
|
375
384
|
}
|
|
385
|
+
// wireApi === "responses" path: forward Codex's Responses payload directly to
|
|
386
|
+
// the upstream's /v1/responses endpoint, with no Chat-Completions translation.
|
|
387
|
+
// Streaming pipes raw SSE bytes; non-streaming JSON is forwarded verbatim.
|
|
388
|
+
async function handleResponsesPassthrough(cfg, req, res, payload, selected) {
|
|
389
|
+
const { provider, runtime, upstreamModel, rewriteNotice } = selected;
|
|
390
|
+
const stream = !!payload.stream;
|
|
391
|
+
const preprocessed = provider.preprocessResponsesPassthrough?.(payload, {
|
|
392
|
+
runtime,
|
|
393
|
+
exposeReasoning: cfg.exposeReasoning,
|
|
394
|
+
}) ?? payload;
|
|
395
|
+
// The routing layer determined upstreamModel; honor it over whatever the
|
|
396
|
+
// provider hook returned. preprocess hooks shouldn't normally rewrite model.
|
|
397
|
+
const forwardBody = {
|
|
398
|
+
...preprocessed,
|
|
399
|
+
model: upstreamModel,
|
|
400
|
+
stream,
|
|
401
|
+
};
|
|
402
|
+
const ac = new AbortController();
|
|
403
|
+
req.on("close", () => ac.abort());
|
|
404
|
+
const startedAt = Date.now();
|
|
405
|
+
const requestBodySnapshot = bodyForLog(payload);
|
|
406
|
+
const rewriteLogFields = rewriteNotice
|
|
407
|
+
? (() => {
|
|
408
|
+
const w = rewriteWarning(rewriteNotice);
|
|
409
|
+
return { error_code: w.code, error_snippet: w.message };
|
|
410
|
+
})()
|
|
411
|
+
: { error_code: null, error_snippet: null };
|
|
412
|
+
const baseEntry = {
|
|
413
|
+
request_id: null,
|
|
414
|
+
provider_id: provider.id,
|
|
415
|
+
client_model: payload.model,
|
|
416
|
+
upstream_model: upstreamModel,
|
|
417
|
+
endpoint: "/v1/responses",
|
|
418
|
+
stream,
|
|
419
|
+
request_body: requestBodySnapshot,
|
|
420
|
+
};
|
|
421
|
+
// Non-streaming path: parse upstream JSON and forward verbatim.
|
|
422
|
+
if (!stream) {
|
|
423
|
+
try {
|
|
424
|
+
const upstreamRes = await callResponsesPassthrough({
|
|
425
|
+
baseUrl: runtime.baseUrl,
|
|
426
|
+
apiKey: runtime.apiKey,
|
|
427
|
+
userAgent: cfg.userAgent,
|
|
428
|
+
enhanceError: provider.enhanceError.bind(provider),
|
|
429
|
+
}, forwardBody, ac.signal);
|
|
430
|
+
const json = (await upstreamRes.json());
|
|
431
|
+
sendJson(res, 200, json);
|
|
432
|
+
const usage = (json.usage ?? {});
|
|
433
|
+
recordLog(cfg, {
|
|
434
|
+
...baseEntry,
|
|
435
|
+
request_id: typeof json.id === "string" ? json.id : null,
|
|
436
|
+
status_code: 200,
|
|
437
|
+
duration_ms: Date.now() - startedAt,
|
|
438
|
+
prompt_tokens: typeof usage.input_tokens === "number" ? usage.input_tokens : null,
|
|
439
|
+
completion_tokens: typeof usage.output_tokens === "number" ? usage.output_tokens : null,
|
|
440
|
+
total_tokens: typeof usage.total_tokens === "number" ? usage.total_tokens : null,
|
|
441
|
+
...rewriteLogFields,
|
|
442
|
+
response_body: bodyForLog(json),
|
|
443
|
+
tool_call_count: null,
|
|
444
|
+
});
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
catch (err) {
|
|
448
|
+
if (err instanceof UpstreamError) {
|
|
449
|
+
sendJson(res, err.status, errorEnvelope(err.status, err.code, err.message));
|
|
450
|
+
recordLog(cfg, {
|
|
451
|
+
...baseEntry,
|
|
452
|
+
status_code: err.status,
|
|
453
|
+
duration_ms: Date.now() - startedAt,
|
|
454
|
+
prompt_tokens: null,
|
|
455
|
+
completion_tokens: null,
|
|
456
|
+
total_tokens: null,
|
|
457
|
+
error_code: err.code,
|
|
458
|
+
error_snippet: err.bodySnippet ?? err.message,
|
|
459
|
+
response_body: null,
|
|
460
|
+
tool_call_count: null,
|
|
461
|
+
});
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
log.error("responses passthrough non-stream failed", { error: err.message });
|
|
465
|
+
sendJson(res, 500, errorEnvelope(500, "internal_error", err.message));
|
|
466
|
+
recordLog(cfg, {
|
|
467
|
+
...baseEntry,
|
|
468
|
+
status_code: 500,
|
|
469
|
+
duration_ms: Date.now() - startedAt,
|
|
470
|
+
prompt_tokens: null,
|
|
471
|
+
completion_tokens: null,
|
|
472
|
+
total_tokens: null,
|
|
473
|
+
error_code: "internal_error",
|
|
474
|
+
error_snippet: err.message,
|
|
475
|
+
response_body: null,
|
|
476
|
+
tool_call_count: null,
|
|
477
|
+
});
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// Streaming path: pipe upstream SSE bytes directly to the client.
|
|
482
|
+
let upstreamRes;
|
|
483
|
+
try {
|
|
484
|
+
upstreamRes = await callResponsesPassthrough({
|
|
485
|
+
baseUrl: runtime.baseUrl,
|
|
486
|
+
apiKey: runtime.apiKey,
|
|
487
|
+
userAgent: cfg.userAgent,
|
|
488
|
+
enhanceError: provider.enhanceError.bind(provider),
|
|
489
|
+
}, forwardBody, ac.signal);
|
|
490
|
+
}
|
|
491
|
+
catch (err) {
|
|
492
|
+
if (err instanceof UpstreamError) {
|
|
493
|
+
sendJson(res, err.status, errorEnvelope(err.status, err.code, err.message));
|
|
494
|
+
recordLog(cfg, {
|
|
495
|
+
...baseEntry,
|
|
496
|
+
status_code: err.status,
|
|
497
|
+
duration_ms: Date.now() - startedAt,
|
|
498
|
+
prompt_tokens: null,
|
|
499
|
+
completion_tokens: null,
|
|
500
|
+
total_tokens: null,
|
|
501
|
+
error_code: err.code,
|
|
502
|
+
error_snippet: err.bodySnippet ?? err.message,
|
|
503
|
+
response_body: null,
|
|
504
|
+
tool_call_count: null,
|
|
505
|
+
});
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
log.error("responses passthrough stream pre-request failed", {
|
|
509
|
+
error: err.message,
|
|
510
|
+
});
|
|
511
|
+
sendJson(res, 500, errorEnvelope(500, "internal_error", err.message));
|
|
512
|
+
recordLog(cfg, {
|
|
513
|
+
...baseEntry,
|
|
514
|
+
status_code: 500,
|
|
515
|
+
duration_ms: Date.now() - startedAt,
|
|
516
|
+
prompt_tokens: null,
|
|
517
|
+
completion_tokens: null,
|
|
518
|
+
total_tokens: null,
|
|
519
|
+
error_code: "internal_error",
|
|
520
|
+
error_snippet: err.message,
|
|
521
|
+
response_body: null,
|
|
522
|
+
tool_call_count: null,
|
|
523
|
+
});
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
res.statusCode = 200;
|
|
527
|
+
res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
|
|
528
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
529
|
+
res.setHeader("Connection", "keep-alive");
|
|
530
|
+
const keepalive = setInterval(() => {
|
|
531
|
+
if (!res.writableEnded)
|
|
532
|
+
res.write(": keepalive\n\n");
|
|
533
|
+
}, KEEPALIVE_INTERVAL_MS);
|
|
534
|
+
res.on("close", () => clearInterval(keepalive));
|
|
535
|
+
let streamError = null;
|
|
536
|
+
try {
|
|
537
|
+
if (!upstreamRes.body) {
|
|
538
|
+
throw new Error("upstream responded without a body");
|
|
539
|
+
}
|
|
540
|
+
const reader = upstreamRes.body.getReader();
|
|
541
|
+
while (true) {
|
|
542
|
+
const { value, done } = await reader.read();
|
|
543
|
+
if (done)
|
|
544
|
+
break;
|
|
545
|
+
if (value && value.length > 0) {
|
|
546
|
+
res.write(Buffer.from(value));
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (!res.writableEnded)
|
|
550
|
+
res.end();
|
|
551
|
+
}
|
|
552
|
+
catch (err) {
|
|
553
|
+
streamError = err;
|
|
554
|
+
log.error("responses passthrough mid-stream failed", { error: streamError.message });
|
|
555
|
+
if (!res.writableEnded) {
|
|
556
|
+
res.write(`event: error\ndata: ${JSON.stringify({ type: "error", code: "server_error", message: streamError.message, sequence_number: 9999 })}\n\n`);
|
|
557
|
+
res.end();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
finally {
|
|
561
|
+
clearInterval(keepalive);
|
|
562
|
+
recordLog(cfg, {
|
|
563
|
+
...baseEntry,
|
|
564
|
+
status_code: streamError ? 500 : 200,
|
|
565
|
+
duration_ms: Date.now() - startedAt,
|
|
566
|
+
prompt_tokens: null,
|
|
567
|
+
completion_tokens: null,
|
|
568
|
+
total_tokens: null,
|
|
569
|
+
error_code: streamError ? "stream_error" : rewriteLogFields.error_code,
|
|
570
|
+
error_snippet: streamError ? streamError.message : rewriteLogFields.error_snippet,
|
|
571
|
+
response_body: null,
|
|
572
|
+
tool_call_count: null,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
376
576
|
function respondToResponsesProbe(payload, res, stream) {
|
|
377
577
|
const id = `resp_probe_${Date.now()}`;
|
|
378
578
|
const created_at = Math.floor(Date.now() / 1000);
|