dsh-provider-qoder 0.3.2 → 0.4.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.
- package/README.md +1 -3
- package/README_CN.md +1 -2
- package/lib/client.js +708 -260
- package/lib/index.d.ts +9 -9
- package/lib/index.js +238 -64
- package/package.json +32 -33
package/lib/index.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ interface QoderCatalogModel {
|
|
|
12
12
|
contextWindow?: number;
|
|
13
13
|
/** Largest known capacity, independent of the effective input budget. */
|
|
14
14
|
maxContextWindow?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Provider-owned key of the context tier the subscriber selected. Absent means
|
|
17
|
+
* the provider default tier decides the effective input budget.
|
|
18
|
+
*/
|
|
19
|
+
contextTier?: string;
|
|
15
20
|
maxTokens?: number;
|
|
16
21
|
source?: string;
|
|
17
22
|
isReasoning?: boolean;
|
|
@@ -23,6 +28,8 @@ interface QoderCatalogModel {
|
|
|
23
28
|
}>;
|
|
24
29
|
defaultReasoningEffort?: string;
|
|
25
30
|
priceFactor?: number;
|
|
31
|
+
originalPriceFactor?: number;
|
|
32
|
+
isFree?: boolean;
|
|
26
33
|
contextOptions?: Record<string, {
|
|
27
34
|
tokenCount?: number;
|
|
28
35
|
isDefault?: boolean;
|
|
@@ -43,8 +50,6 @@ type QoderWebSearchMode = 'auto' | 'always' | 'disabled';
|
|
|
43
50
|
interface Config {
|
|
44
51
|
region?: QoderRegion;
|
|
45
52
|
modelsByRegion?: QoderModelsByRegion;
|
|
46
|
-
/** @deprecated Migrated to modelsByRegion for the selected region. */
|
|
47
|
-
models?: QoderCatalogModel[];
|
|
48
53
|
streamIdleTimeoutMs?: number;
|
|
49
54
|
responseHeaderTimeoutMs?: number;
|
|
50
55
|
preserveThinking?: boolean;
|
|
@@ -52,7 +57,7 @@ interface Config {
|
|
|
52
57
|
}
|
|
53
58
|
declare const Config: z<Config>;
|
|
54
59
|
type PluginConfig = Config;
|
|
55
|
-
declare const PluginConfig: z<NoInfer<Config>, NoInfer<Config>, "volatile">;
|
|
60
|
+
declare const PluginConfig: z<Config> | z<NoInfer<Config>, NoInfer<Config>, "volatile">;
|
|
56
61
|
type LiveConfig = ReturnType<typeof PluginConfig>;
|
|
57
62
|
//#endregion
|
|
58
63
|
//#region src/dsh/plugin.d.ts
|
|
@@ -158,11 +163,6 @@ interface QoderLogger {
|
|
|
158
163
|
error?(message: string, ...details: unknown[]): void;
|
|
159
164
|
}
|
|
160
165
|
//#endregion
|
|
161
|
-
//#region src/qoder/transport/endpoints.d.ts
|
|
162
|
-
/** Path of the center web search route. */
|
|
163
|
-
declare const qoderWebSearchPath = "/api/v1/webSearch/oneSearch";
|
|
164
|
-
declare function getQoderWebSearchUrl(region?: QoderRegion): string;
|
|
165
|
-
//#endregion
|
|
166
166
|
//#region src/qoder/transport/wire/cosy.d.ts
|
|
167
167
|
interface CosyCredentials {
|
|
168
168
|
userID: string;
|
|
@@ -228,4 +228,4 @@ declare class QoderSearchProvider implements WebSearchProvider {
|
|
|
228
228
|
private resolveFallback;
|
|
229
229
|
}
|
|
230
230
|
//#endregion
|
|
231
|
-
export { PluginConfig as Config, QODER_SEARCH_PROVIDER_ID, type QoderAccountInfo, type QoderCatalogModel, type QoderModelsByRegion, type QoderRegion,
|
|
231
|
+
export { PluginConfig as Config, QODER_SEARCH_PROVIDER_ID, type QoderAccountInfo, type QoderCatalogModel, type QoderModelsByRegion, type QoderRegion, QoderSearchProvider, type QoderSearchProviderOptions, type QoderWebSearchMode, apply, inject, name };
|
package/lib/index.js
CHANGED
|
@@ -16,6 +16,8 @@ const discoveredMetadataKeys = [
|
|
|
16
16
|
"reasoningEfforts",
|
|
17
17
|
"defaultReasoningEffort",
|
|
18
18
|
"priceFactor",
|
|
19
|
+
"originalPriceFactor",
|
|
20
|
+
"isFree",
|
|
19
21
|
"contextOptions",
|
|
20
22
|
"maxContextWindow",
|
|
21
23
|
"supportsImages"
|
|
@@ -94,7 +96,11 @@ function normalizeQoderModels(payload, onConflict) {
|
|
|
94
96
|
const contextWindow = (defaultOptions.length === 1 ? defaultOptions[0].tokenCount : void 0) ?? positiveNumber(raw.max_input_tokens) ?? 18e4;
|
|
95
97
|
const maxContextWindow = Math.max(positiveNumber(raw.max_input_tokens) ?? 0, contextWindow, ...Object.values(contextOptions ?? {}).map((option) => option.tokenCount ?? 0));
|
|
96
98
|
const isReasoning = thinkingDefault(raw.thinking_config, raw.is_reasoning === true, onConflict);
|
|
97
|
-
const priceFactor = typeof raw.price_factor === "number" && Number.isFinite(raw.price_factor) && raw.price_factor >= 0 ? raw.price_factor : void 0;
|
|
99
|
+
const priceFactor = typeof raw.price_factor === "number" && Number.isFinite(raw.price_factor) && raw.price_factor >= 0 ? raw.price_factor : typeof raw.priceFactor === "number" && Number.isFinite(raw.priceFactor) && raw.priceFactor >= 0 ? raw.priceFactor : void 0;
|
|
100
|
+
const rawOriginalPrice = typeof raw.original_price_factor === "number" && Number.isFinite(raw.original_price_factor) && raw.original_price_factor >= 0 ? raw.original_price_factor : typeof raw.originPriceFactor === "number" && Number.isFinite(raw.originPriceFactor) && raw.originPriceFactor >= 0 ? raw.originPriceFactor : void 0;
|
|
101
|
+
const promotionPrice = typeof raw.promotion === "object" && raw.promotion !== null && raw.promotion.active === true && typeof raw.promotion.before_promotion_price_factor === "number" ? raw.promotion.before_promotion_price_factor : void 0;
|
|
102
|
+
const originalPriceFactor = rawOriginalPrice ?? promotionPrice;
|
|
103
|
+
const isFree = raw.is_free === true || raw.isFree === true || Array.isArray(raw.tags) && raw.tags.includes("limited_time_free") || priceFactor === 0;
|
|
98
104
|
const reasoning = reasoningEffortsOf(raw.thinking_config);
|
|
99
105
|
models.push({
|
|
100
106
|
id,
|
|
@@ -109,20 +115,99 @@ function normalizeQoderModels(payload, onConflict) {
|
|
|
109
115
|
...reasoning.efforts === void 0 ? {} : { reasoningEfforts: reasoning.efforts },
|
|
110
116
|
...reasoning.defaultEffort === void 0 ? {} : { defaultReasoningEffort: reasoning.defaultEffort },
|
|
111
117
|
...priceFactor === void 0 ? {} : { priceFactor },
|
|
118
|
+
...originalPriceFactor === void 0 ? {} : { originalPriceFactor },
|
|
119
|
+
...isFree ? { isFree: true } : {},
|
|
112
120
|
...contextOptions === void 0 ? {} : { contextOptions }
|
|
113
121
|
});
|
|
114
122
|
}
|
|
115
123
|
return models;
|
|
116
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Provider-advertised context tiers of one model, smallest first. A tier without
|
|
127
|
+
* a usable capacity cannot be selected and is dropped.
|
|
128
|
+
*/
|
|
129
|
+
function contextTiersOf(model) {
|
|
130
|
+
const options = model.contextOptions;
|
|
131
|
+
if (options === void 0) return [];
|
|
132
|
+
return Object.entries(options).flatMap(([key, value]) => {
|
|
133
|
+
const tokenCount = value.tokenCount;
|
|
134
|
+
return typeof tokenCount === "number" && Number.isFinite(tokenCount) && tokenCount > 0 ? [{
|
|
135
|
+
key,
|
|
136
|
+
tokenCount,
|
|
137
|
+
isDefault: value.isDefault === true
|
|
138
|
+
}] : [];
|
|
139
|
+
}).sort((left, right) => left.tokenCount - right.tokenCount);
|
|
140
|
+
}
|
|
141
|
+
/** Resolve a usable selection consistently for the composer and adapter. */
|
|
142
|
+
function resolveContextTier(model, manualTier, historicalWindow) {
|
|
143
|
+
const tiers = contextTiersOf(model);
|
|
144
|
+
return tiers.find((tier) => tier.key === manualTier) ?? tiers.find((tier) => tier.tokenCount === historicalWindow) ?? tiers.find((tier) => tier.key === model.contextTier) ?? tiers.find((tier) => tier.tokenCount === model.contextWindow) ?? (model.contextWindow === void 0 ? tiers.find((tier) => tier.isDefault) : void 0);
|
|
145
|
+
}
|
|
146
|
+
/** Format a price factor number to string (e.g. 1 -> '1', 1.6 -> '1.6', 0.5 -> '0.5', 1.25 -> '1.25'). */
|
|
147
|
+
function formatRateFactor(value) {
|
|
148
|
+
const rounded = Number(value.toFixed(2));
|
|
149
|
+
return String(rounded);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Format model price factor / rate string.
|
|
153
|
+
* - Free models or price factor 0: '免费' or 'Free'
|
|
154
|
+
* - Promotional models: '0.5x(原 1x)' or '0.5x (was 1x)'
|
|
155
|
+
* - Standard models: '1x'
|
|
156
|
+
*/
|
|
157
|
+
function formatModelRate(model, isZh = true) {
|
|
158
|
+
if (model.isFree || model.priceFactor === 0) return isZh ? "免费" : "Free";
|
|
159
|
+
if (model.priceFactor === void 0) return void 0;
|
|
160
|
+
const current = `${formatRateFactor(model.priceFactor)}x`;
|
|
161
|
+
if (model.originalPriceFactor !== void 0 && model.originalPriceFactor > model.priceFactor) {
|
|
162
|
+
const orig = `${formatRateFactor(model.originalPriceFactor)}x`;
|
|
163
|
+
return isZh ? `${current}(原 ${orig})` : `${current} (was ${orig})`;
|
|
164
|
+
}
|
|
165
|
+
return current;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* The provider-advertised context tier the subscriber selected, when the
|
|
169
|
+
* provider still advertises that tier with a usable capacity.
|
|
170
|
+
*/
|
|
171
|
+
function selectedContextTier(model) {
|
|
172
|
+
const key = model.contextTier;
|
|
173
|
+
if (typeof key !== "string" || key.length === 0) return void 0;
|
|
174
|
+
const tokenCount = positiveNumber(model.contextOptions?.[key]?.tokenCount);
|
|
175
|
+
return tokenCount === void 0 ? void 0 : {
|
|
176
|
+
key,
|
|
177
|
+
tokenCount
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Effective input budget carried to DSH for one catalog entry.
|
|
182
|
+
*
|
|
183
|
+
* A selected tier is an explicit subscriber decision, so it outranks the
|
|
184
|
+
* provider default tier and may widen the budget beyond it; without a selection
|
|
185
|
+
* the entry keeps its stored budget.
|
|
186
|
+
*/
|
|
187
|
+
function effectiveContextWindow(model) {
|
|
188
|
+
return selectedContextTier(model)?.tokenCount ?? model.contextWindow;
|
|
189
|
+
}
|
|
117
190
|
function mergeQoderDiscoveryMetadata(configured, discovered) {
|
|
118
191
|
const catalog = new Map(discovered.map((model) => [model.id, model]));
|
|
119
192
|
return configured.map((model) => {
|
|
120
193
|
const advertised = catalog.get(model.id);
|
|
121
|
-
if (advertised === void 0) return {
|
|
194
|
+
if (advertised === void 0) return {
|
|
195
|
+
...model,
|
|
196
|
+
...model.contextOptions !== void 0 ? { contextOptions: { ...model.contextOptions } } : {}
|
|
197
|
+
};
|
|
122
198
|
const merged = { ...model };
|
|
123
|
-
if (advertised.contextWindow !== void 0) merged.contextWindow = Math.min(model.contextWindow ?? advertised.contextWindow, advertised.contextWindow);
|
|
124
199
|
for (const key of discoveredMetadataKeys) delete merged[key];
|
|
125
|
-
for (const key of discoveredMetadataKeys) if (advertised[key] !== void 0)
|
|
200
|
+
for (const key of discoveredMetadataKeys) if (advertised[key] !== void 0) {
|
|
201
|
+
const val = advertised[key];
|
|
202
|
+
Object.assign(merged, { [key]: key === "contextOptions" && typeof val === "object" && val !== null ? { ...val } : val });
|
|
203
|
+
}
|
|
204
|
+
const tier = selectedContextTier(merged);
|
|
205
|
+
if (tier !== void 0) {
|
|
206
|
+
merged.contextWindow = tier.tokenCount;
|
|
207
|
+
return merged;
|
|
208
|
+
}
|
|
209
|
+
delete merged.contextTier;
|
|
210
|
+
if (advertised.contextWindow !== void 0) merged.contextWindow = Math.min(model.contextWindow ?? advertised.contextWindow, advertised.contextWindow);
|
|
126
211
|
return merged;
|
|
127
212
|
});
|
|
128
213
|
}
|
|
@@ -198,8 +283,6 @@ const qoderRegionEndpoints = {
|
|
|
198
283
|
};
|
|
199
284
|
/** Signed path of the center image upload route; it never carries an `/algo` prefix. */
|
|
200
285
|
const qoderImageUploadPath = "/api/v2/image/upload";
|
|
201
|
-
qoderRegionEndpoints.global.baseUrl;
|
|
202
|
-
qoderRegionEndpoints.global.openApiUrl;
|
|
203
286
|
function resolveQoderEndpoints(region = "global") {
|
|
204
287
|
return qoderRegionEndpoints[region] ?? qoderRegionEndpoints.global;
|
|
205
288
|
}
|
|
@@ -830,8 +913,7 @@ async function resolveImagePart(block, context) {
|
|
|
830
913
|
const target = {
|
|
831
914
|
width: Math.max(1, Math.floor(width * scale)),
|
|
832
915
|
height: Math.max(1, Math.floor(height * scale)),
|
|
833
|
-
maxBytes: limits.maxImageBytes
|
|
834
|
-
maxPixels: limits.maxImagePixels
|
|
916
|
+
maxBytes: limits.maxImageBytes
|
|
835
917
|
};
|
|
836
918
|
image = await attachments.readImageRequest(block.attachment, target, signal);
|
|
837
919
|
} catch (error) {
|
|
@@ -1001,11 +1083,9 @@ async function validateAndTranslateMessages(messages, systemPrompt, attachments,
|
|
|
1001
1083
|
}
|
|
1002
1084
|
if (toolResults.length > 0 && !hasImage && text.trim().length === 0) continue;
|
|
1003
1085
|
const wireRole = toolResults.length > 0 ? "user" : message.role;
|
|
1004
|
-
const wireToolCallId = wireRole === "tool" ? consumeToolCallId(message, void 0, lastAssistantToolCallIds, output.length) : void 0;
|
|
1005
1086
|
output.push({
|
|
1006
1087
|
role: wireRole,
|
|
1007
|
-
content: hasImage ? userContent.filter((part) => part !== void 0) : text
|
|
1008
|
-
...wireToolCallId ? { tool_call_id: wireToolCallId } : {}
|
|
1088
|
+
content: hasImage ? userContent.filter((part) => part !== void 0) : text
|
|
1009
1089
|
});
|
|
1010
1090
|
}
|
|
1011
1091
|
return output;
|
|
@@ -1346,11 +1426,13 @@ async function buildQoderRequestBody(options, userId, translatedMessages, model,
|
|
|
1346
1426
|
const maxTokens = Math.min(options.maxTokens ?? modelMaxTokens, modelMaxTokens);
|
|
1347
1427
|
const isReasoning = options.reasoningEffort !== void 0 || (model?.isReasoning ?? false);
|
|
1348
1428
|
const tools = translateTools(options.tools);
|
|
1429
|
+
const selectedTier = model === void 0 ? void 0 : selectedContextTier(model);
|
|
1349
1430
|
const defaultContexts = Object.values(model?.contextOptions ?? {}).filter((option) => option.isDefault === true && typeof option.tokenCount === "number" && Number.isFinite(option.tokenCount) && option.tokenCount > 0);
|
|
1350
|
-
const contextConfig = model?.contextOptions === void 0 || defaultContexts.length !== 1 ? void 0 : Object.fromEntries(Object.entries(model.contextOptions).map(([key, value]) => [key, {
|
|
1431
|
+
const contextConfig = model?.contextOptions === void 0 || selectedTier === void 0 && defaultContexts.length !== 1 ? void 0 : Object.fromEntries(Object.entries(model.contextOptions).map(([key, value]) => [key, {
|
|
1351
1432
|
...value.tokenCount === void 0 ? {} : { token_count: value.tokenCount },
|
|
1352
|
-
...value.isDefault === void 0 ? {} : { is_default: value.isDefault }
|
|
1433
|
+
...selectedTier === void 0 ? value.isDefault === void 0 ? {} : { is_default: value.isDefault } : { is_default: key === selectedTier.key }
|
|
1353
1434
|
}]));
|
|
1435
|
+
const effectiveBudget = selectedTier?.tokenCount ?? model?.contextWindow;
|
|
1354
1436
|
let lastUserText = "";
|
|
1355
1437
|
for (let index = messages.length - 1; index >= 0; index--) if (messages[index].role === "user") {
|
|
1356
1438
|
lastUserText = wireMessageText(messages[index]);
|
|
@@ -1384,6 +1466,7 @@ async function buildQoderRequestBody(options, userId, translatedMessages, model,
|
|
|
1384
1466
|
tools,
|
|
1385
1467
|
parameters: {
|
|
1386
1468
|
max_tokens: maxTokens,
|
|
1469
|
+
...effectiveBudget === void 0 ? {} : { context_length: effectiveBudget },
|
|
1387
1470
|
...options.reasoningEffort === void 0 ? {} : { reasoning_effort: String(options.reasoningEffort) }
|
|
1388
1471
|
},
|
|
1389
1472
|
chat_context: {
|
|
@@ -2067,6 +2150,18 @@ function qoderEncodeBody(plaintext) {
|
|
|
2067
2150
|
}
|
|
2068
2151
|
return out;
|
|
2069
2152
|
}
|
|
2153
|
+
//#endregion
|
|
2154
|
+
//#region src/qoder/transport/search.ts
|
|
2155
|
+
/**
|
|
2156
|
+
* Web search transport client communicating with the Qoder center service.
|
|
2157
|
+
*
|
|
2158
|
+
* Dispatches query requests to the center-hosted `oneSearch` route using the
|
|
2159
|
+
* subscriber's COSY credentials, handling payload serialization, signing,
|
|
2160
|
+
* 401/403 credential refresh retry, and mapping to DSH WebSearchResult.
|
|
2161
|
+
*
|
|
2162
|
+
* @module dsh-provider-qoder/qoder/transport/search
|
|
2163
|
+
*/
|
|
2164
|
+
const defaultSearchTimeoutMs = 3e4;
|
|
2070
2165
|
function searchAborted(signal, fallback) {
|
|
2071
2166
|
return new WebError("Web search was cancelled.", "WEB_ABORTED", { cause: signal?.aborted === true ? signal.reason : fallback });
|
|
2072
2167
|
}
|
|
@@ -2085,7 +2180,7 @@ var QoderSearchClient = class {
|
|
|
2085
2180
|
this.fetchImpl = options.fetch ?? globalThis.fetch;
|
|
2086
2181
|
this.region = options.region ?? "global";
|
|
2087
2182
|
this.logger = options.logger;
|
|
2088
|
-
this.timeoutMs = options.timeoutMs ??
|
|
2183
|
+
this.timeoutMs = options.timeoutMs ?? defaultSearchTimeoutMs;
|
|
2089
2184
|
this.resolveCredentials = options.resolveCredentials;
|
|
2090
2185
|
this.refreshCredentials = options.refreshCredentials;
|
|
2091
2186
|
this.now = options.now ?? Date.now;
|
|
@@ -2385,6 +2480,7 @@ var QoderThinkingParser = class {
|
|
|
2385
2480
|
//#region src/qoder/transport/wire/sse.ts
|
|
2386
2481
|
/** Decode Qoder's outer SSE envelope and inner model chunks. */
|
|
2387
2482
|
const doneMarker = "[DONE]";
|
|
2483
|
+
const defaultMaxSseBufferChars = 2 * 1024 * 1024;
|
|
2388
2484
|
function malformed(message) {
|
|
2389
2485
|
return new QoderLlmError(message, "MALFORMED_RESPONSE");
|
|
2390
2486
|
}
|
|
@@ -2487,7 +2583,7 @@ async function* parseQoderSse(stream, options = {}) {
|
|
|
2487
2583
|
let terminalKind = "stop";
|
|
2488
2584
|
let sawFinishReason = false;
|
|
2489
2585
|
let sawContent = false;
|
|
2490
|
-
const maxBufferChars = options.maxBufferChars ??
|
|
2586
|
+
const maxBufferChars = options.maxBufferChars ?? defaultMaxSseBufferChars;
|
|
2491
2587
|
const closeTextual = () => {
|
|
2492
2588
|
if (activeTextual === void 0) return [];
|
|
2493
2589
|
const state = activeTextual;
|
|
@@ -2751,6 +2847,16 @@ async function* streamQoderChat(options, model, credentials, messages, dependenc
|
|
|
2751
2847
|
}, dependencies.streamIdleTimeoutMs);
|
|
2752
2848
|
};
|
|
2753
2849
|
try {
|
|
2850
|
+
const markedTiers = Object.entries(request.model_config.context_config ?? {}).filter(([, tier]) => tier.is_default === true);
|
|
2851
|
+
dependencies.logger?.debug?.("[Qoder Stream] Request context", redactLogValue({
|
|
2852
|
+
sessionId: options.sessionId ?? null,
|
|
2853
|
+
requestId: request.request_id,
|
|
2854
|
+
model: request.model_config.key,
|
|
2855
|
+
region: dependencies.region,
|
|
2856
|
+
purpose: options.purpose ?? "chat",
|
|
2857
|
+
contextTier: markedTiers.length === 1 ? markedTiers[0][0] : null,
|
|
2858
|
+
context_length: request.parameters.context_length ?? null
|
|
2859
|
+
}));
|
|
2754
2860
|
const response = await dependencies.fetch(chatUrl, {
|
|
2755
2861
|
method: "POST",
|
|
2756
2862
|
headers: {
|
|
@@ -3000,13 +3106,14 @@ function createQoderTransport(options) {
|
|
|
3000
3106
|
}
|
|
3001
3107
|
//#endregion
|
|
3002
3108
|
//#region src/dsh/config.ts
|
|
3003
|
-
/** DSH configuration schema and region-scoped model catalog
|
|
3109
|
+
/** DSH configuration schema and region-scoped model catalog selection. */
|
|
3004
3110
|
const catalogModel = z.object({
|
|
3005
3111
|
id: z.string().required(),
|
|
3006
3112
|
name: z.string().required(),
|
|
3007
3113
|
description: z.string(),
|
|
3008
3114
|
contextWindow: z.number().step(1).min(1),
|
|
3009
3115
|
maxContextWindow: z.number().step(1).min(1),
|
|
3116
|
+
contextTier: z.string(),
|
|
3010
3117
|
maxTokens: z.number().step(1).min(1),
|
|
3011
3118
|
source: z.string(),
|
|
3012
3119
|
isReasoning: z.boolean(),
|
|
@@ -3018,6 +3125,8 @@ const catalogModel = z.object({
|
|
|
3018
3125
|
})),
|
|
3019
3126
|
defaultReasoningEffort: z.string(),
|
|
3020
3127
|
priceFactor: z.number().min(0),
|
|
3128
|
+
originalPriceFactor: z.number().min(0),
|
|
3129
|
+
isFree: z.boolean(),
|
|
3021
3130
|
supportsImages: z.boolean(),
|
|
3022
3131
|
contextOptions: z.dict(z.object({
|
|
3023
3132
|
tokenCount: z.number().step(1).min(1),
|
|
@@ -3028,7 +3137,6 @@ const modelsByRegionSchema = z.dict(z.array(catalogModel));
|
|
|
3028
3137
|
const Config = z.object({
|
|
3029
3138
|
region: z.union(["global", "china"]).default("global"),
|
|
3030
3139
|
modelsByRegion: modelsByRegionSchema.default({}),
|
|
3031
|
-
models: z.array(catalogModel),
|
|
3032
3140
|
streamIdleTimeoutMs: z.number().step(1).min(1).default(defaultStreamIdleTimeoutMs),
|
|
3033
3141
|
responseHeaderTimeoutMs: z.number().step(1).min(1).default(defaultResponseHeaderTimeoutMs),
|
|
3034
3142
|
preserveThinking: z.boolean().default(true),
|
|
@@ -3038,7 +3146,7 @@ const Config = z.object({
|
|
|
3038
3146
|
"disabled"
|
|
3039
3147
|
]).default("auto")
|
|
3040
3148
|
});
|
|
3041
|
-
const PluginConfig = Config.volatile();
|
|
3149
|
+
const PluginConfig = typeof Config.volatile === "function" ? Config.volatile() : Config;
|
|
3042
3150
|
function readConfig(value) {
|
|
3043
3151
|
return "get" in value ? Config(structuredClone(value.get())) : value;
|
|
3044
3152
|
}
|
|
@@ -3053,11 +3161,8 @@ function resolveModels(models) {
|
|
|
3053
3161
|
return { ...model };
|
|
3054
3162
|
});
|
|
3055
3163
|
}
|
|
3056
|
-
function modelsFor(config, region
|
|
3057
|
-
|
|
3058
|
-
if (scoped !== void 0) return resolveModels(scoped);
|
|
3059
|
-
if (legacyModelsRegion === region && config.models !== void 0 && config.models.length > 0) return resolveModels(config.models);
|
|
3060
|
-
return resolveModels(defaultModels);
|
|
3164
|
+
function modelsFor(config, region) {
|
|
3165
|
+
return resolveModels(config.modelsByRegion?.[region]);
|
|
3061
3166
|
}
|
|
3062
3167
|
//#endregion
|
|
3063
3168
|
//#region src/dsh/provider.ts
|
|
@@ -3067,10 +3172,11 @@ const QODER_PROVIDER_ID = "dsh-provider-qoder";
|
|
|
3067
3172
|
//#region src/dsh/adapter.ts
|
|
3068
3173
|
/** Thin DSH adapter over the Qoder transport seam. */
|
|
3069
3174
|
function modelInfo(provider, model) {
|
|
3175
|
+
const rate = formatModelRate(model, true);
|
|
3070
3176
|
return {
|
|
3071
3177
|
provider,
|
|
3072
3178
|
id: model.id,
|
|
3073
|
-
name:
|
|
3179
|
+
name: rate === void 0 ? model.name : `${model.name} (${rate})`,
|
|
3074
3180
|
description: model.description,
|
|
3075
3181
|
inputModalities: model.supportsImages === true ? ["text", "image"] : ["text"]
|
|
3076
3182
|
};
|
|
@@ -3082,6 +3188,12 @@ var QoderAdapter = class extends LlmAdapter {
|
|
|
3082
3188
|
providerName;
|
|
3083
3189
|
onModelsDiscovered;
|
|
3084
3190
|
discoveries = /* @__PURE__ */ new WeakMap();
|
|
3191
|
+
historyRegion;
|
|
3192
|
+
historyRegionChanged = false;
|
|
3193
|
+
region;
|
|
3194
|
+
sessionTiers = /* @__PURE__ */ new Map();
|
|
3195
|
+
sessions;
|
|
3196
|
+
agents;
|
|
3085
3197
|
constructor(options) {
|
|
3086
3198
|
super();
|
|
3087
3199
|
this.resolveTransport = options.resolveTransport;
|
|
@@ -3089,6 +3201,52 @@ var QoderAdapter = class extends LlmAdapter {
|
|
|
3089
3201
|
this.providerId = options.providerId ?? "dsh-provider-qoder";
|
|
3090
3202
|
this.providerName = options.providerName ?? "Qoder";
|
|
3091
3203
|
this.onModelsDiscovered = options.onModelsDiscovered;
|
|
3204
|
+
this.region = options.region ?? (() => "global");
|
|
3205
|
+
this.historyRegion = this.region();
|
|
3206
|
+
this.sessions = options.sessions;
|
|
3207
|
+
this.agents = options.agents;
|
|
3208
|
+
}
|
|
3209
|
+
setSessionTier(sessionId, modelId, tierKey, region = this.region()) {
|
|
3210
|
+
const model = this.effectiveModels().find((candidate) => candidate.id === modelId);
|
|
3211
|
+
if (region !== this.region() || !sessionId || !model || !contextTiersOf(model).some((tier) => tier.key === tierKey) || this.sessions && !this.sessions.get(sessionId)) throw new QoderLlmError("Invalid session context tier selection.", "INVALID_REQUEST");
|
|
3212
|
+
this.sessionTiers.set(JSON.stringify([
|
|
3213
|
+
region,
|
|
3214
|
+
sessionId,
|
|
3215
|
+
modelId
|
|
3216
|
+
]), tierKey);
|
|
3217
|
+
}
|
|
3218
|
+
getSessionTier(sessionId, modelId) {
|
|
3219
|
+
return this.sessionTiers.get(JSON.stringify([
|
|
3220
|
+
this.region(),
|
|
3221
|
+
sessionId,
|
|
3222
|
+
modelId
|
|
3223
|
+
]));
|
|
3224
|
+
}
|
|
3225
|
+
observeRegion() {
|
|
3226
|
+
if (this.region() !== this.historyRegion) {
|
|
3227
|
+
this.historyRegionChanged = true;
|
|
3228
|
+
this.historyRegion = this.region();
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
resolveEffectiveModelForSession(modelId, sessionId) {
|
|
3232
|
+
this.observeRegion();
|
|
3233
|
+
const base = this.effectiveModels().find((candidate) => candidate.id === modelId);
|
|
3234
|
+
if (base === void 0) return void 0;
|
|
3235
|
+
if (sessionId) {
|
|
3236
|
+
const manualTierKey = this.sessionTiers.get(JSON.stringify([
|
|
3237
|
+
this.region(),
|
|
3238
|
+
sessionId,
|
|
3239
|
+
modelId
|
|
3240
|
+
]));
|
|
3241
|
+
const reqCtx = this.sessions?.get(sessionId)?.requestContext?.();
|
|
3242
|
+
const tier = resolveContextTier(base, manualTierKey, !this.historyRegionChanged && reqCtx?.provider === this.providerId && reqCtx.model === modelId ? reqCtx.contextWindow : void 0);
|
|
3243
|
+
if (tier) return {
|
|
3244
|
+
...base,
|
|
3245
|
+
contextTier: tier.key,
|
|
3246
|
+
contextWindow: tier.tokenCount
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
return base;
|
|
3092
3250
|
}
|
|
3093
3251
|
providerInfo(provider) {
|
|
3094
3252
|
return {
|
|
@@ -3133,6 +3291,7 @@ var QoderAdapter = class extends LlmAdapter {
|
|
|
3133
3291
|
return mergeQoderDiscoveryMetadata(this.catalogModels, this.discoveries.get(this.resolveTransport())?.models ?? []);
|
|
3134
3292
|
}
|
|
3135
3293
|
replaceModels(models) {
|
|
3294
|
+
this.observeRegion();
|
|
3136
3295
|
this.catalogModels = models;
|
|
3137
3296
|
}
|
|
3138
3297
|
/** Publish an explicit discovery without letting older in-flight reads overwrite it. */
|
|
@@ -3144,16 +3303,34 @@ var QoderAdapter = class extends LlmAdapter {
|
|
|
3144
3303
|
}
|
|
3145
3304
|
resolveModel(provider, modelId, signal) {
|
|
3146
3305
|
if (signal?.aborted) return Promise.reject(new QoderLlmError("Qoder model resolution was aborted.", "ABORTED"));
|
|
3147
|
-
const
|
|
3306
|
+
const sessionId = this.agents?.currentInitiator()?.session?.id;
|
|
3307
|
+
const configured = this.resolveEffectiveModelForSession(modelId, sessionId);
|
|
3308
|
+
return this.resolvedModel(provider, modelId, configured);
|
|
3309
|
+
}
|
|
3310
|
+
prepareCall(provider, modelId, signal) {
|
|
3311
|
+
if (signal?.aborted) return Promise.reject(new QoderLlmError("Qoder model resolution was aborted.", "ABORTED"));
|
|
3312
|
+
const configured = this.resolveEffectiveModelForSession(modelId, this.agents?.currentInitiator()?.session?.id);
|
|
3313
|
+
const snapshot = configured === void 0 ? void 0 : structuredClone(configured);
|
|
3314
|
+
const transport = this.resolveTransport();
|
|
3315
|
+
return this.resolvedModel(provider, modelId, snapshot).then((model) => ({
|
|
3316
|
+
model,
|
|
3317
|
+
stream: (options) => {
|
|
3318
|
+
if (options.provider !== this.providerId) throw new QoderLlmError(`Qoder adapter does not own provider "${options.provider}".`, "INVALID_PROVIDER");
|
|
3319
|
+
return transport.stream(options, snapshot);
|
|
3320
|
+
}
|
|
3321
|
+
}));
|
|
3322
|
+
}
|
|
3323
|
+
resolvedModel(provider, modelId, configured) {
|
|
3148
3324
|
if (configured === void 0) return Promise.resolve({
|
|
3149
3325
|
provider,
|
|
3150
3326
|
id: modelId,
|
|
3151
3327
|
name: modelId,
|
|
3152
3328
|
inputModalities: ["text"]
|
|
3153
3329
|
});
|
|
3330
|
+
const contextWindow = effectiveContextWindow(configured);
|
|
3154
3331
|
return Promise.resolve({
|
|
3155
3332
|
...modelInfo(provider, configured),
|
|
3156
|
-
...
|
|
3333
|
+
...contextWindow === void 0 ? {} : { context: { contextWindow } },
|
|
3157
3334
|
...configured.maxTokens === void 0 ? {} : { defaultMaxTokens: configured.maxTokens },
|
|
3158
3335
|
...configured.reasoningEfforts === void 0 || configured.reasoningEfforts.length === 0 ? {} : { reasoning: {
|
|
3159
3336
|
efforts: configured.reasoningEfforts.map((effort) => ({
|
|
@@ -3167,7 +3344,7 @@ var QoderAdapter = class extends LlmAdapter {
|
|
|
3167
3344
|
}
|
|
3168
3345
|
stream(options) {
|
|
3169
3346
|
if (options.provider !== this.providerId) throw new QoderLlmError(`Qoder adapter does not own provider "${options.provider}".`, "INVALID_PROVIDER");
|
|
3170
|
-
const model = this.
|
|
3347
|
+
const model = this.resolveEffectiveModelForSession(options.model || "cmodel", options.sessionId);
|
|
3171
3348
|
return this.resolveTransport().stream(options, model);
|
|
3172
3349
|
}
|
|
3173
3350
|
};
|
|
@@ -3183,7 +3360,6 @@ var QoderAdapter = class extends LlmAdapter {
|
|
|
3183
3360
|
* @module dsh-provider-qoder/dsh/search-provider
|
|
3184
3361
|
*/
|
|
3185
3362
|
const QODER_SEARCH_PROVIDER_ID = "qoder";
|
|
3186
|
-
const QODER_MODEL_PROVIDER_ROUTE = QODER_PROVIDER_ID;
|
|
3187
3363
|
var QoderSearchProvider = class {
|
|
3188
3364
|
id = QODER_SEARCH_PROVIDER_ID;
|
|
3189
3365
|
ctx;
|
|
@@ -3207,14 +3383,14 @@ var QoderSearchProvider = class {
|
|
|
3207
3383
|
const agent = (this.ctx.get("agents") ?? this.ctx.agents)?.currentInitiator?.();
|
|
3208
3384
|
const defaultModelService = this.ctx.get("agentDefaultModel");
|
|
3209
3385
|
const providerRoute = agent?.options?.provider ?? defaultModelService?.get?.()?.provider;
|
|
3210
|
-
if (mode === "always" || providerRoute ===
|
|
3386
|
+
if (mode === "always" || providerRoute === "dsh-provider-qoder") {
|
|
3211
3387
|
if (this.resolveTransport !== void 0) return this.resolveTransport().searchWeb(request, signal);
|
|
3212
3388
|
if (this.searchClient !== void 0) return this.searchClient.search(request, signal);
|
|
3213
3389
|
throw new WebError("No Qoder transport configured for web search.", "WEB_PROVIDER_ERROR");
|
|
3214
3390
|
}
|
|
3215
3391
|
const fallback = this.resolveFallback();
|
|
3216
3392
|
if (fallback && fallback.available()) return fallback.search(request, signal);
|
|
3217
|
-
throw new WebError(`Current model provider is "${providerRoute ?? "unknown"}" rather than "${
|
|
3393
|
+
throw new WebError(`Current model provider is "${providerRoute ?? "unknown"}" rather than "${QODER_PROVIDER_ID}", and no fallback web search provider is available. Switch to a Qoder model or configure an ambient search provider.`, "WEB_PROVIDER_UNAVAILABLE");
|
|
3218
3394
|
}
|
|
3219
3395
|
resolveFallback() {
|
|
3220
3396
|
if (this.fallbackProvider && this.fallbackProvider.available()) return this.fallbackProvider;
|
|
@@ -3245,14 +3421,6 @@ async function resolveManagedQoderPat(credentials) {
|
|
|
3245
3421
|
//#endregion
|
|
3246
3422
|
//#region src/dsh/settings.ts
|
|
3247
3423
|
function bindQoderSettings(owner, ctx, input, options) {
|
|
3248
|
-
const settings = ctx.settings;
|
|
3249
|
-
if (typeof settings.register === "function") {
|
|
3250
|
-
const namespace = "provider-qoder";
|
|
3251
|
-
return {
|
|
3252
|
-
namespace,
|
|
3253
|
-
scope: settings.register(namespace, Config, options)
|
|
3254
|
-
};
|
|
3255
|
-
}
|
|
3256
3424
|
const namespace = owner.fiber.entry?.options.id ?? "provider-qoder";
|
|
3257
3425
|
const get = () => readConfig(input);
|
|
3258
3426
|
options.validate?.(get());
|
|
@@ -3265,10 +3433,9 @@ function bindQoderSettings(owner, ctx, input, options) {
|
|
|
3265
3433
|
namespace,
|
|
3266
3434
|
scope: {
|
|
3267
3435
|
get,
|
|
3268
|
-
update: (patch) => settings.update(namespace, patch),
|
|
3436
|
+
update: (patch) => ctx.settings.update(namespace, patch),
|
|
3269
3437
|
watch: (listener) => {
|
|
3270
|
-
|
|
3271
|
-
return ctx.effect(() => on("loader/volatile-update", () => {
|
|
3438
|
+
return ctx.effect(() => owner.on("loader/volatile-update", () => {
|
|
3272
3439
|
Promise.resolve().then(listener).catch((error) => ctx.emit("internal/error", error));
|
|
3273
3440
|
}));
|
|
3274
3441
|
}
|
|
@@ -3280,8 +3447,12 @@ function bindQoderSettings(owner, ctx, input, options) {
|
|
|
3280
3447
|
* channel at `/api`, and every exact Fetch route lives directly below it.
|
|
3281
3448
|
*/
|
|
3282
3449
|
const qoderRpcApiPath = `/api/qoder-subscription`;
|
|
3283
|
-
/** Endpoints the account card
|
|
3284
|
-
const qoderRpcEndpoints = [
|
|
3450
|
+
/** Endpoints the account card, the model catalog, and the composer context select call. */
|
|
3451
|
+
const qoderRpcEndpoints = [
|
|
3452
|
+
"account",
|
|
3453
|
+
"models",
|
|
3454
|
+
"sessionTier"
|
|
3455
|
+
];
|
|
3285
3456
|
/** Absolute Fetch-route path of one endpoint. */
|
|
3286
3457
|
function qoderRpcPath(endpoint) {
|
|
3287
3458
|
return `${qoderRpcApiPath}/${endpoint}`;
|
|
@@ -3313,7 +3484,7 @@ function registerQoderRpc(ctx, handler) {
|
|
|
3313
3484
|
fetch: (request) => handleQoderRpcRequest(endpoint, handler, request)
|
|
3314
3485
|
}));
|
|
3315
3486
|
return () => {
|
|
3316
|
-
for (const dispose of disposers)
|
|
3487
|
+
for (const dispose of disposers) dispose();
|
|
3317
3488
|
};
|
|
3318
3489
|
}
|
|
3319
3490
|
/**
|
|
@@ -3386,19 +3557,15 @@ function publicError(code, message, details = { issues: [] }) {
|
|
|
3386
3557
|
function apply(ctx, input = {}) {
|
|
3387
3558
|
const config = readConfig(input);
|
|
3388
3559
|
const logger = ctx.logger;
|
|
3389
|
-
const initialRegion = config.region ?? "global";
|
|
3390
|
-
const hasConfiguredModels = config.models !== void 0 && config.models.length > 0;
|
|
3391
3560
|
const baseConfig = {
|
|
3392
|
-
region:
|
|
3561
|
+
region: config.region ?? "global",
|
|
3393
3562
|
modelsByRegion: { ...config.modelsByRegion },
|
|
3394
|
-
...hasConfiguredModels ? { models: resolveModels(config.models) } : {},
|
|
3395
3563
|
streamIdleTimeoutMs: config.streamIdleTimeoutMs ?? 3e5,
|
|
3396
3564
|
responseHeaderTimeoutMs: config.responseHeaderTimeoutMs ?? 6e4,
|
|
3397
3565
|
preserveThinking: config.preserveThinking ?? true,
|
|
3398
3566
|
webSearchMode: config.webSearchMode ?? "auto"
|
|
3399
3567
|
};
|
|
3400
3568
|
let current = () => baseConfig;
|
|
3401
|
-
let legacyModelsRegion = initialRegion;
|
|
3402
3569
|
let persistDiscoveredModels = async (_region) => {};
|
|
3403
3570
|
const discoveredCatalogs = {
|
|
3404
3571
|
global: [],
|
|
@@ -3418,7 +3585,7 @@ function apply(ctx, input = {}) {
|
|
|
3418
3585
|
const region = value.region ?? "global";
|
|
3419
3586
|
return {
|
|
3420
3587
|
region,
|
|
3421
|
-
models: mergeQoderDiscoveryMetadata(modelsFor(value, region
|
|
3588
|
+
models: mergeQoderDiscoveryMetadata(modelsFor(value, region), discoveredCatalogs[region]),
|
|
3422
3589
|
streamIdleTimeoutMs: value.streamIdleTimeoutMs ?? 3e5,
|
|
3423
3590
|
responseHeaderTimeoutMs: value.responseHeaderTimeoutMs ?? 6e4,
|
|
3424
3591
|
preserveThinking: value.preserveThinking ?? true,
|
|
@@ -3436,8 +3603,11 @@ function apply(ctx, input = {}) {
|
|
|
3436
3603
|
const adapter = new QoderAdapter({
|
|
3437
3604
|
resolveTransport: () => activeTransport,
|
|
3438
3605
|
models: initial.models,
|
|
3606
|
+
region: () => activeTransportConfig.region,
|
|
3439
3607
|
providerId: providerQoder,
|
|
3440
3608
|
providerName: "Qoder",
|
|
3609
|
+
sessions: ctx.get("sessions"),
|
|
3610
|
+
agents: ctx.get("agents"),
|
|
3441
3611
|
onModelsDiscovered: (transport, models) => {
|
|
3442
3612
|
if (transport !== activeTransport || ctx.fiber.state === fiberUnloading || ctx.fiber.state === fiberDisposed) return;
|
|
3443
3613
|
const region = activeTransportConfig.region;
|
|
@@ -3464,15 +3634,11 @@ function apply(ctx, input = {}) {
|
|
|
3464
3634
|
registration.replace([providerQoder]);
|
|
3465
3635
|
};
|
|
3466
3636
|
ctx.inject(["settings"], (settingsCtx) => {
|
|
3467
|
-
const { scope, namespace } = bindQoderSettings(ctx, settingsCtx, input, {
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
modelsFor(value, "china", legacyModelsRegion);
|
|
3473
|
-
}
|
|
3474
|
-
});
|
|
3475
|
-
legacyModelsRegion = scope.get().region ?? initialRegion;
|
|
3637
|
+
const { scope, namespace } = bindQoderSettings(ctx, settingsCtx, input, { validate: (value) => {
|
|
3638
|
+
for (const region of Object.keys(value.modelsByRegion ?? {})) if (region !== "global" && region !== "china") throw new Error(`provider-qoder: unsupported model catalog region "${region}"`);
|
|
3639
|
+
modelsFor(value, "global");
|
|
3640
|
+
modelsFor(value, "china");
|
|
3641
|
+
} });
|
|
3476
3642
|
current = () => scope.get();
|
|
3477
3643
|
let bindingActive = true;
|
|
3478
3644
|
const persistCatalog = async (region) => {
|
|
@@ -3480,7 +3646,7 @@ function apply(ctx, input = {}) {
|
|
|
3480
3646
|
if (!settingsCtx.settings.writable || discoveredCatalogs[region].length === 0) return;
|
|
3481
3647
|
const snapshot = settingsCtx.settings.describe().find((section) => section.ns === namespace);
|
|
3482
3648
|
if (snapshot === void 0) return;
|
|
3483
|
-
const selected = modelsFor(snapshot.value, region
|
|
3649
|
+
const selected = modelsFor(snapshot.value, region);
|
|
3484
3650
|
const enriched = modelsFor(Config({ modelsByRegion: { [region]: mergeQoderDiscoveryMetadata(selected, discoveredCatalogs[region]) } }), region);
|
|
3485
3651
|
if (hasSameQoderDiscoveryMetadata(selected, enriched)) return;
|
|
3486
3652
|
try {
|
|
@@ -3493,9 +3659,6 @@ function apply(ctx, input = {}) {
|
|
|
3493
3659
|
};
|
|
3494
3660
|
persistDiscoveredModels = persistCatalog;
|
|
3495
3661
|
refreshAdapter();
|
|
3496
|
-
const loaded = scope.get();
|
|
3497
|
-
const loadedRegion = loaded.region ?? "global";
|
|
3498
|
-
if (loaded.models !== void 0 && loaded.models.length > 0 && loaded.modelsByRegion?.[loadedRegion] === void 0) scope.update({ modelsByRegion: { [loadedRegion]: resolveModels(loaded.models) } }).catch((error) => logger?.error?.("[Qoder Settings] Failed to migrate model catalog", logError(error)));
|
|
3499
3662
|
scope.watch(async () => {
|
|
3500
3663
|
if (!bindingActive || ctx.fiber.state === fiberUnloading || ctx.fiber.state === fiberDisposed) return;
|
|
3501
3664
|
refreshAdapter();
|
|
@@ -3557,6 +3720,17 @@ function apply(ctx, input = {}) {
|
|
|
3557
3720
|
}
|
|
3558
3721
|
};
|
|
3559
3722
|
if (endpoint === "models") return await executeRpc("discover Qoder models", () => discoverModels(signal));
|
|
3723
|
+
if (endpoint === "sessionTier") {
|
|
3724
|
+
const data = payload;
|
|
3725
|
+
if (typeof data?.sessionId === "string" && typeof data?.modelId === "string" && typeof data?.tierKey === "string" && (data.region === "global" || data.region === "china")) {
|
|
3726
|
+
const { sessionId, modelId, tierKey, region } = data;
|
|
3727
|
+
return await executeRpc("select session context tier", async () => {
|
|
3728
|
+
adapter.setSessionTier(sessionId, modelId, tierKey, region);
|
|
3729
|
+
return { success: true };
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
return publicError("INTERNAL", "Invalid sessionTier payload");
|
|
3733
|
+
}
|
|
3560
3734
|
const force = typeof payload === "object" && payload !== null && "force" in payload ? payload.force === true : false;
|
|
3561
3735
|
logger?.debug?.("[Qoder RPC] Reading subscriber account", { force });
|
|
3562
3736
|
const outcome = await executeRpc("load Qoder account", () => activeTransport.readAccount({
|
|
@@ -3598,4 +3772,4 @@ function apply(ctx, input = {}) {
|
|
|
3598
3772
|
});
|
|
3599
3773
|
}
|
|
3600
3774
|
//#endregion
|
|
3601
|
-
export { PluginConfig as Config, QODER_SEARCH_PROVIDER_ID,
|
|
3775
|
+
export { PluginConfig as Config, QODER_SEARCH_PROVIDER_ID, QoderSearchProvider, apply, inject, name };
|