@eliotstocker/open-harness-plugin-openai-compatible-auto 0.0.0-development
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/LICENSE +21 -0
- package/README.md +63 -0
- package/cordis.patch.yml +3 -0
- package/dist/core/auth.d.ts +56 -0
- package/dist/core/auth.js +407 -0
- package/dist/core/discovery.d.ts +41 -0
- package/dist/core/discovery.js +222 -0
- package/dist/dsh.d.ts +30 -0
- package/dist/dsh.js +583 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +376 -0
- package/dist/pi.d.ts +8 -0
- package/dist/pi.js +272 -0
- package/package.json +121 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
const TIMEOUT_MS = 15_000;
|
|
2
|
+
const ME_TIMEOUT_MS = 5_000;
|
|
3
|
+
const NON_CHAT_RE = /\b(embed|embedding|whisper|tts|dall-e|moderation|rerank|speech)\b/i;
|
|
4
|
+
/** Strip trailing slashes and optional `/v1` suffix so endpoints resolve consistently. */
|
|
5
|
+
export function rootURL(baseURL) {
|
|
6
|
+
let url = baseURL.trim().replace(/\/+$/, "");
|
|
7
|
+
if (url.endsWith("/v1"))
|
|
8
|
+
url = url.slice(0, -3);
|
|
9
|
+
return url;
|
|
10
|
+
}
|
|
11
|
+
export function v1URL(baseURL) {
|
|
12
|
+
return `${rootURL(baseURL)}/v1`;
|
|
13
|
+
}
|
|
14
|
+
function authHeaders(apiKey, extra) {
|
|
15
|
+
return {
|
|
16
|
+
Accept: "application/json",
|
|
17
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
18
|
+
...extra,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function isRecord(v) {
|
|
22
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
23
|
+
}
|
|
24
|
+
function posInt(v) {
|
|
25
|
+
return typeof v === "number" && Number.isFinite(v) && v > 0 ? Math.floor(v) : undefined;
|
|
26
|
+
}
|
|
27
|
+
function nonNegNum(v) {
|
|
28
|
+
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : undefined;
|
|
29
|
+
}
|
|
30
|
+
function strArray(v) {
|
|
31
|
+
if (!Array.isArray(v))
|
|
32
|
+
return undefined;
|
|
33
|
+
const out = v.filter((x) => typeof x === "string" && x.trim().length > 0).map((x) => x.trim());
|
|
34
|
+
return out.length > 0 ? out : undefined;
|
|
35
|
+
}
|
|
36
|
+
function roundUsd(v) {
|
|
37
|
+
return Math.round(v * 1_000_000) / 1_000_000;
|
|
38
|
+
}
|
|
39
|
+
async function fetchModelInfo(baseURL, headers, signal) {
|
|
40
|
+
const map = new Map();
|
|
41
|
+
try {
|
|
42
|
+
const reqSignal = signal
|
|
43
|
+
? AbortSignal.any([signal, AbortSignal.timeout(TIMEOUT_MS)])
|
|
44
|
+
: AbortSignal.timeout(TIMEOUT_MS);
|
|
45
|
+
const res = await fetch(`${v1URL(baseURL)}/model/info`, {
|
|
46
|
+
headers,
|
|
47
|
+
signal: reqSignal,
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok)
|
|
50
|
+
return map;
|
|
51
|
+
const json = (await res.json());
|
|
52
|
+
if (!Array.isArray(json.data))
|
|
53
|
+
return map;
|
|
54
|
+
for (const item of json.data) {
|
|
55
|
+
if (!isRecord(item) || !isRecord(item.model_info))
|
|
56
|
+
continue;
|
|
57
|
+
const info = item.model_info;
|
|
58
|
+
const params = isRecord(item.litellm_params) ? item.litellm_params : undefined;
|
|
59
|
+
const key = (typeof item.model_name === "string" && item.model_name) ||
|
|
60
|
+
(typeof info.key === "string" && info.key) ||
|
|
61
|
+
(typeof params?.model === "string" ? params.model : undefined);
|
|
62
|
+
if (!key || map.has(key))
|
|
63
|
+
continue;
|
|
64
|
+
const inTok = nonNegNum(info.input_cost_per_token);
|
|
65
|
+
const outTok = nonNegNum(info.output_cost_per_token);
|
|
66
|
+
const cacheReadTok = nonNegNum(info.cache_read_input_token_cost);
|
|
67
|
+
const cacheWriteTok = nonNegNum(info.cache_creation_input_token_cost);
|
|
68
|
+
const cost = inTok !== undefined || outTok !== undefined
|
|
69
|
+
? {
|
|
70
|
+
input: roundUsd((inTok ?? 0) * 1_000_000),
|
|
71
|
+
output: roundUsd((outTok ?? 0) * 1_000_000),
|
|
72
|
+
cacheRead: roundUsd((cacheReadTok ?? 0) * 1_000_000),
|
|
73
|
+
cacheWrite: roundUsd((cacheWriteTok ?? 0) * 1_000_000),
|
|
74
|
+
}
|
|
75
|
+
: undefined;
|
|
76
|
+
map.set(key, {
|
|
77
|
+
displayName: typeof info.display_name === "string" && info.display_name.trim() ? info.display_name.trim() : undefined,
|
|
78
|
+
publisher: typeof info.litellm_provider === "string" && info.litellm_provider.trim() ? info.litellm_provider.trim() : undefined,
|
|
79
|
+
mode: typeof info.mode === "string" ? info.mode : undefined,
|
|
80
|
+
maxInputTokens: posInt(info.max_input_tokens),
|
|
81
|
+
maxOutputTokens: posInt(info.max_output_tokens),
|
|
82
|
+
maxTokens: posInt(info.max_tokens),
|
|
83
|
+
supportsTools: typeof info.supports_function_calling === "boolean"
|
|
84
|
+
? info.supports_function_calling
|
|
85
|
+
: undefined,
|
|
86
|
+
supportsVision: typeof info.supports_vision === "boolean" ? info.supports_vision : undefined,
|
|
87
|
+
supportsAudio: typeof info.supports_audio_input === "boolean" ? info.supports_audio_input : undefined,
|
|
88
|
+
supportsVideo: typeof info.supports_video_input === "boolean" ? info.supports_video_input : undefined,
|
|
89
|
+
supportsPdf: typeof info.supports_pdf_input === "boolean" ? info.supports_pdf_input : undefined,
|
|
90
|
+
supportsPromptCaching: typeof info.supports_prompt_caching === "boolean" ? info.supports_prompt_caching : undefined,
|
|
91
|
+
supportsReasoning: typeof info.supports_reasoning === "boolean" ? info.supports_reasoning : undefined,
|
|
92
|
+
reasoningEfforts: strArray(info.supported_reasoning_efforts),
|
|
93
|
+
cost,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// `/v1/model/info` is LiteLLM/prov-ai-specific; standard OpenAI-compatible endpoints only expose `/v1/models`.
|
|
99
|
+
}
|
|
100
|
+
return map;
|
|
101
|
+
}
|
|
102
|
+
/** Query `/v1/models` (and optional `/v1/model/info`) and return normalized chat model descriptors. */
|
|
103
|
+
export async function fetchDiscoveredModels(baseURL, apiKey, extraHeaders, signal) {
|
|
104
|
+
const headers = authHeaders(apiKey, extraHeaders);
|
|
105
|
+
const reqSignal = signal
|
|
106
|
+
? AbortSignal.any([signal, AbortSignal.timeout(TIMEOUT_MS)])
|
|
107
|
+
: AbortSignal.timeout(TIMEOUT_MS);
|
|
108
|
+
const [modelsRes, infoMap] = await Promise.all([
|
|
109
|
+
fetch(`${v1URL(baseURL)}/models`, {
|
|
110
|
+
headers,
|
|
111
|
+
signal: reqSignal,
|
|
112
|
+
}),
|
|
113
|
+
fetchModelInfo(baseURL, headers, signal),
|
|
114
|
+
]);
|
|
115
|
+
if (!modelsRes.ok) {
|
|
116
|
+
throw new Error(`GET /v1/models failed: HTTP ${modelsRes.status} ${modelsRes.statusText}`);
|
|
117
|
+
}
|
|
118
|
+
const body = (await modelsRes.json());
|
|
119
|
+
const list = Array.isArray(body)
|
|
120
|
+
? body
|
|
121
|
+
: isRecord(body) && Array.isArray(body.data)
|
|
122
|
+
? body.data
|
|
123
|
+
: isRecord(body) && Array.isArray(body.models)
|
|
124
|
+
? body.models
|
|
125
|
+
: null;
|
|
126
|
+
if (!list)
|
|
127
|
+
throw new Error("Unexpected /v1/models response shape");
|
|
128
|
+
const seen = new Set();
|
|
129
|
+
const models = [];
|
|
130
|
+
for (const item of list) {
|
|
131
|
+
if (!isRecord(item))
|
|
132
|
+
continue;
|
|
133
|
+
const id = typeof item.id === "string" ? item.id : typeof item.name === "string" ? item.name : "";
|
|
134
|
+
if (!id || seen.has(id))
|
|
135
|
+
continue;
|
|
136
|
+
const meta = infoMap.get(id);
|
|
137
|
+
const mode = meta?.mode ?? (typeof item.mode === "string" ? item.mode : undefined);
|
|
138
|
+
if (mode && mode !== "chat" && mode !== "completion" && mode !== "responses")
|
|
139
|
+
continue;
|
|
140
|
+
if (!mode && NON_CHAT_RE.test(id))
|
|
141
|
+
continue;
|
|
142
|
+
seen.add(id);
|
|
143
|
+
const contextWindow = meta?.maxInputTokens ?? posInt(item.context_length) ?? posInt(item.max_model_len);
|
|
144
|
+
const maxTokens = meta?.maxOutputTokens ?? meta?.maxTokens ?? posInt(item.max_tokens);
|
|
145
|
+
const name = (typeof item.name === "string" && item.name.trim() ? item.name.trim() : undefined) ??
|
|
146
|
+
meta?.displayName ??
|
|
147
|
+
id;
|
|
148
|
+
const publisher = meta?.publisher ??
|
|
149
|
+
(typeof item.owned_by === "string" && item.owned_by.trim() ? item.owned_by.trim() : undefined);
|
|
150
|
+
const supportsVision = meta?.supportsVision ?? (typeof item.supports_vision === "boolean" ? item.supports_vision : undefined);
|
|
151
|
+
const supportsAudio = meta?.supportsAudio ?? (typeof item.supports_audio_input === "boolean" ? item.supports_audio_input : undefined);
|
|
152
|
+
const supportsVideo = meta?.supportsVideo ?? (typeof item.supports_video_input === "boolean" ? item.supports_video_input : undefined);
|
|
153
|
+
const supportsPdf = meta?.supportsPdf;
|
|
154
|
+
const rawModalities = strArray(item.input_modalities) ?? ["text"];
|
|
155
|
+
const modalitySet = new Set(rawModalities);
|
|
156
|
+
if (supportsVision)
|
|
157
|
+
modalitySet.add("image");
|
|
158
|
+
if (supportsAudio)
|
|
159
|
+
modalitySet.add("audio");
|
|
160
|
+
if (supportsVideo)
|
|
161
|
+
modalitySet.add("video");
|
|
162
|
+
if (supportsPdf)
|
|
163
|
+
modalitySet.add("pdf");
|
|
164
|
+
const reasoningEfforts = meta?.reasoningEfforts ?? strArray(item.supported_reasoning_efforts);
|
|
165
|
+
const reasoning = meta?.supportsReasoning ??
|
|
166
|
+
(typeof item.supports_reasoning === "boolean" ? item.supports_reasoning : undefined) ??
|
|
167
|
+
(reasoningEfforts && reasoningEfforts.length > 0 ? true : undefined);
|
|
168
|
+
models.push({
|
|
169
|
+
id,
|
|
170
|
+
name,
|
|
171
|
+
publisher,
|
|
172
|
+
contextWindow,
|
|
173
|
+
maxTokens,
|
|
174
|
+
reasoning,
|
|
175
|
+
reasoningEfforts,
|
|
176
|
+
supportsTools: meta?.supportsTools,
|
|
177
|
+
supportsVision,
|
|
178
|
+
supportsAudio,
|
|
179
|
+
supportsVideo,
|
|
180
|
+
supportsPdf,
|
|
181
|
+
supportsPromptCaching: meta?.supportsPromptCaching,
|
|
182
|
+
inputModalities: [...modalitySet],
|
|
183
|
+
cost: meta?.cost,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return models;
|
|
187
|
+
}
|
|
188
|
+
/** Optional query for `GET /v1/me` on prov-ai gateways; returns `undefined` cleanly on unsupported endpoints. */
|
|
189
|
+
export async function fetchGatewayUserInfo(baseURL, apiKey, extraHeaders, signal) {
|
|
190
|
+
if (!apiKey)
|
|
191
|
+
return undefined;
|
|
192
|
+
try {
|
|
193
|
+
const reqSignal = signal
|
|
194
|
+
? AbortSignal.any([signal, AbortSignal.timeout(ME_TIMEOUT_MS)])
|
|
195
|
+
: AbortSignal.timeout(ME_TIMEOUT_MS);
|
|
196
|
+
const res = await fetch(`${v1URL(baseURL)}/me`, {
|
|
197
|
+
headers: authHeaders(apiKey, extraHeaders),
|
|
198
|
+
signal: reqSignal,
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok)
|
|
201
|
+
return undefined;
|
|
202
|
+
const json = (await res.json());
|
|
203
|
+
if (!isRecord(json))
|
|
204
|
+
return undefined;
|
|
205
|
+
return {
|
|
206
|
+
email: typeof json.email === "string" && json.email.trim() ? json.email.trim() : undefined,
|
|
207
|
+
gcpBillingLabel: typeof json.gcp_billing_label === "string" && json.gcp_billing_label.trim()
|
|
208
|
+
? json.gcp_billing_label.trim()
|
|
209
|
+
: undefined,
|
|
210
|
+
keyName: typeof json.key_name === "string" && json.key_name.trim() ? json.key_name.trim() : undefined,
|
|
211
|
+
monthlyCapUsd: nonNegNum(json.monthly_cap_usd),
|
|
212
|
+
settledUsd: nonNegNum(json.settled_usd),
|
|
213
|
+
unsettledUsd: nonNegNum(json.unsettled_usd),
|
|
214
|
+
effectiveSpendUsd: nonNegNum(json.effective_spend_usd),
|
|
215
|
+
remainingUsd: nonNegNum(json.remaining_usd),
|
|
216
|
+
allowed: typeof json.allowed === "boolean" ? json.allowed : undefined,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
}
|
package/dist/dsh.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Context } from "@deepseek-ai/cordis";
|
|
2
|
+
import "@deepseek-ai/dsh-authorization";
|
|
3
|
+
import "@deepseek-ai/dsh-settings";
|
|
4
|
+
import z from "@deepseek-ai/schemastery";
|
|
5
|
+
export declare const name = "openai-auto";
|
|
6
|
+
export declare const inject: readonly ["llm"];
|
|
7
|
+
export interface OpenAIAutoDshConfig {
|
|
8
|
+
displayName?: string;
|
|
9
|
+
baseURL?: string;
|
|
10
|
+
apiKeyEnv?: string;
|
|
11
|
+
headers?: Record<string, string>;
|
|
12
|
+
defaultContextWindow?: number;
|
|
13
|
+
defaultMaxTokens?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare const Config: z<Schemastery.ObjectS<{
|
|
16
|
+
displayName: z<string, string>;
|
|
17
|
+
baseURL: z<string, string>;
|
|
18
|
+
apiKeyEnv: z<string, string>;
|
|
19
|
+
headers: z<import("@deepseek-ai/cosmokit").Dict<string, string>, import("@deepseek-ai/cosmokit").Dict<string, string>>;
|
|
20
|
+
defaultContextWindow: z<number, number>;
|
|
21
|
+
defaultMaxTokens: z<number, number>;
|
|
22
|
+
}>, Schemastery.ObjectT<{
|
|
23
|
+
displayName: z<string, string>;
|
|
24
|
+
baseURL: z<string, string>;
|
|
25
|
+
apiKeyEnv: z<string, string>;
|
|
26
|
+
headers: z<import("@deepseek-ai/cosmokit").Dict<string, string>, import("@deepseek-ai/cosmokit").Dict<string, string>>;
|
|
27
|
+
defaultContextWindow: z<number, number>;
|
|
28
|
+
defaultMaxTokens: z<number, number>;
|
|
29
|
+
}>>;
|
|
30
|
+
export declare function apply(ctx: Context, initialConfig?: OpenAIAutoDshConfig): void;
|