@morlay/dsh-llm-openai-compatible 0.0.1

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.
@@ -0,0 +1,89 @@
1
+ import { DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_REQUEST_IMAGE_BYTES, DEFAULT_MAX_TOKENS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, OpenAICompatibleAdapter, OpenAICompatibleAdapterOptions, ReasoningEffort, ResolvedModelProfile, ResolvedProviderProfile } from "./adapter.mjs";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { ModelModality, RetryPolicyConfig } from "@deepseek-ai/dsh-llm";
4
+ import { Context } from "@deepseek-ai/cordis";
5
+ //#region src/index.d.ts
6
+ declare const name = "llm-openai-compatible";
7
+ declare const inject: string[];
8
+ declare const NS: import("@deepseek-ai/dsh-settings").SettingsNamespace;
9
+ /** Selectable reasoning levels a profile or model may declare. */
10
+ declare const REASONING_LEVELS: readonly ["off", "low", "high", "max"];
11
+ /** Accepted model input modalities. */
12
+ declare const MODEL_MODALITIES: readonly ["text", "image"];
13
+ /** Source shape of one model catalog entry. */
14
+ interface ModelProfileSource {
15
+ id: string;
16
+ name?: string;
17
+ description?: string;
18
+ contextWindow?: number;
19
+ maxTokens?: number;
20
+ inputModalities?: ModelModality[];
21
+ reasoningEfforts?: false | Partial<Record<ReasoningEffort, string | null>>;
22
+ }
23
+ /** Source shape of one provider route profile; the `providers` dict key IS the route. */
24
+ interface ProviderProfileSource {
25
+ /** Credential reference (environment-variable name); absence sends no authorization header. */
26
+ apiKeyEnv?: string;
27
+ /** Name shown by configuration surfaces; defaults to the route key. */
28
+ displayName?: string;
29
+ /** Required endpoint base; requests hit `${baseURL}/chat/completions`. */
30
+ baseURL: string;
31
+ /** Extra request headers, merged under the mandatory attribution headers. */
32
+ headers?: Record<string, string>;
33
+ temperature?: number;
34
+ topP?: number;
35
+ topK?: number;
36
+ presencePenalty?: number;
37
+ frequencyPenalty?: number;
38
+ seed?: number;
39
+ /** Deployment default reasoning level; omission keeps the provider default. */
40
+ reasoning?: ReasoningEffort;
41
+ /** This route's model catalog; omission serves an empty catalog (unlisted ids pass through). */
42
+ models?: ModelProfileSource[];
43
+ defaultContextWindow?: number;
44
+ defaultMaxTokens?: number;
45
+ maxRequestImageBytes?: number;
46
+ streamIdleTimeoutMs?: number;
47
+ /** Whole-request deadline in milliseconds; unset arms no overall timer. */
48
+ timeoutMs?: number;
49
+ retryPolicy?: RetryPolicyConfig;
50
+ }
51
+ /** Plugin configuration: the provider routes this instance owns. */
52
+ interface Config {
53
+ /** Provider routes, keyed by route. An empty (or omitted) dict is the dormant posture. */
54
+ providers?: Record<string, ProviderProfileSource>;
55
+ }
56
+ /** Runtime schema for {@link Config}. */
57
+ declare const Config: z<Config>;
58
+ /**
59
+ * The one explicit resolve step from a raw profile to validated connection
60
+ * facts. Programmatic construction may bypass Schemastery normalization, so
61
+ * every default and bound is re-judged here — for the composition entry at
62
+ * load (fail loud) and for each settings snapshot at its first use.
63
+ * @param provider - the route key owning this profile.
64
+ * @param source - raw profile from config or a resolved settings snapshot.
65
+ * @returns validated connection facts plus the credential reference.
66
+ */
67
+ declare function resolveAdapterOptions(provider: string, source: ProviderProfileSource): ResolvedProviderProfile;
68
+ /**
69
+ * Validate profiles and return a detached route-keyed map suitable for
70
+ * per-request reads. This is the one explicit resolve step, so an omitted dict
71
+ * resolves to the empty (dormant) route set here rather than through a hidden
72
+ * fallback.
73
+ * @param providers - configured provider profiles keyed by route.
74
+ * @returns validated profiles in configuration order.
75
+ */
76
+ declare function resolveProfiles(providers: Readonly<Record<string, ProviderProfileSource>> | undefined): Map<string, ResolvedProviderProfile>;
77
+ /**
78
+ * Reject a section this adapter could not serve. Registered as the settings
79
+ * namespace's validator, so an unserviceable profile is refused where it is
80
+ * written instead of being stored and then quietly disabling every route in
81
+ * the namespace.
82
+ * @param config - the resolved section to check.
83
+ */
84
+ declare function assertServiceable(config: Config): void;
85
+ /** Register one generic OpenAI-compatible adapter for all configured provider routes. */
86
+ declare function apply(ctx: Context, config: Config): void;
87
+ //#endregion
88
+ export { Config, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_REQUEST_IMAGE_BYTES, DEFAULT_MAX_TOKENS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, MODEL_MODALITIES, ModelProfileSource, NS, OpenAICompatibleAdapter, type OpenAICompatibleAdapterOptions, ProviderProfileSource, REASONING_LEVELS, type ReasoningEffort, type ResolvedModelProfile, type ResolvedProviderProfile, apply, assertServiceable, inject, name, resolveAdapterOptions, resolveProfiles };
89
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;cAsDa;cACA;cACA,wCAAE;;cAGF;;cAEA;;UAGI;EACf;EACA;EACA;EACA;EACA;EACA,kBAAkB;EAClB,2BAA2B,QAAQ,OAAO;;;UAI3B;;EAEf;;EAEA;;EAEA;;EAEA,UAAU;EAEV;EACA;EACA;EACA;EACA;EACA;;EAEA,YAAY;;EAEZ,SAAS;EACT;EACA;EACA;EACA;;EAEA;EACA,cAAc;;;UAIC;;EAEf,YAAY,eAAe;;;cAuChB,QAAQ,EAAE;;;;;;;;;;iBA8HP,sBACd,kBACA,QAAQ,wBACP;;;;;;;;;iBAgHa,gBACd,WAAW,SAAS,eAAe,sCAClC,YAAY;;;;;;;;iBAmBC,kBAAkB,QAAQ;;iBAoD1B,MAAM,KAAK,SAAS,QAAQ"}
package/lib/index.mjs ADDED
@@ -0,0 +1,293 @@
1
+ import { DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_REQUEST_IMAGE_BYTES, DEFAULT_MAX_TOKENS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, OpenAICompatibleAdapter } from "./adapter.mjs";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { LlmError, RetryPolicySchema, assertUsableApiKey, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
4
+ import { credentialRef } from "@deepseek-ai/dsh-credentials";
5
+ import { launchEnvironmentOf } from "@deepseek-ai/dsh-launch-environment";
6
+ import { deepEqualJson, installSettingsSection, settingsNamespace } from "@deepseek-ai/dsh-settings";
7
+ import { MAX_TIMER_DELAY_MS } from "@deepseek-ai/dsh-timeout";
8
+ import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
9
+ //#region src/index.ts
10
+ const name = "llm-openai-compatible";
11
+ const inject = ["llm"];
12
+ const NS = settingsNamespace("llm-openai-compatible");
13
+ /** Selectable reasoning levels a profile or model may declare. */
14
+ const REASONING_LEVELS = [
15
+ "off",
16
+ "low",
17
+ "high",
18
+ "max"
19
+ ];
20
+ /** Accepted model input modalities. */
21
+ const MODEL_MODALITIES = ["text", "image"];
22
+ const modelSchema = z.object({
23
+ id: z.string().required(),
24
+ name: z.string(),
25
+ description: z.string(),
26
+ contextWindow: z.number().step(1).min(1),
27
+ maxTokens: z.number().step(1).min(1),
28
+ inputModalities: z.array(z.union(MODEL_MODALITIES)).min(1).default(["text"]),
29
+ reasoningEfforts: z.union([z.const(false), z.dict(z.union([z.string(), z.const(null)]))])
30
+ });
31
+ const providerSchema = z.object({
32
+ apiKeyEnv: z.string().role("credential-ref"),
33
+ displayName: z.string(),
34
+ baseURL: z.string().required(),
35
+ headers: z.dict(z.string()),
36
+ temperature: z.number().min(0).max(2),
37
+ topP: z.number().min(0).max(1),
38
+ topK: z.number().step(1).min(1),
39
+ presencePenalty: z.number().min(-2).max(2),
40
+ frequencyPenalty: z.number().min(-2).max(2),
41
+ seed: z.number().step(1).min(1),
42
+ reasoning: z.union(REASONING_LEVELS),
43
+ models: z.array(modelSchema),
44
+ defaultContextWindow: z.number().step(1).min(1).default(DEFAULT_CONTEXT_WINDOW),
45
+ defaultMaxTokens: z.number().step(1).min(1).default(DEFAULT_MAX_TOKENS),
46
+ maxRequestImageBytes: z.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),
47
+ streamIdleTimeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS).default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),
48
+ timeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS),
49
+ retryPolicy: RetryPolicySchema
50
+ });
51
+ /** Runtime schema for {@link Config}. */
52
+ const Config = z.object({ providers: z.dict(providerSchema).default({}) });
53
+ function isReasoningEffort(value) {
54
+ return REASONING_LEVELS.includes(value);
55
+ }
56
+ /** Validate one model's declared reasoning efforts into detached form. */
57
+ function resolveReasoningEfforts(provider, modelId, value) {
58
+ if (value === void 0) return {};
59
+ if (value === false) return { reasoningEfforts: false };
60
+ const declaration = {};
61
+ for (const [effort, wire] of Object.entries(value)) {
62
+ if (!isReasoningEffort(effort)) throw new Error(`llm-openai-compatible: provider "${provider}" model "${modelId}" declares unknown reasoning effort "${effort}"`);
63
+ if (effort === "off") {
64
+ if (wire !== null) throw new Error(`llm-openai-compatible: provider "${provider}" model "${modelId}" reasoning effort "off" must leave an empty wire spelling (null) to omit reasoning_effort`);
65
+ declaration.off = null;
66
+ continue;
67
+ }
68
+ if (wire === null || wire.length === 0) throw new Error(`llm-openai-compatible: provider "${provider}" model "${modelId}" reasoning effort "${effort}" needs a non-empty wire spelling`);
69
+ declaration[effort] = wire;
70
+ }
71
+ return { reasoningEfforts: declaration };
72
+ }
73
+ /** Validate and detach one provider route's model catalog. */
74
+ function resolveModels(provider, models) {
75
+ if (models === void 0) return [];
76
+ const seen = /* @__PURE__ */ new Set();
77
+ return models.map((model) => {
78
+ if (model.id.length === 0) throw new Error(`llm-openai-compatible: provider "${provider}" catalog model ids must be non-empty`);
79
+ if (model.name !== void 0 && model.name.length === 0) throw new Error(`llm-openai-compatible: provider "${provider}" catalog model "${model.id}" has an empty name`);
80
+ if (model.contextWindow !== void 0 && (!Number.isInteger(model.contextWindow) || model.contextWindow <= 0)) throw new Error(`llm-openai-compatible: provider "${provider}" catalog model "${model.id}" contextWindow must be a positive integer`);
81
+ if (model.maxTokens !== void 0 && (!Number.isInteger(model.maxTokens) || model.maxTokens <= 0)) throw new Error(`llm-openai-compatible: provider "${provider}" catalog model "${model.id}" maxTokens must be a positive integer`);
82
+ const inputModalities = model.inputModalities ?? ["text"];
83
+ if (inputModalities.length === 0) throw new Error(`llm-openai-compatible: provider "${provider}" catalog model "${model.id}" inputModalities must not be empty`);
84
+ if (inputModalities.some((modality) => !MODEL_MODALITIES.includes(modality))) throw new Error(`llm-openai-compatible: provider "${provider}" catalog model "${model.id}" inputModalities must contain only "text" and "image"`);
85
+ if (new Set(inputModalities).size !== inputModalities.length) throw new Error(`llm-openai-compatible: provider "${provider}" catalog model "${model.id}" inputModalities must not contain duplicates`);
86
+ if (seen.has(model.id)) throw new Error(`llm-openai-compatible: provider "${provider}" has duplicate catalog model "${model.id}"`);
87
+ seen.add(model.id);
88
+ return {
89
+ id: model.id,
90
+ ...model.name === void 0 ? {} : { name: model.name },
91
+ ...model.description === void 0 ? {} : { description: model.description },
92
+ ...model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow },
93
+ ...model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens },
94
+ inputModalities: [...inputModalities],
95
+ ...resolveReasoningEfforts(provider, model.id, model.reasoningEfforts)
96
+ };
97
+ });
98
+ }
99
+ /** A bounded finite number within `[lo, hi]`, or undefined. */
100
+ function bounded(value, lo, hi) {
101
+ if (value === void 0) return void 0;
102
+ if (!Number.isFinite(value) || value < lo || value > hi) return void 0;
103
+ return value;
104
+ }
105
+ /**
106
+ * The one explicit resolve step from a raw profile to validated connection
107
+ * facts. Programmatic construction may bypass Schemastery normalization, so
108
+ * every default and bound is re-judged here — for the composition entry at
109
+ * load (fail loud) and for each settings snapshot at its first use.
110
+ * @param provider - the route key owning this profile.
111
+ * @param source - raw profile from config or a resolved settings snapshot.
112
+ * @returns validated connection facts plus the credential reference.
113
+ */
114
+ function resolveAdapterOptions(provider, source) {
115
+ if (provider.length === 0) throw new Error("llm-openai-compatible: provider names must be non-empty");
116
+ if (source.baseURL === void 0 || source.baseURL.length === 0) throw new Error(`llm-openai-compatible: provider "${provider}" requires a non-empty baseURL`);
117
+ if (source.displayName !== void 0 && source.displayName.length === 0) throw new Error(`llm-openai-compatible: provider "${provider}" has an empty displayName`);
118
+ const streamIdleTimeoutMs = source.streamIdleTimeoutMs ?? 3e5;
119
+ if (!Number.isFinite(streamIdleTimeoutMs) || streamIdleTimeoutMs <= 0 || streamIdleTimeoutMs > MAX_TIMER_DELAY_MS) throw new Error(`llm-openai-compatible: provider "${provider}" streamIdleTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
120
+ const maxRequestImageBytes = source.maxRequestImageBytes ?? 20971520;
121
+ if (!Number.isSafeInteger(maxRequestImageBytes) || maxRequestImageBytes <= 0) throw new Error(`llm-openai-compatible: provider "${provider}" maxRequestImageBytes must be a positive safe integer`);
122
+ const defaultContextWindow = source.defaultContextWindow ?? 262144;
123
+ if (!Number.isInteger(defaultContextWindow) || defaultContextWindow <= 0) throw new Error(`llm-openai-compatible: provider "${provider}" defaultContextWindow must be a positive integer`);
124
+ const defaultMaxTokens = source.defaultMaxTokens ?? 32768;
125
+ if (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0) throw new Error(`llm-openai-compatible: provider "${provider}" defaultMaxTokens must be a positive safe integer`);
126
+ const timeoutMs = bounded(source.timeoutMs, Number.MIN_VALUE, MAX_TIMER_DELAY_MS);
127
+ if (source.timeoutMs !== void 0 && timeoutMs === void 0) throw new Error(`llm-openai-compatible: provider "${provider}" timeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`);
128
+ if (bounded(source.temperature, 0, 2) === void 0 && source.temperature !== void 0) throw new Error(`llm-openai-compatible: provider "${provider}" temperature must be a finite number within 0..2`);
129
+ if (bounded(source.topP, 0, 1) === void 0 && source.topP !== void 0) throw new Error(`llm-openai-compatible: provider "${provider}" topP must be a finite number within 0..1`);
130
+ if (source.topK !== void 0 && (!Number.isInteger(source.topK) || source.topK <= 0)) throw new Error(`llm-openai-compatible: provider "${provider}" topK must be a positive integer`);
131
+ if (bounded(source.presencePenalty, -2, 2) === void 0 && source.presencePenalty !== void 0) throw new Error(`llm-openai-compatible: provider "${provider}" presencePenalty must be a finite number within -2..2`);
132
+ if (bounded(source.frequencyPenalty, -2, 2) === void 0 && source.frequencyPenalty !== void 0) throw new Error(`llm-openai-compatible: provider "${provider}" frequencyPenalty must be a finite number within -2..2`);
133
+ if (source.seed !== void 0 && (!Number.isInteger(source.seed) || source.seed <= 0)) throw new Error(`llm-openai-compatible: provider "${provider}" seed must be a positive integer`);
134
+ if (source.reasoning !== void 0 && !isReasoningEffort(source.reasoning)) throw new Error(`llm-openai-compatible: provider "${provider}" reasoning must be one of ${REASONING_LEVELS.join(", ")}`);
135
+ return {
136
+ provider,
137
+ displayName: source.displayName ?? provider,
138
+ ...source.apiKeyEnv === void 0 ? {} : { apiKeyEnv: credentialRef(source.apiKeyEnv) },
139
+ baseURL: source.baseURL,
140
+ ...source.headers === void 0 ? {} : { headers: { ...source.headers } },
141
+ ...source.temperature === void 0 ? {} : { temperature: source.temperature },
142
+ ...source.topP === void 0 ? {} : { topP: source.topP },
143
+ ...source.topK === void 0 ? {} : { topK: source.topK },
144
+ ...source.presencePenalty === void 0 ? {} : { presencePenalty: source.presencePenalty },
145
+ ...source.frequencyPenalty === void 0 ? {} : { frequencyPenalty: source.frequencyPenalty },
146
+ ...source.seed === void 0 ? {} : { seed: source.seed },
147
+ ...source.reasoning === void 0 ? {} : { reasoning: source.reasoning },
148
+ models: resolveModels(provider, source.models),
149
+ defaultContextWindow,
150
+ defaultMaxTokens,
151
+ maxRequestImageBytes,
152
+ streamIdleTimeoutMs,
153
+ ...timeoutMs === void 0 ? {} : { timeoutMs },
154
+ retryPolicy: resolveRetryPolicy(source.retryPolicy, `llm-openai-compatible: provider "${provider}" retryPolicy`)
155
+ };
156
+ }
157
+ /**
158
+ * Validate profiles and return a detached route-keyed map suitable for
159
+ * per-request reads. This is the one explicit resolve step, so an omitted dict
160
+ * resolves to the empty (dormant) route set here rather than through a hidden
161
+ * fallback.
162
+ * @param providers - configured provider profiles keyed by route.
163
+ * @returns validated profiles in configuration order.
164
+ */
165
+ function resolveProfiles(providers) {
166
+ if (Array.isArray(providers)) throw new Error("llm-openai-compatible: providers is now a dict keyed by provider route, not an array of profiles");
167
+ const resolved = /* @__PURE__ */ new Map();
168
+ for (const [provider, source] of Object.entries(providers ?? {})) resolved.set(provider, resolveAdapterOptions(provider, source));
169
+ return resolved;
170
+ }
171
+ /**
172
+ * Reject a section this adapter could not serve. Registered as the settings
173
+ * namespace's validator, so an unserviceable profile is refused where it is
174
+ * written instead of being stored and then quietly disabling every route in
175
+ * the namespace.
176
+ * @param config - the resolved section to check.
177
+ */
178
+ function assertServiceable(config) {
179
+ resolveProfiles(config.providers);
180
+ }
181
+ /** The registry captures these per route; a change here must re-register. */
182
+ function registrationFacts(profiles) {
183
+ return [...profiles.entries()].map(([provider, profile]) => ({
184
+ provider,
185
+ displayName: profile.displayName,
186
+ retryPolicy: profile.retryPolicy
187
+ })).sort((left, right) => left.provider.localeCompare(right.provider));
188
+ }
189
+ /**
190
+ * The configurable-provider directory: every route the current profiles
191
+ * declare. A hand-declared route has no catalog entry, so without this it
192
+ * would have no settings address and configuration surfaces could neither
193
+ * show nor edit it. The profile half is unconditional, which keeps a route
194
+ * already stored against a withheld provider editable and deletable.
195
+ */
196
+ function directoryEntries(profiles) {
197
+ const entries = /* @__PURE__ */ new Map();
198
+ for (const [provider, profile] of profiles) entries.set(provider, {
199
+ provider,
200
+ displayName: profile.displayName,
201
+ settingsNs: NS,
202
+ settingsPath: ["providers", provider],
203
+ declared: true
204
+ });
205
+ return [...entries.values()];
206
+ }
207
+ /** Register one generic OpenAI-compatible adapter for all configured provider routes. */
208
+ function apply(ctx, config) {
209
+ let current = () => config;
210
+ let lastRaw;
211
+ let memoized;
212
+ /** The resolved profiles for the current configuration, memoized by raw identity. */
213
+ const profiles = () => {
214
+ const raw = current();
215
+ if (raw === lastRaw && memoized !== void 0) return memoized;
216
+ const next = resolveProfiles(raw.providers);
217
+ lastRaw = raw;
218
+ memoized = next;
219
+ return next;
220
+ };
221
+ profiles();
222
+ const resolveApiKey = async (provider, profile) => {
223
+ const ref = profile.apiKeyEnv;
224
+ if (ref === void 0) return void 0;
225
+ const credentials = ctx.get("credentials");
226
+ if (credentials !== void 0) {
227
+ const hit = await credentials.resolve(ref);
228
+ if (hit !== void 0) return assertUsableApiKey(hit.value, "llm-openai-compatible", ref);
229
+ } else {
230
+ const ambient = launchEnvironmentOf(ctx).get(ref);
231
+ if (ambient !== void 0 && ambient.value.length > 0) return assertUsableApiKey(ambient.value, "llm-openai-compatible", ref);
232
+ }
233
+ throw new LlmError(`llm-openai-compatible: no credential for provider route "${provider}"; its profile resolves ${ref}, which is not set — store ${ref} through the credentials service (the web Models page writes it), or export ${ref} in the launching environment`, "MISSING_CREDENTIAL");
234
+ };
235
+ let userId;
236
+ const resolveUserId = () => userId ??= getOrCreateAnonymousUserId();
237
+ const adapter = new OpenAICompatibleAdapter({
238
+ profiles,
239
+ resolveApiKey,
240
+ resolveUserId,
241
+ resolveAttachments: () => ctx.get("attachments")
242
+ });
243
+ let directory;
244
+ let directoryFacts;
245
+ const ensureDirectory = () => {
246
+ const entries = directoryEntries(profiles());
247
+ if (deepEqualJson(entries, directoryFacts)) return;
248
+ if (directory === void 0) directory = ctx.llm.registerConfigurableProviders(entries);
249
+ else directory.replace(entries);
250
+ directoryFacts = entries;
251
+ };
252
+ ensureDirectory();
253
+ let registration;
254
+ let registeredFacts;
255
+ const ensureRegistrationFacts = () => {
256
+ const facts = registrationFacts(profiles());
257
+ if (deepEqualJson(facts, registeredFacts)) return;
258
+ const routes = [...profiles().keys()];
259
+ if (registration === void 0) {
260
+ if (routes.length === 0) {
261
+ registeredFacts = facts;
262
+ return;
263
+ }
264
+ registration = ctx.llm.registerAdapter(routes, adapter);
265
+ } else registration.replace(routes);
266
+ registeredFacts = facts;
267
+ };
268
+ ensureRegistrationFacts();
269
+ installSettingsSection(ctx, NS, Config, config, {
270
+ validate: assertServiceable,
271
+ setSource: (source) => {
272
+ current = source;
273
+ },
274
+ onChange: () => {
275
+ try {
276
+ ensureRegistrationFacts();
277
+ } catch (error) {
278
+ ctx.logger.error("llm-openai-compatible: keeping the previously registered routes after a refused update");
279
+ ctx.logger.error(error);
280
+ }
281
+ try {
282
+ ensureDirectory();
283
+ } catch (error) {
284
+ ctx.logger.error("llm-openai-compatible: keeping the previous configurable-provider directory after a refused update");
285
+ ctx.logger.error(error);
286
+ }
287
+ }
288
+ });
289
+ }
290
+ //#endregion
291
+ export { Config, DEFAULT_CONTEXT_WINDOW, DEFAULT_MAX_REQUEST_IMAGE_BYTES, DEFAULT_MAX_TOKENS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, MODEL_MODALITIES, NS, OpenAICompatibleAdapter, REASONING_LEVELS, apply, assertServiceable, inject, name, resolveAdapterOptions, resolveProfiles };
292
+
293
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Register a {@link OpenAICompatibleAdapter} for every route in the plugin's\n * `providers` dict on `ctx.llm`. Profile facts resolve per request over the\n * optional `llm-openai-compatible` user-settings section (`ctx.settings`), so\n * a changed base URL, catalog, sampling default, or key reaches the very next\n * request without restarting anything, while an in-flight stream keeps the\n * facts it started with. A changed *route set* (or a route's\n * registration-captured retry policy) re-registers the same adapter instance\n * in place, and the configurable-provider directory tracks the declared\n * routes so configuration surfaces can show and edit each profile.\n * @module @morlay/dsh-llm-openai-compatible\n */\n\nimport type { Context } from \"@deepseek-ai/cordis\";\nimport z from \"@deepseek-ai/schemastery\";\nimport {\n LlmError,\n RetryPolicySchema,\n assertUsableApiKey,\n resolveRetryPolicy,\n} from \"@deepseek-ai/dsh-llm\";\nimport type { ModelModality, RetryPolicyConfig } from \"@deepseek-ai/dsh-llm\";\nimport { credentialRef } from \"@deepseek-ai/dsh-credentials\";\nimport { launchEnvironmentOf } from \"@deepseek-ai/dsh-launch-environment\";\nimport {\n deepEqualJson,\n installSettingsSection,\n settingsNamespace,\n} from \"@deepseek-ai/dsh-settings\";\nimport { MAX_TIMER_DELAY_MS } from \"@deepseek-ai/dsh-timeout\";\nimport { getOrCreateAnonymousUserId } from \"@deepseek-ai/dsh-anonymous-user-id\";\nimport {\n DEFAULT_CONTEXT_WINDOW,\n DEFAULT_MAX_REQUEST_IMAGE_BYTES,\n DEFAULT_MAX_TOKENS,\n DEFAULT_STREAM_IDLE_TIMEOUT_MS,\n OpenAICompatibleAdapter,\n} from \"./adapter.ts\";\nimport type { ReasoningEffort, ResolvedModelProfile, ResolvedProviderProfile } from \"./adapter.ts\";\n\nexport { OpenAICompatibleAdapter } from \"./adapter.ts\";\nexport type {\n OpenAICompatibleAdapterOptions,\n ReasoningEffort,\n ResolvedModelProfile,\n ResolvedProviderProfile,\n} from \"./adapter.ts\";\nexport {\n DEFAULT_CONTEXT_WINDOW,\n DEFAULT_MAX_REQUEST_IMAGE_BYTES,\n DEFAULT_MAX_TOKENS,\n DEFAULT_STREAM_IDLE_TIMEOUT_MS,\n} from \"./adapter.ts\";\n\nexport const name = \"llm-openai-compatible\";\nexport const inject = [\"llm\"];\nexport const NS = settingsNamespace(\"llm-openai-compatible\");\n\n/** Selectable reasoning levels a profile or model may declare. */\nexport const REASONING_LEVELS = [\"off\", \"low\", \"high\", \"max\"] as const;\n/** Accepted model input modalities. */\nexport const MODEL_MODALITIES = [\"text\", \"image\"] as const;\n\n/** Source shape of one model catalog entry. */\nexport interface ModelProfileSource {\n id: string;\n name?: string;\n description?: string;\n contextWindow?: number;\n maxTokens?: number;\n inputModalities?: ModelModality[];\n reasoningEfforts?: false | Partial<Record<ReasoningEffort, string | null>>;\n}\n\n/** Source shape of one provider route profile; the `providers` dict key IS the route. */\nexport interface ProviderProfileSource {\n /** Credential reference (environment-variable name); absence sends no authorization header. */\n apiKeyEnv?: string;\n /** Name shown by configuration surfaces; defaults to the route key. */\n displayName?: string;\n /** Required endpoint base; requests hit `${baseURL}/chat/completions`. */\n baseURL: string;\n /** Extra request headers, merged under the mandatory attribution headers. */\n headers?: Record<string, string>;\n // === sampling defaults (request-level values win) ===\n temperature?: number;\n topP?: number;\n topK?: number;\n presencePenalty?: number;\n frequencyPenalty?: number;\n seed?: number;\n /** Deployment default reasoning level; omission keeps the provider default. */\n reasoning?: ReasoningEffort;\n /** This route's model catalog; omission serves an empty catalog (unlisted ids pass through). */\n models?: ModelProfileSource[];\n defaultContextWindow?: number;\n defaultMaxTokens?: number;\n maxRequestImageBytes?: number;\n streamIdleTimeoutMs?: number;\n /** Whole-request deadline in milliseconds; unset arms no overall timer. */\n timeoutMs?: number;\n retryPolicy?: RetryPolicyConfig;\n}\n\n/** Plugin configuration: the provider routes this instance owns. */\nexport interface Config {\n /** Provider routes, keyed by route. An empty (or omitted) dict is the dormant posture. */\n providers?: Record<string, ProviderProfileSource>;\n}\n\nconst modelSchema = z.object({\n id: z.string().required(),\n name: z.string(),\n description: z.string(),\n contextWindow: z.number().step(1).min(1),\n maxTokens: z.number().step(1).min(1),\n inputModalities: z.array(z.union(MODEL_MODALITIES)).min(1).default([\"text\"]),\n reasoningEfforts: z.union([z.const(false), z.dict(z.union([z.string(), z.const(null)]))]),\n});\n\nconst providerSchema = z.object({\n apiKeyEnv: z.string().role(\"credential-ref\"),\n displayName: z.string(),\n baseURL: z.string().required(),\n headers: z.dict(z.string()),\n temperature: z.number().min(0).max(2),\n topP: z.number().min(0).max(1),\n topK: z.number().step(1).min(1),\n presencePenalty: z.number().min(-2).max(2),\n frequencyPenalty: z.number().min(-2).max(2),\n seed: z.number().step(1).min(1),\n reasoning: z.union(REASONING_LEVELS),\n models: z.array(modelSchema),\n defaultContextWindow: z.number().step(1).min(1).default(DEFAULT_CONTEXT_WINDOW),\n defaultMaxTokens: z.number().step(1).min(1).default(DEFAULT_MAX_TOKENS),\n maxRequestImageBytes: z.number().step(1).min(1).default(DEFAULT_MAX_REQUEST_IMAGE_BYTES),\n streamIdleTimeoutMs: z\n .number()\n .min(Number.MIN_VALUE)\n .max(MAX_TIMER_DELAY_MS)\n .default(DEFAULT_STREAM_IDLE_TIMEOUT_MS),\n timeoutMs: z.number().min(Number.MIN_VALUE).max(MAX_TIMER_DELAY_MS),\n retryPolicy: RetryPolicySchema,\n});\n\n/** Runtime schema for {@link Config}. */\nexport const Config: z<Config> = z.object({\n providers: z.dict(providerSchema).default({}),\n});\n\nfunction isReasoningEffort(value: string): value is ReasoningEffort {\n return (REASONING_LEVELS as readonly string[]).includes(value);\n}\n\n/** Validate one model's declared reasoning efforts into detached form. */\nfunction resolveReasoningEfforts(\n provider: string,\n modelId: string,\n value: ModelProfileSource[\"reasoningEfforts\"],\n): Pick<ResolvedModelProfile, \"reasoningEfforts\"> {\n if (value === void 0) return {};\n if (value === false) return { reasoningEfforts: false };\n const declaration: Partial<Record<ReasoningEffort, string | null>> = {};\n for (const [effort, wire] of Object.entries(value)) {\n if (!isReasoningEffort(effort)) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" model \"${modelId}\" declares unknown reasoning effort \"${effort}\"`,\n );\n }\n if (effort === \"off\") {\n if (wire !== null) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" model \"${modelId}\" reasoning effort \"off\" must leave an empty wire spelling (null) to omit reasoning_effort`,\n );\n }\n declaration.off = null;\n continue;\n }\n if (wire === null || wire.length === 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" model \"${modelId}\" reasoning effort \"${effort}\" needs a non-empty wire spelling`,\n );\n }\n declaration[effort] = wire;\n }\n return { reasoningEfforts: declaration };\n}\n\n/** Validate and detach one provider route's model catalog. */\nfunction resolveModels(\n provider: string,\n models: readonly ModelProfileSource[] | undefined,\n): readonly ResolvedModelProfile[] {\n if (models === void 0) return [];\n const seen = new Set<string>();\n return models.map((model) => {\n if (model.id.length === 0)\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" catalog model ids must be non-empty`,\n );\n if (model.name !== void 0 && model.name.length === 0)\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" catalog model \"${model.id}\" has an empty name`,\n );\n if (\n model.contextWindow !== void 0 &&\n (!Number.isInteger(model.contextWindow) || model.contextWindow <= 0)\n ) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" catalog model \"${model.id}\" contextWindow must be a positive integer`,\n );\n }\n if (\n model.maxTokens !== void 0 &&\n (!Number.isInteger(model.maxTokens) || model.maxTokens <= 0)\n ) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" catalog model \"${model.id}\" maxTokens must be a positive integer`,\n );\n }\n const inputModalities = model.inputModalities ?? [\"text\"];\n if (inputModalities.length === 0)\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" catalog model \"${model.id}\" inputModalities must not be empty`,\n );\n if (\n inputModalities.some(\n (modality) => !(MODEL_MODALITIES as readonly string[]).includes(modality),\n )\n ) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" catalog model \"${model.id}\" inputModalities must contain only \"text\" and \"image\"`,\n );\n }\n if (new Set(inputModalities).size !== inputModalities.length) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" catalog model \"${model.id}\" inputModalities must not contain duplicates`,\n );\n }\n if (seen.has(model.id))\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" has duplicate catalog model \"${model.id}\"`,\n );\n seen.add(model.id);\n return {\n id: model.id,\n ...(model.name === void 0 ? {} : { name: model.name }),\n ...(model.description === void 0 ? {} : { description: model.description }),\n ...(model.contextWindow === void 0 ? {} : { contextWindow: model.contextWindow }),\n ...(model.maxTokens === void 0 ? {} : { maxTokens: model.maxTokens }),\n inputModalities: [...inputModalities],\n ...resolveReasoningEfforts(provider, model.id, model.reasoningEfforts),\n };\n });\n}\n\n/** A bounded finite number within `[lo, hi]`, or undefined. */\nfunction bounded(value: number | undefined, lo: number, hi: number): number | undefined {\n if (value === void 0) return void 0;\n if (!Number.isFinite(value) || value < lo || value > hi) return void 0;\n return value;\n}\n\n/**\n * The one explicit resolve step from a raw profile to validated connection\n * facts. Programmatic construction may bypass Schemastery normalization, so\n * every default and bound is re-judged here — for the composition entry at\n * load (fail loud) and for each settings snapshot at its first use.\n * @param provider - the route key owning this profile.\n * @param source - raw profile from config or a resolved settings snapshot.\n * @returns validated connection facts plus the credential reference.\n */\nexport function resolveAdapterOptions(\n provider: string,\n source: ProviderProfileSource,\n): ResolvedProviderProfile {\n if (provider.length === 0)\n throw new Error(\"llm-openai-compatible: provider names must be non-empty\");\n if (source.baseURL === void 0 || source.baseURL.length === 0) {\n throw new Error(`llm-openai-compatible: provider \"${provider}\" requires a non-empty baseURL`);\n }\n if (source.displayName !== void 0 && source.displayName.length === 0) {\n throw new Error(`llm-openai-compatible: provider \"${provider}\" has an empty displayName`);\n }\n const streamIdleTimeoutMs = source.streamIdleTimeoutMs ?? DEFAULT_STREAM_IDLE_TIMEOUT_MS;\n if (\n !Number.isFinite(streamIdleTimeoutMs) ||\n streamIdleTimeoutMs <= 0 ||\n streamIdleTimeoutMs > MAX_TIMER_DELAY_MS\n ) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" streamIdleTimeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`,\n );\n }\n const maxRequestImageBytes = source.maxRequestImageBytes ?? DEFAULT_MAX_REQUEST_IMAGE_BYTES;\n if (!Number.isSafeInteger(maxRequestImageBytes) || maxRequestImageBytes <= 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" maxRequestImageBytes must be a positive safe integer`,\n );\n }\n const defaultContextWindow = source.defaultContextWindow ?? DEFAULT_CONTEXT_WINDOW;\n if (!Number.isInteger(defaultContextWindow) || defaultContextWindow <= 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" defaultContextWindow must be a positive integer`,\n );\n }\n const defaultMaxTokens = source.defaultMaxTokens ?? DEFAULT_MAX_TOKENS;\n if (!Number.isSafeInteger(defaultMaxTokens) || defaultMaxTokens <= 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" defaultMaxTokens must be a positive safe integer`,\n );\n }\n const timeoutMs = bounded(source.timeoutMs, Number.MIN_VALUE, MAX_TIMER_DELAY_MS);\n if (source.timeoutMs !== void 0 && timeoutMs === void 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" timeoutMs must be a positive finite number no greater than ${MAX_TIMER_DELAY_MS}`,\n );\n }\n if (bounded(source.temperature, 0, 2) === void 0 && source.temperature !== void 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" temperature must be a finite number within 0..2`,\n );\n }\n if (bounded(source.topP, 0, 1) === void 0 && source.topP !== void 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" topP must be a finite number within 0..1`,\n );\n }\n if (source.topK !== void 0 && (!Number.isInteger(source.topK) || source.topK <= 0)) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" topK must be a positive integer`,\n );\n }\n if (bounded(source.presencePenalty, -2, 2) === void 0 && source.presencePenalty !== void 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" presencePenalty must be a finite number within -2..2`,\n );\n }\n if (bounded(source.frequencyPenalty, -2, 2) === void 0 && source.frequencyPenalty !== void 0) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" frequencyPenalty must be a finite number within -2..2`,\n );\n }\n if (source.seed !== void 0 && (!Number.isInteger(source.seed) || source.seed <= 0)) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" seed must be a positive integer`,\n );\n }\n if (source.reasoning !== void 0 && !isReasoningEffort(source.reasoning)) {\n throw new Error(\n `llm-openai-compatible: provider \"${provider}\" reasoning must be one of ${REASONING_LEVELS.join(\", \")}`,\n );\n }\n return {\n provider,\n displayName: source.displayName ?? provider,\n ...(source.apiKeyEnv === void 0 ? {} : { apiKeyEnv: credentialRef(source.apiKeyEnv) }),\n baseURL: source.baseURL,\n ...(source.headers === void 0 ? {} : { headers: { ...source.headers } }),\n ...(source.temperature === void 0 ? {} : { temperature: source.temperature }),\n ...(source.topP === void 0 ? {} : { topP: source.topP }),\n ...(source.topK === void 0 ? {} : { topK: source.topK }),\n ...(source.presencePenalty === void 0 ? {} : { presencePenalty: source.presencePenalty }),\n ...(source.frequencyPenalty === void 0 ? {} : { frequencyPenalty: source.frequencyPenalty }),\n ...(source.seed === void 0 ? {} : { seed: source.seed }),\n ...(source.reasoning === void 0 ? {} : { reasoning: source.reasoning }),\n models: resolveModels(provider, source.models),\n defaultContextWindow,\n defaultMaxTokens,\n maxRequestImageBytes,\n streamIdleTimeoutMs,\n ...(timeoutMs === void 0 ? {} : { timeoutMs }),\n retryPolicy: resolveRetryPolicy(\n source.retryPolicy,\n `llm-openai-compatible: provider \"${provider}\" retryPolicy`,\n ),\n };\n}\n\n/**\n * Validate profiles and return a detached route-keyed map suitable for\n * per-request reads. This is the one explicit resolve step, so an omitted dict\n * resolves to the empty (dormant) route set here rather than through a hidden\n * fallback.\n * @param providers - configured provider profiles keyed by route.\n * @returns validated profiles in configuration order.\n */\nexport function resolveProfiles(\n providers: Readonly<Record<string, ProviderProfileSource>> | undefined,\n): Map<string, ResolvedProviderProfile> {\n if (Array.isArray(providers))\n throw new Error(\n \"llm-openai-compatible: providers is now a dict keyed by provider route, not an array of profiles\",\n );\n const resolved = new Map<string, ResolvedProviderProfile>();\n for (const [provider, source] of Object.entries(providers ?? {})) {\n resolved.set(provider, resolveAdapterOptions(provider, source));\n }\n return resolved;\n}\n\n/**\n * Reject a section this adapter could not serve. Registered as the settings\n * namespace's validator, so an unserviceable profile is refused where it is\n * written instead of being stored and then quietly disabling every route in\n * the namespace.\n * @param config - the resolved section to check.\n */\nexport function assertServiceable(config: Config): void {\n resolveProfiles(config.providers);\n}\n\n/** The registry captures these per route; a change here must re-register. */\nfunction registrationFacts(profiles: ReadonlyMap<string, ResolvedProviderProfile>): unknown[] {\n return [...profiles.entries()]\n .map(([provider, profile]) => ({\n provider,\n displayName: profile.displayName,\n retryPolicy: profile.retryPolicy,\n }))\n .sort((left, right) => left.provider.localeCompare(right.provider));\n}\n\n/**\n * The configurable-provider directory: every route the current profiles\n * declare. A hand-declared route has no catalog entry, so without this it\n * would have no settings address and configuration surfaces could neither\n * show nor edit it. The profile half is unconditional, which keeps a route\n * already stored against a withheld provider editable and deletable.\n */\nfunction directoryEntries(profiles: ReadonlyMap<string, ResolvedProviderProfile>): {\n provider: string;\n displayName: string;\n settingsNs: typeof NS;\n settingsPath: readonly string[];\n declared: boolean;\n}[] {\n const entries = new Map<\n string,\n {\n provider: string;\n displayName: string;\n settingsNs: typeof NS;\n settingsPath: readonly string[];\n declared: boolean;\n }\n >();\n for (const [provider, profile] of profiles) {\n entries.set(provider, {\n provider,\n displayName: profile.displayName,\n settingsNs: NS,\n settingsPath: [\"providers\", provider],\n declared: true,\n });\n }\n return [...entries.values()];\n}\n\n/** Register one generic OpenAI-compatible adapter for all configured provider routes. */\nexport function apply(ctx: Context, config: Config): void {\n let current = () => config;\n let lastRaw: Config | undefined;\n let memoized: Map<string, ResolvedProviderProfile> | undefined;\n /** The resolved profiles for the current configuration, memoized by raw identity. */\n const profiles = (): ReadonlyMap<string, ResolvedProviderProfile> => {\n const raw = current();\n if (raw === lastRaw && memoized !== void 0) return memoized;\n const next = resolveProfiles(raw.providers);\n lastRaw = raw;\n memoized = next;\n return next;\n };\n profiles();\n const resolveApiKey = async (\n provider: string,\n profile: ResolvedProviderProfile,\n ): Promise<string | undefined> => {\n const ref = profile.apiKeyEnv;\n if (ref === void 0) return void 0;\n const credentials = ctx.get(\"credentials\");\n if (credentials !== void 0) {\n const hit = await credentials.resolve(ref);\n if (hit !== void 0) return assertUsableApiKey(hit.value, \"llm-openai-compatible\", ref);\n } else {\n const ambient = launchEnvironmentOf(ctx).get(ref);\n if (ambient !== void 0 && ambient.value.length > 0)\n return assertUsableApiKey(ambient.value, \"llm-openai-compatible\", ref);\n }\n throw new LlmError(\n `llm-openai-compatible: no credential for provider route \"${provider}\"; its profile resolves ${ref}, which is not set — store ${ref} through the credentials service (the web Models page writes it), or export ${ref} in the launching environment`,\n \"MISSING_CREDENTIAL\",\n );\n };\n let userId: string | undefined;\n const resolveUserId = () => (userId ??= getOrCreateAnonymousUserId());\n const adapter = new OpenAICompatibleAdapter({\n profiles,\n resolveApiKey,\n resolveUserId,\n resolveAttachments: () => ctx.get(\"attachments\"),\n });\n let directory: ReturnType<typeof ctx.llm.registerConfigurableProviders> | undefined;\n let directoryFacts: unknown[] | undefined;\n const ensureDirectory = () => {\n const entries = directoryEntries(profiles());\n if (deepEqualJson(entries, directoryFacts)) return;\n if (directory === void 0) directory = ctx.llm.registerConfigurableProviders(entries);\n else directory.replace(entries);\n directoryFacts = entries;\n };\n ensureDirectory();\n let registration: ReturnType<typeof ctx.llm.registerAdapter> | undefined;\n let registeredFacts: unknown[] | undefined;\n const ensureRegistrationFacts = () => {\n const facts = registrationFacts(profiles());\n if (deepEqualJson(facts, registeredFacts)) return;\n const routes = [...profiles().keys()];\n if (registration === void 0) {\n if (routes.length === 0) {\n registeredFacts = facts;\n return;\n }\n registration = ctx.llm.registerAdapter(routes, adapter);\n } else {\n registration.replace(routes);\n }\n registeredFacts = facts;\n };\n ensureRegistrationFacts();\n installSettingsSection(ctx, NS, Config, config, {\n validate: assertServiceable,\n setSource: (source) => {\n current = source;\n },\n onChange: () => {\n try {\n ensureRegistrationFacts();\n } catch (error) {\n ctx.logger.error(\n \"llm-openai-compatible: keeping the previously registered routes after a refused update\",\n );\n ctx.logger.error(error);\n }\n try {\n ensureDirectory();\n } catch (error) {\n ctx.logger.error(\n \"llm-openai-compatible: keeping the previous configurable-provider directory after a refused update\",\n );\n ctx.logger.error(error);\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import { ResolvedModelProfile, ResolvedProviderProfile } from "./adapter.mjs";
2
+ import { GenerateOptions } from "@deepseek-ai/dsh-llm";
3
+ import { LanguageModelV4FunctionTool, LanguageModelV4Prompt, SharedV4ProviderOptions } from "@ai-sdk/provider";
4
+ import { AttachmentStore } from "@deepseek-ai/dsh-attachment";
5
+ //#region src/serialize.d.ts
6
+ /** Provider-specific options the adapter forwards into the request body. */
7
+ type OpenAICompatibleProviderOptions = SharedV4ProviderOptions & {
8
+ "openai-compatible"?: {
9
+ /** Exact wire `reasoning_effort` spelling; absence omits the field. */
10
+ reasoningEffort?: string;
11
+ /** Non-standard `top_k` sampling knob, sent only to gateways that accept it. */
12
+ top_k?: number;
13
+ };
14
+ };
15
+ /** The per-call options resolved from one harness request and provider profile. */
16
+ interface OpenAICompatibleCallOptions {
17
+ prompt: LanguageModelV4Prompt;
18
+ maxOutputTokens?: number;
19
+ temperature?: number;
20
+ topP?: number;
21
+ presencePenalty?: number;
22
+ frequencyPenalty?: number;
23
+ seed?: number;
24
+ stopSequences?: string[];
25
+ tools?: LanguageModelV4FunctionTool[];
26
+ providerOptions?: OpenAICompatibleProviderOptions;
27
+ }
28
+ /**
29
+ * Resolve one reasoning effort to its wire spelling for the exact model.
30
+ * `off` (and a `null` wire spelling) means *omit the field* — the provider
31
+ * default applies; every other declared effort sends its configured value.
32
+ * An effort the model does not declare fails here, before any network I/O:
33
+ * that is where a bad request-level effort AND a bad profile default both
34
+ * belong (describing a model must never throw, but executing a request must).
35
+ * @param model - the configured model descriptor, or `undefined` for an
36
+ * unlisted model id (which carries no reasoning declaration).
37
+ * @param effort - the resolved effort to send, or `undefined` to send none.
38
+ * @returns the wire `reasoning_effort` value, or `undefined` to omit the field.
39
+ * @throws LlmError `UNSUPPORTED_REASONING_EFFORT` when the model does not
40
+ * declare the effort.
41
+ */
42
+ declare function resolveReasoningWire(model: ResolvedModelProfile | undefined, effort: ResolvedProviderProfile["reasoning"] | undefined): string | undefined;
43
+ /**
44
+ * Build the full call options for text-only content.
45
+ * @param options - the harness request.
46
+ * @param profile - resolved provider profile.
47
+ * @param model - configured model descriptor, or `undefined` for unlisted ids.
48
+ * @returns the AI SDK call options (settings + prompt + provider options).
49
+ */
50
+ declare function serializeCallOptions(options: GenerateOptions, profile: ResolvedProviderProfile, model: ResolvedModelProfile | undefined): Promise<OpenAICompatibleCallOptions>;
51
+ /**
52
+ * Build one image-capable request while keeping durable bytes out of session
53
+ * messages. Oversized oldest images become deterministic text before any
54
+ * attachment read.
55
+ * @param options - the harness request containing image-capable user content.
56
+ * @param profile - resolved provider profile.
57
+ * @param model - configured model descriptor, or `undefined` for unlisted ids.
58
+ * @param images - the attachment resolver, request bound, and cancellation.
59
+ * @returns the fully materialized call options.
60
+ */
61
+ declare function serializeCallOptionsWithImages(options: GenerateOptions, profile: ResolvedProviderProfile, model: ResolvedModelProfile | undefined, images: {
62
+ attachments: AttachmentStore;
63
+ maxRequestImageBytes: number;
64
+ signal?: AbortSignal;
65
+ }): Promise<OpenAICompatibleCallOptions>;
66
+ //#endregion
67
+ export { OpenAICompatibleCallOptions, OpenAICompatibleProviderOptions, resolveReasoningWire, serializeCallOptions, serializeCallOptionsWithImages };
68
+ //# sourceMappingURL=serialize.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serialize.d.mts","names":[],"sources":["../src/serialize.ts"],"mappings":";;;;;;KA+BY,kCAAkC;EAC5C;;IAEE;;IAEA;;;;UAKa;EACf,QAAQ;EACR;EACA;EACA;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR,kBAAkB;;;;;;;;;;;;;;;;iBAiBJ,qBACd,OAAO,kCACP,QAAQ;;;;;;;;iBAiSY,qBACpB,SAAS,iBACT,SAAS,yBACT,OAAO,mCACN,QAAQ;;;;;;;;;;;iBAiBW,+BACpB,SAAS,iBACT,SAAS,yBACT,OAAO,kCACP;EAAU,aAAa;EAAiB;EAA8B,SAAS;IAC9E,QAAQ"}