@kenkaiiii/gg-ai 5.76.0 → 5.78.0
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 +131 -0
- package/dist/index.cjs +784 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -2
- package/dist/index.d.ts +233 -2
- package/dist/index.js +780 -186
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
clampProviderContextImages: () => clampProviderContextImages,
|
|
40
40
|
classifyProviderError: () => classifyProviderError,
|
|
41
41
|
dropInvalidToolCalls: () => dropInvalidToolCalls,
|
|
42
|
+
embed: () => embed,
|
|
42
43
|
environmentSecrets: () => environmentSecrets,
|
|
43
44
|
formatChatError: () => formatChatError,
|
|
44
45
|
formatError: () => formatError,
|
|
@@ -49,6 +50,8 @@ __export(index_exports, {
|
|
|
49
50
|
isValidToolCallName: () => isValidToolCallName,
|
|
50
51
|
localWireModelId: () => localWireModelId,
|
|
51
52
|
palsuAssistantMessage: () => palsuAssistantMessage,
|
|
53
|
+
palsuEmbedding: () => palsuEmbedding,
|
|
54
|
+
palsuRerankScore: () => palsuRerankScore,
|
|
52
55
|
palsuText: () => palsuText,
|
|
53
56
|
palsuThinking: () => palsuThinking,
|
|
54
57
|
palsuToolCall: () => palsuToolCall,
|
|
@@ -57,6 +60,7 @@ __export(index_exports, {
|
|
|
57
60
|
redactText: () => redactText,
|
|
58
61
|
redactValue: () => redactValue,
|
|
59
62
|
registerPalsuProvider: () => registerPalsuProvider,
|
|
63
|
+
rerank: () => rerank,
|
|
60
64
|
resolveToolSchema: () => resolveToolSchema,
|
|
61
65
|
sanitizeMessagesForWire: () => sanitizeMessagesForWire,
|
|
62
66
|
setProviderDiagnostic: () => setProviderDiagnostic,
|
|
@@ -111,7 +115,7 @@ var ProviderError = class extends GGAIError {
|
|
|
111
115
|
resetsAt;
|
|
112
116
|
constructor(provider, message, options) {
|
|
113
117
|
super(message, {
|
|
114
|
-
source: "provider",
|
|
118
|
+
source: options?.source ?? "provider",
|
|
115
119
|
requestId: options?.requestId,
|
|
116
120
|
hint: options?.hint,
|
|
117
121
|
cause: options?.cause
|
|
@@ -508,6 +512,46 @@ var StreamResult = class _StreamResult {
|
|
|
508
512
|
}
|
|
509
513
|
};
|
|
510
514
|
|
|
515
|
+
// src/utils/provider-options.ts
|
|
516
|
+
var RESERVED_BODY_FIELDS = /* @__PURE__ */ new Set([
|
|
517
|
+
"contents",
|
|
518
|
+
"dimensions",
|
|
519
|
+
"documents",
|
|
520
|
+
"encoding_format",
|
|
521
|
+
"input",
|
|
522
|
+
"messages",
|
|
523
|
+
"model",
|
|
524
|
+
"query",
|
|
525
|
+
"requests",
|
|
526
|
+
"stream",
|
|
527
|
+
"system",
|
|
528
|
+
"tools",
|
|
529
|
+
"top_n",
|
|
530
|
+
// Never copy prototype-shaped keys onto a request body.
|
|
531
|
+
"__proto__",
|
|
532
|
+
"constructor",
|
|
533
|
+
"prototype"
|
|
534
|
+
]);
|
|
535
|
+
function mergeProviderOptions(body, providerOptions) {
|
|
536
|
+
if (!providerOptions) return body;
|
|
537
|
+
const base = body;
|
|
538
|
+
const extra = {};
|
|
539
|
+
for (const key of Object.keys(providerOptions).sort()) {
|
|
540
|
+
if (RESERVED_BODY_FIELDS.has(key)) continue;
|
|
541
|
+
if (Object.hasOwn(base, key) && base[key] !== void 0) continue;
|
|
542
|
+
const value = providerOptions[key];
|
|
543
|
+
if (value === void 0) continue;
|
|
544
|
+
Object.defineProperty(extra, key, {
|
|
545
|
+
value,
|
|
546
|
+
enumerable: true,
|
|
547
|
+
writable: true,
|
|
548
|
+
configurable: true
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
if (Object.keys(extra).length === 0) return body;
|
|
552
|
+
return { ...base, ...extra };
|
|
553
|
+
}
|
|
554
|
+
|
|
511
555
|
// src/utils/zod-to-json-schema.ts
|
|
512
556
|
var import_zod = require("zod");
|
|
513
557
|
var schemaCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -1163,6 +1207,7 @@ function remapAnthropicToolCallId(id, idMap) {
|
|
|
1163
1207
|
function toAnthropicMessages(messages, cacheControl, options) {
|
|
1164
1208
|
let systemText;
|
|
1165
1209
|
const out = [];
|
|
1210
|
+
const userTurnIdx = [];
|
|
1166
1211
|
const idMap = /* @__PURE__ */ new Map();
|
|
1167
1212
|
const keepFallbackBlocks = options?.fallbackBlocks === true;
|
|
1168
1213
|
const droppedToolCallIds = /* @__PURE__ */ new Set();
|
|
@@ -1183,6 +1228,7 @@ function toAnthropicMessages(messages, cacheControl, options) {
|
|
|
1183
1228
|
} else if (!msg.content.some((p) => !(p.type === "text" && p.text === ""))) {
|
|
1184
1229
|
continue;
|
|
1185
1230
|
}
|
|
1231
|
+
userTurnIdx.push(out.length);
|
|
1186
1232
|
out.push({
|
|
1187
1233
|
role: "user",
|
|
1188
1234
|
content: typeof msg.content === "string" ? msg.content : msg.content.filter((part) => !(part.type === "text" && part.text === "")).map((part) => {
|
|
@@ -1237,28 +1283,21 @@ function toAnthropicMessages(messages, cacheControl, options) {
|
|
|
1237
1283
|
}
|
|
1238
1284
|
}
|
|
1239
1285
|
if (cacheControl && out.length > 0) {
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
]
|
|
1253
|
-
};
|
|
1254
|
-
} else if (Array.isArray(content) && content.length > 0) {
|
|
1255
|
-
const last = content[content.length - 1];
|
|
1256
|
-
content[content.length - 1] = {
|
|
1257
|
-
...last,
|
|
1258
|
-
cache_control: cacheControl
|
|
1259
|
-
};
|
|
1286
|
+
let tail = out.length - 1;
|
|
1287
|
+
while (tail >= 0 && out[tail]?.role !== "user") tail--;
|
|
1288
|
+
if (tail !== -1) {
|
|
1289
|
+
if (cacheControl.ttl === "1h") {
|
|
1290
|
+
const anchor = userTurnIdx.at(-1);
|
|
1291
|
+
if (anchor === tail) {
|
|
1292
|
+
const previous = userTurnIdx.at(-2);
|
|
1293
|
+
if (previous !== void 0) markAnthropicCacheBreakpoint(out, previous, cacheControl);
|
|
1294
|
+
markAnthropicCacheBreakpoint(out, tail, cacheControl);
|
|
1295
|
+
} else {
|
|
1296
|
+
if (anchor !== void 0) markAnthropicCacheBreakpoint(out, anchor, cacheControl);
|
|
1297
|
+
markAnthropicCacheBreakpoint(out, tail, { type: "ephemeral" });
|
|
1260
1298
|
}
|
|
1261
|
-
|
|
1299
|
+
} else {
|
|
1300
|
+
markAnthropicCacheBreakpoint(out, tail, cacheControl);
|
|
1262
1301
|
}
|
|
1263
1302
|
}
|
|
1264
1303
|
}
|
|
@@ -1285,6 +1324,25 @@ function toAnthropicMessages(messages, cacheControl, options) {
|
|
|
1285
1324
|
}
|
|
1286
1325
|
return { system, messages: out };
|
|
1287
1326
|
}
|
|
1327
|
+
function markAnthropicCacheBreakpoint(out, index, cacheControl) {
|
|
1328
|
+
const message = out[index];
|
|
1329
|
+
if (!message) return;
|
|
1330
|
+
const content = message.content;
|
|
1331
|
+
if (typeof content === "string") {
|
|
1332
|
+
out[index] = {
|
|
1333
|
+
role: message.role,
|
|
1334
|
+
content: [
|
|
1335
|
+
{ type: "text", text: content, cache_control: cacheControl }
|
|
1336
|
+
]
|
|
1337
|
+
};
|
|
1338
|
+
} else if (content.length > 0) {
|
|
1339
|
+
const last = content[content.length - 1];
|
|
1340
|
+
content[content.length - 1] = {
|
|
1341
|
+
...last,
|
|
1342
|
+
cache_control: cacheControl
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1288
1346
|
function toAnthropicTools(tools, options) {
|
|
1289
1347
|
return tools.map((tool, index) => {
|
|
1290
1348
|
const anthropicTool = {
|
|
@@ -1750,7 +1808,7 @@ async function* runStream(options) {
|
|
|
1750
1808
|
}
|
|
1751
1809
|
maxTokens = 1;
|
|
1752
1810
|
}
|
|
1753
|
-
const
|
|
1811
|
+
const baseParams = {
|
|
1754
1812
|
model: options.model,
|
|
1755
1813
|
max_tokens: maxTokens,
|
|
1756
1814
|
messages,
|
|
@@ -1793,6 +1851,7 @@ async function* runStream(options) {
|
|
|
1793
1851
|
...useServerFallback ? { fallbacks: "default" } : {},
|
|
1794
1852
|
stream: useStreaming
|
|
1795
1853
|
};
|
|
1854
|
+
const params = mergeProviderOptions(baseParams, options.providerOptions);
|
|
1796
1855
|
const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
|
|
1797
1856
|
const betaHeaders = [
|
|
1798
1857
|
...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
|
|
@@ -2498,20 +2557,21 @@ async function* runStream2(options) {
|
|
|
2498
2557
|
params.thinking = { type: "disabled" };
|
|
2499
2558
|
}
|
|
2500
2559
|
}
|
|
2560
|
+
const requestParams = mergeProviderOptions(params, options.providerOptions);
|
|
2501
2561
|
if (getEnvironment()?.GGAI_DUMP_REQUEST) {
|
|
2502
2562
|
const fs = await import("fs");
|
|
2503
2563
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2504
2564
|
const dumpPath = `/tmp/ggai-request-${ts}.json`;
|
|
2505
|
-
fs.writeFileSync(dumpPath, JSON.stringify(
|
|
2565
|
+
fs.writeFileSync(dumpPath, JSON.stringify(requestParams, null, 2));
|
|
2506
2566
|
fs.appendFileSync(
|
|
2507
2567
|
"/tmp/ggai-requests.log",
|
|
2508
|
-
`[${ts}] ${dumpPath} messages=${
|
|
2568
|
+
`[${ts}] ${dumpPath} messages=${requestParams.messages.length}
|
|
2509
2569
|
`
|
|
2510
2570
|
);
|
|
2511
2571
|
}
|
|
2512
2572
|
if (!useStreaming) {
|
|
2513
2573
|
try {
|
|
2514
|
-
const completion = await client.chat.completions.create(
|
|
2574
|
+
const completion = await client.chat.completions.create(requestParams, {
|
|
2515
2575
|
signal: options.signal ?? void 0
|
|
2516
2576
|
});
|
|
2517
2577
|
yield* synthesizeEventsFromCompletion(completion, !!options.thinking, endpointKey);
|
|
@@ -2522,7 +2582,7 @@ async function* runStream2(options) {
|
|
|
2522
2582
|
}
|
|
2523
2583
|
let stream2;
|
|
2524
2584
|
try {
|
|
2525
|
-
stream2 = await client.chat.completions.create(
|
|
2585
|
+
stream2 = await client.chat.completions.create(requestParams, {
|
|
2526
2586
|
signal: options.signal ?? void 0
|
|
2527
2587
|
});
|
|
2528
2588
|
} catch (err) {
|
|
@@ -3010,7 +3070,9 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
3010
3070
|
headers["session_id"] = transportSessionId;
|
|
3011
3071
|
headers["x-client-request-id"] = transportSessionId;
|
|
3012
3072
|
}
|
|
3013
|
-
const encodedRequest = await encodeCodexRequest(
|
|
3073
|
+
const encodedRequest = await encodeCodexRequest(
|
|
3074
|
+
mergeProviderOptions(body, options.providerOptions)
|
|
3075
|
+
);
|
|
3014
3076
|
if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
|
|
3015
3077
|
providerDiag("codex_request_body", {
|
|
3016
3078
|
rawBytes: encodedRequest.rawBytes,
|
|
@@ -3725,11 +3787,11 @@ function stripUnsupportedSchemaFields(value) {
|
|
|
3725
3787
|
["exclusiveMinimum", "minimum", 1],
|
|
3726
3788
|
["exclusiveMaximum", "maximum", -1]
|
|
3727
3789
|
]) {
|
|
3728
|
-
const
|
|
3729
|
-
if (
|
|
3790
|
+
const bound2 = value[key];
|
|
3791
|
+
if (bound2 === void 0) continue;
|
|
3730
3792
|
delete value[key];
|
|
3731
|
-
if (typeof
|
|
3732
|
-
value[target] = value.type === "integer" ?
|
|
3793
|
+
if (typeof bound2 === "number" && value[target] === void 0) {
|
|
3794
|
+
value[target] = value.type === "integer" ? bound2 + step : bound2;
|
|
3733
3795
|
}
|
|
3734
3796
|
}
|
|
3735
3797
|
for (const item of Object.values(value)) {
|
|
@@ -3823,7 +3885,7 @@ function buildRequestPlan(options, method) {
|
|
|
3823
3885
|
throw new ProviderError("gemini", formatUnsupportedModelMessage(options.model));
|
|
3824
3886
|
}
|
|
3825
3887
|
const projectId = getGoogleProject(options);
|
|
3826
|
-
const request = buildGenerateRequest(options);
|
|
3888
|
+
const request = mergeProviderOptions(buildGenerateRequest(options), options.providerOptions);
|
|
3827
3889
|
return {
|
|
3828
3890
|
url: getCodeAssistEndpoint(method),
|
|
3829
3891
|
headers: {
|
|
@@ -4061,6 +4123,449 @@ function toError3(err) {
|
|
|
4061
4123
|
return new ProviderError("gemini", String(err));
|
|
4062
4124
|
}
|
|
4063
4125
|
|
|
4126
|
+
// src/redaction.ts
|
|
4127
|
+
var REDACTED = "[REDACTED]";
|
|
4128
|
+
var TRUNCATED = "[TRUNCATED]";
|
|
4129
|
+
var CIRCULAR = "[CIRCULAR]";
|
|
4130
|
+
var SENSITIVE_NAME = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret)(?:$|[_-])/i;
|
|
4131
|
+
var ENV_SECRET_ASSIGNMENT = /\b((?:[A-Z0-9]+_)*(?:API_?KEY|ACCESS_TOKEN|REFRESH_TOKEN|TOKEN|KEY|AUTH|AUTHORIZATION|BEARER|CREDENTIALS?|PASSWORD|PASSWD|SECRET))\b(\s*[=:]\s*)(["']?)(?!\$|process\.env|os\.environ|import\.meta|env\.)(?=[^\s,"';}]*\d)([^\s,"';}=$][^\s,"';}]{7,})\3/g;
|
|
4132
|
+
var COMPACT_SECRET_ASSIGNMENT = /\b((?:[a-z0-9]+[_-])*(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|auth|authorization|credentials?|password|passwd|secret)|(?:[a-z0-9]+[_-])+key)=(["']?)(?!\$)([^\s,"'&;}=][^\s,"'&;}]{7,})\2/gi;
|
|
4133
|
+
var URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#@:"'`<>]*:[^\s/?#"'`<>]+@/gi;
|
|
4134
|
+
var URL_USERINFO_TO_FIRST_AT = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]*:[^\s/@]+@/gi;
|
|
4135
|
+
function escaped(value) {
|
|
4136
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4137
|
+
}
|
|
4138
|
+
function normalizedSecrets(secrets) {
|
|
4139
|
+
if (!secrets) return [];
|
|
4140
|
+
return [...new Set([...secrets].filter((value) => value.length >= 8 && value !== REDACTED))].sort(
|
|
4141
|
+
(a, b) => b.length - a.length
|
|
4142
|
+
);
|
|
4143
|
+
}
|
|
4144
|
+
function environmentSecrets(env) {
|
|
4145
|
+
const values = /* @__PURE__ */ new Set();
|
|
4146
|
+
for (const [name, value] of Object.entries(env)) {
|
|
4147
|
+
if (!value || value.length < 8 || value === REDACTED || !SENSITIVE_NAME.test(name)) continue;
|
|
4148
|
+
values.add(value);
|
|
4149
|
+
}
|
|
4150
|
+
return [...values].sort((a, b) => b.length - a.length);
|
|
4151
|
+
}
|
|
4152
|
+
function redactText(text, options = {}) {
|
|
4153
|
+
let result = text;
|
|
4154
|
+
result = result.replace(
|
|
4155
|
+
/-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,
|
|
4156
|
+
REDACTED
|
|
4157
|
+
);
|
|
4158
|
+
result = result.replace(URL_USERINFO, `$1${REDACTED}@`);
|
|
4159
|
+
result = result.replace(URL_USERINFO_TO_FIRST_AT, `$1${REDACTED}@`);
|
|
4160
|
+
result = result.replace(
|
|
4161
|
+
/\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[^\s,;]+/gi,
|
|
4162
|
+
`$1${REDACTED}`
|
|
4163
|
+
);
|
|
4164
|
+
result = result.replace(
|
|
4165
|
+
/\b(bearer|basic)\s+(?![A-Za-z][a-z]{0,14}\b)[A-Za-z0-9+/_.=-]{8,}/gi,
|
|
4166
|
+
`$1 ${REDACTED}`
|
|
4167
|
+
);
|
|
4168
|
+
result = result.replace(/\b(cookie|set-cookie)(\s*:\s*)[^\s=;:]+=[^\r\n]*/gi, `$1$2${REDACTED}`);
|
|
4169
|
+
result = result.replace(
|
|
4170
|
+
/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,
|
|
4171
|
+
REDACTED
|
|
4172
|
+
);
|
|
4173
|
+
result = result.replace(
|
|
4174
|
+
/\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|gh[pousr]_|github_pat_|AIza)[A-Za-z0-9_-]{12,}\b/g,
|
|
4175
|
+
REDACTED
|
|
4176
|
+
);
|
|
4177
|
+
result = result.replace(
|
|
4178
|
+
ENV_SECRET_ASSIGNMENT,
|
|
4179
|
+
(_match, name, separator, quote) => `${name}${separator}${quote}${REDACTED}${quote}`
|
|
4180
|
+
);
|
|
4181
|
+
result = result.replace(
|
|
4182
|
+
COMPACT_SECRET_ASSIGNMENT,
|
|
4183
|
+
(_match, name, quote) => `${name}=${quote}${REDACTED}${quote}`
|
|
4184
|
+
);
|
|
4185
|
+
for (const secret of normalizedSecrets(options.secrets)) {
|
|
4186
|
+
result = result.replace(new RegExp(escaped(secret), "g"), REDACTED);
|
|
4187
|
+
}
|
|
4188
|
+
const maxStringLength = options.maxStringLength ?? 1e6;
|
|
4189
|
+
if (result.length > maxStringLength) {
|
|
4190
|
+
result = `${result.slice(0, maxStringLength)}${TRUNCATED}`;
|
|
4191
|
+
}
|
|
4192
|
+
return result;
|
|
4193
|
+
}
|
|
4194
|
+
function isBinary(value) {
|
|
4195
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob;
|
|
4196
|
+
}
|
|
4197
|
+
function isMediaObject(value) {
|
|
4198
|
+
return (value.type === "image" || value.type === "video") && (typeof value.data === "string" || typeof value.url === "string");
|
|
4199
|
+
}
|
|
4200
|
+
function redactValue(value, options = {}) {
|
|
4201
|
+
const maxDepth = options.maxDepth ?? 20;
|
|
4202
|
+
const maxEntries = options.maxEntries ?? 1e4;
|
|
4203
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
4204
|
+
let entries = 0;
|
|
4205
|
+
const visit = (current, depth, sensitive = false) => {
|
|
4206
|
+
if (typeof current === "string") {
|
|
4207
|
+
if (sensitive && current.length > 0 && current !== REDACTED) return REDACTED;
|
|
4208
|
+
return redactText(current, options);
|
|
4209
|
+
}
|
|
4210
|
+
if (current === null || current === void 0 || typeof current === "number" || typeof current === "boolean" || typeof current === "bigint") {
|
|
4211
|
+
return current;
|
|
4212
|
+
}
|
|
4213
|
+
if (typeof current !== "object") return current;
|
|
4214
|
+
if (isBinary(current)) return current;
|
|
4215
|
+
if (current instanceof Date) return new Date(current.getTime());
|
|
4216
|
+
if (depth >= maxDepth) return TRUNCATED;
|
|
4217
|
+
if (ancestors.has(current)) return CIRCULAR;
|
|
4218
|
+
ancestors.add(current);
|
|
4219
|
+
try {
|
|
4220
|
+
return cloneObject(current, depth);
|
|
4221
|
+
} finally {
|
|
4222
|
+
ancestors.delete(current);
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
const cloneObject = (current, depth) => {
|
|
4226
|
+
if (current instanceof Error) {
|
|
4227
|
+
const error = {
|
|
4228
|
+
name: current.name,
|
|
4229
|
+
message: visit(current.message, depth + 1),
|
|
4230
|
+
stack: visit(current.stack, depth + 1)
|
|
4231
|
+
};
|
|
4232
|
+
for (const [key, child] of Object.entries(current)) {
|
|
4233
|
+
error[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4234
|
+
}
|
|
4235
|
+
return error;
|
|
4236
|
+
}
|
|
4237
|
+
if (Array.isArray(current)) {
|
|
4238
|
+
const clone2 = [];
|
|
4239
|
+
for (const child of current) {
|
|
4240
|
+
if (++entries > maxEntries) {
|
|
4241
|
+
clone2.push(TRUNCATED);
|
|
4242
|
+
break;
|
|
4243
|
+
}
|
|
4244
|
+
clone2.push(visit(child, depth + 1));
|
|
4245
|
+
}
|
|
4246
|
+
return clone2;
|
|
4247
|
+
}
|
|
4248
|
+
const record = current;
|
|
4249
|
+
if (isMediaObject(record)) return { ...record };
|
|
4250
|
+
const clone = {};
|
|
4251
|
+
for (const [key, child] of Object.entries(record)) {
|
|
4252
|
+
if (++entries > maxEntries) {
|
|
4253
|
+
clone[TRUNCATED] = true;
|
|
4254
|
+
break;
|
|
4255
|
+
}
|
|
4256
|
+
clone[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4257
|
+
}
|
|
4258
|
+
return clone;
|
|
4259
|
+
};
|
|
4260
|
+
return visit(value, 0);
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
// src/utils/json-post.ts
|
|
4264
|
+
var MAX_ERROR_MESSAGE_CHARS = 240;
|
|
4265
|
+
function abortError() {
|
|
4266
|
+
return new DOMException("Aborted", "AbortError");
|
|
4267
|
+
}
|
|
4268
|
+
function throwIfAborted(signal) {
|
|
4269
|
+
if (signal?.aborted) throw abortError();
|
|
4270
|
+
}
|
|
4271
|
+
async function postJson(request) {
|
|
4272
|
+
const { provider, signal } = request;
|
|
4273
|
+
const secrets = request.secrets?.filter((s) => typeof s === "string" && s !== "");
|
|
4274
|
+
const clean = (text2) => bound(redactText(text2, { secrets }));
|
|
4275
|
+
const fetchImpl = request.fetch ?? globalThis.fetch;
|
|
4276
|
+
throwIfAborted(signal);
|
|
4277
|
+
let response;
|
|
4278
|
+
try {
|
|
4279
|
+
response = await fetchImpl(request.url, {
|
|
4280
|
+
method: "POST",
|
|
4281
|
+
headers: request.headers,
|
|
4282
|
+
body: JSON.stringify(request.body),
|
|
4283
|
+
signal
|
|
4284
|
+
});
|
|
4285
|
+
} catch (err) {
|
|
4286
|
+
if (signal?.aborted || isAbortError2(err)) throw abortError();
|
|
4287
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
4288
|
+
throw new GGAIError(`Couldn't reach ${provider}: ${clean(detail)}`, {
|
|
4289
|
+
source: "network",
|
|
4290
|
+
cause: err
|
|
4291
|
+
});
|
|
4292
|
+
}
|
|
4293
|
+
const headerRequestId = readHeader(
|
|
4294
|
+
response.headers,
|
|
4295
|
+
"x-request-id",
|
|
4296
|
+
"request-id",
|
|
4297
|
+
"openai-request-id"
|
|
4298
|
+
);
|
|
4299
|
+
let text;
|
|
4300
|
+
try {
|
|
4301
|
+
text = await response.text();
|
|
4302
|
+
} catch (err) {
|
|
4303
|
+
if (signal?.aborted || isAbortError2(err)) throw abortError();
|
|
4304
|
+
throw new GGAIError(`Lost the connection to ${provider} while reading its response.`, {
|
|
4305
|
+
source: "network",
|
|
4306
|
+
cause: err
|
|
4307
|
+
});
|
|
4308
|
+
}
|
|
4309
|
+
if (!response.ok) {
|
|
4310
|
+
throw httpError(provider, response, text, headerRequestId, clean);
|
|
4311
|
+
}
|
|
4312
|
+
try {
|
|
4313
|
+
return {
|
|
4314
|
+
json: JSON.parse(text),
|
|
4315
|
+
...headerRequestId ? { requestId: headerRequestId } : {}
|
|
4316
|
+
};
|
|
4317
|
+
} catch {
|
|
4318
|
+
throw new ProviderError(provider, `${provider} returned a response that isn't valid JSON.`, {
|
|
4319
|
+
statusCode: response.status,
|
|
4320
|
+
...headerRequestId ? { requestId: headerRequestId } : {}
|
|
4321
|
+
});
|
|
4322
|
+
}
|
|
4323
|
+
}
|
|
4324
|
+
function httpError(provider, response, text, headerRequestId, clean) {
|
|
4325
|
+
const status = response.status;
|
|
4326
|
+
const parsed = parseErrorBody(text);
|
|
4327
|
+
const rawMessage = parsed.message ?? (parsed.isJson ? "" : text.trim());
|
|
4328
|
+
const message = !rawMessage ? emptyProviderErrorMessage(status) : isRawHtmlErrorEcho(rawMessage) ? providerHtmlErrorMessage(status) : clean(rawMessage);
|
|
4329
|
+
const requestId = headerRequestId ?? parsed.requestId;
|
|
4330
|
+
const base = {
|
|
4331
|
+
statusCode: status,
|
|
4332
|
+
...requestId ? { requestId } : {}
|
|
4333
|
+
};
|
|
4334
|
+
if (status === 401 || status === 403) {
|
|
4335
|
+
return new ProviderError(provider, message, { ...base, source: "auth" });
|
|
4336
|
+
}
|
|
4337
|
+
const codeType = `${parsed.code ?? ""} ${parsed.type ?? ""}`.toLowerCase();
|
|
4338
|
+
if (status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message)) {
|
|
4339
|
+
const stamped = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
|
|
4340
|
+
return new ProviderError(provider, stamped, base);
|
|
4341
|
+
}
|
|
4342
|
+
if (status === 429) {
|
|
4343
|
+
const retryAfter = Number(readHeader(response.headers, "retry-after"));
|
|
4344
|
+
const resetsAt = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.floor(Date.now() / 1e3) + Math.ceil(retryAfter) : void 0;
|
|
4345
|
+
return new ProviderError(provider, message, {
|
|
4346
|
+
...base,
|
|
4347
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
4348
|
+
});
|
|
4349
|
+
}
|
|
4350
|
+
return new ProviderError(provider, message, base);
|
|
4351
|
+
}
|
|
4352
|
+
function parseErrorBody(text) {
|
|
4353
|
+
let parsed;
|
|
4354
|
+
try {
|
|
4355
|
+
parsed = JSON.parse(text);
|
|
4356
|
+
} catch {
|
|
4357
|
+
return { isJson: false };
|
|
4358
|
+
}
|
|
4359
|
+
if (!isRecord(parsed)) return { isJson: true };
|
|
4360
|
+
const error = parsed.error;
|
|
4361
|
+
const errorObj = isRecord(error) ? error : void 0;
|
|
4362
|
+
const message = stringField(errorObj, "message") ?? (typeof error === "string" && error.trim() ? error.trim() : void 0) ?? stringField(parsed, "message") ?? stringField(parsed, "detail");
|
|
4363
|
+
const requestId = stringField(parsed, "request_id") ?? stringField(errorObj, "request_id");
|
|
4364
|
+
const code = stringField(errorObj, "code") ?? stringField(errorObj, "status");
|
|
4365
|
+
const type = stringField(errorObj, "type");
|
|
4366
|
+
return {
|
|
4367
|
+
isJson: true,
|
|
4368
|
+
...message ? { message } : {},
|
|
4369
|
+
...requestId ? { requestId } : {},
|
|
4370
|
+
...code ? { code } : {},
|
|
4371
|
+
...type ? { type } : {}
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
function stringField(record, key) {
|
|
4375
|
+
const value = record?.[key];
|
|
4376
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
4377
|
+
}
|
|
4378
|
+
function isRecord(value) {
|
|
4379
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4380
|
+
}
|
|
4381
|
+
function isAbortError2(err) {
|
|
4382
|
+
return (err instanceof Error || err instanceof DOMException) && err.name === "AbortError";
|
|
4383
|
+
}
|
|
4384
|
+
function bound(text) {
|
|
4385
|
+
const trimmed = text.trim();
|
|
4386
|
+
return trimmed.length > MAX_ERROR_MESSAGE_CHARS ? `${trimmed.slice(0, MAX_ERROR_MESSAGE_CHARS)}\u2026` : trimmed;
|
|
4387
|
+
}
|
|
4388
|
+
function joinUrl(baseUrl, path) {
|
|
4389
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
4390
|
+
}
|
|
4391
|
+
|
|
4392
|
+
// src/providers/gemini-embeddings.ts
|
|
4393
|
+
var GEMINI_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
4394
|
+
var GEMINI_EMBED_BATCH_SIZE = 100;
|
|
4395
|
+
var TASK_TYPES = {
|
|
4396
|
+
query: "RETRIEVAL_QUERY",
|
|
4397
|
+
document: "RETRIEVAL_DOCUMENT"
|
|
4398
|
+
};
|
|
4399
|
+
function supportsTaskType(model) {
|
|
4400
|
+
return !/^gemini-embedding-2(?:$|[-.@])/.test(model);
|
|
4401
|
+
}
|
|
4402
|
+
async function embedGemini(request) {
|
|
4403
|
+
if (!request.apiKey) {
|
|
4404
|
+
throw new GGAIError("Gemini embeddings need a Gemini API key (apiKey).", {
|
|
4405
|
+
source: "auth",
|
|
4406
|
+
hint: "Create a key in Google AI Studio. Gemini Code Assist sign-in can't be used for embeddings."
|
|
4407
|
+
});
|
|
4408
|
+
}
|
|
4409
|
+
const model = request.model.replace(/^models\//, "");
|
|
4410
|
+
const resource = `models/${model}`;
|
|
4411
|
+
const taskType = request.inputType && supportsTaskType(model) ? TASK_TYPES[request.inputType] : void 0;
|
|
4412
|
+
const embedContentConfig = {
|
|
4413
|
+
...taskType ? { taskType } : {},
|
|
4414
|
+
...request.dimensions !== void 0 ? { outputDimensionality: request.dimensions } : {}
|
|
4415
|
+
};
|
|
4416
|
+
const body = mergeProviderOptions(
|
|
4417
|
+
{
|
|
4418
|
+
requests: request.input.map((text) => ({
|
|
4419
|
+
model: resource,
|
|
4420
|
+
content: { parts: [{ text }] },
|
|
4421
|
+
...Object.keys(embedContentConfig).length > 0 ? { embedContentConfig } : {}
|
|
4422
|
+
}))
|
|
4423
|
+
},
|
|
4424
|
+
request.providerOptions
|
|
4425
|
+
);
|
|
4426
|
+
const { json, requestId } = await postJson({
|
|
4427
|
+
provider: "gemini",
|
|
4428
|
+
url: joinUrl(request.baseUrl ?? GEMINI_API_BASE_URL, `${resource}:batchEmbedContents`),
|
|
4429
|
+
headers: { "Content-Type": "application/json", "x-goog-api-key": request.apiKey },
|
|
4430
|
+
body,
|
|
4431
|
+
fetch: request.fetch,
|
|
4432
|
+
signal: request.signal,
|
|
4433
|
+
secrets: [request.apiKey]
|
|
4434
|
+
});
|
|
4435
|
+
const invalid = (detail) => new ProviderError("gemini", `Invalid embeddings response: ${detail}.`, {
|
|
4436
|
+
...requestId ? { requestId } : {}
|
|
4437
|
+
});
|
|
4438
|
+
if (!isRecord(json) || !Array.isArray(json.embeddings)) {
|
|
4439
|
+
throw invalid("missing `embeddings` array");
|
|
4440
|
+
}
|
|
4441
|
+
const embeddings = json.embeddings.map((entry) => {
|
|
4442
|
+
if (!isRecord(entry) || !Array.isArray(entry.values)) {
|
|
4443
|
+
throw invalid("an entry has no `values` array");
|
|
4444
|
+
}
|
|
4445
|
+
return entry.values;
|
|
4446
|
+
});
|
|
4447
|
+
const usage = json.usageMetadata;
|
|
4448
|
+
const promptTokens = isRecord(usage) ? usage.promptTokenCount : void 0;
|
|
4449
|
+
return {
|
|
4450
|
+
embeddings,
|
|
4451
|
+
model,
|
|
4452
|
+
...typeof promptTokens === "number" && Number.isFinite(promptTokens) ? { usage: { inputTokens: promptTokens } } : {}
|
|
4453
|
+
};
|
|
4454
|
+
}
|
|
4455
|
+
|
|
4456
|
+
// src/providers/openai-compat-retrieval.ts
|
|
4457
|
+
function jsonHeaders(apiKey) {
|
|
4458
|
+
return {
|
|
4459
|
+
"Content-Type": "application/json",
|
|
4460
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
4461
|
+
};
|
|
4462
|
+
}
|
|
4463
|
+
async function embedOpenAICompatible(request, config) {
|
|
4464
|
+
const body = mergeProviderOptions(
|
|
4465
|
+
{
|
|
4466
|
+
model: config.wireModel ?? request.model,
|
|
4467
|
+
input: request.input,
|
|
4468
|
+
// gg-ai parses float arrays only; never let a server default to base64.
|
|
4469
|
+
encoding_format: "float",
|
|
4470
|
+
...request.dimensions !== void 0 ? { dimensions: request.dimensions } : {},
|
|
4471
|
+
...request.inputType && config.inputTypeFields ? config.inputTypeFields(request.inputType) : {}
|
|
4472
|
+
},
|
|
4473
|
+
request.providerOptions
|
|
4474
|
+
);
|
|
4475
|
+
const { json, requestId } = await postJson({
|
|
4476
|
+
provider: config.provider,
|
|
4477
|
+
url: joinUrl(config.baseUrl, "embeddings"),
|
|
4478
|
+
headers: jsonHeaders(request.apiKey),
|
|
4479
|
+
body,
|
|
4480
|
+
fetch: request.fetch,
|
|
4481
|
+
signal: request.signal,
|
|
4482
|
+
secrets: [request.apiKey]
|
|
4483
|
+
});
|
|
4484
|
+
const invalid = (detail) => new ProviderError(config.provider, `Invalid embeddings response: ${detail}.`, {
|
|
4485
|
+
...requestId ? { requestId } : {}
|
|
4486
|
+
});
|
|
4487
|
+
if (!isRecord(json) || !Array.isArray(json.data)) throw invalid("missing `data` array");
|
|
4488
|
+
const items = json.data.map((item) => {
|
|
4489
|
+
if (!isRecord(item)) throw invalid("an entry in `data` is not an object");
|
|
4490
|
+
const index = item.index;
|
|
4491
|
+
if (index !== void 0 && !(typeof index === "number" && Number.isInteger(index))) {
|
|
4492
|
+
throw invalid("an entry has a non-integer `index`");
|
|
4493
|
+
}
|
|
4494
|
+
if (!Array.isArray(item.embedding)) {
|
|
4495
|
+
throw invalid("an entry's `embedding` is not an array of numbers");
|
|
4496
|
+
}
|
|
4497
|
+
return { index, embedding: item.embedding };
|
|
4498
|
+
});
|
|
4499
|
+
const withIndex = items.filter((item) => item.index !== void 0).length;
|
|
4500
|
+
let embeddings;
|
|
4501
|
+
if (withIndex === 0) {
|
|
4502
|
+
embeddings = items.map((item) => item.embedding);
|
|
4503
|
+
} else if (withIndex !== items.length) {
|
|
4504
|
+
throw invalid("some entries have an `index` and some don't");
|
|
4505
|
+
} else {
|
|
4506
|
+
const slots = new Array(items.length);
|
|
4507
|
+
for (const item of items) {
|
|
4508
|
+
const index = item.index;
|
|
4509
|
+
if (index < 0 || index >= items.length || slots[index] !== void 0) {
|
|
4510
|
+
throw invalid(`unexpected or duplicate index ${index}`);
|
|
4511
|
+
}
|
|
4512
|
+
slots[index] = item.embedding;
|
|
4513
|
+
}
|
|
4514
|
+
embeddings = slots;
|
|
4515
|
+
}
|
|
4516
|
+
const usage = json.usage;
|
|
4517
|
+
const promptTokens = isRecord(usage) ? usage.prompt_tokens : void 0;
|
|
4518
|
+
return {
|
|
4519
|
+
embeddings,
|
|
4520
|
+
...typeof json.model === "string" && json.model ? { model: json.model } : {},
|
|
4521
|
+
...typeof promptTokens === "number" && Number.isFinite(promptTokens) ? { usage: { inputTokens: promptTokens } } : {}
|
|
4522
|
+
};
|
|
4523
|
+
}
|
|
4524
|
+
async function rerankCohereCompatible(request, config) {
|
|
4525
|
+
const body = mergeProviderOptions(
|
|
4526
|
+
{
|
|
4527
|
+
model: config.wireModel ?? request.model,
|
|
4528
|
+
query: request.query,
|
|
4529
|
+
documents: request.documents,
|
|
4530
|
+
...request.topN !== void 0 ? { top_n: request.topN } : {}
|
|
4531
|
+
},
|
|
4532
|
+
request.providerOptions
|
|
4533
|
+
);
|
|
4534
|
+
const { json, requestId } = await postJson({
|
|
4535
|
+
provider: config.provider,
|
|
4536
|
+
url: joinUrl(config.baseUrl, "rerank"),
|
|
4537
|
+
headers: jsonHeaders(request.apiKey),
|
|
4538
|
+
body,
|
|
4539
|
+
fetch: request.fetch,
|
|
4540
|
+
signal: request.signal,
|
|
4541
|
+
secrets: [request.apiKey]
|
|
4542
|
+
});
|
|
4543
|
+
const invalid = (detail) => new ProviderError(config.provider, `Invalid rerank response: ${detail}.`, {
|
|
4544
|
+
...requestId ? { requestId } : {}
|
|
4545
|
+
});
|
|
4546
|
+
if (!isRecord(json) || !Array.isArray(json.results)) throw invalid("missing `results` array");
|
|
4547
|
+
const results = json.results.map((item) => {
|
|
4548
|
+
if (!isRecord(item)) throw invalid("an entry in `results` is not an object");
|
|
4549
|
+
const { index, relevance_score: score } = item;
|
|
4550
|
+
if (typeof index !== "number" || typeof score !== "number") {
|
|
4551
|
+
throw invalid("an entry is missing a numeric `index` or `relevance_score`");
|
|
4552
|
+
}
|
|
4553
|
+
return { index, score };
|
|
4554
|
+
});
|
|
4555
|
+
const usage = json.usage;
|
|
4556
|
+
const tokens = isRecord(usage) ? usage.prompt_tokens ?? usage.total_tokens : void 0;
|
|
4557
|
+
const searchUnits = isRecord(usage) ? usage.search_units : void 0;
|
|
4558
|
+
const rerankUsage = {
|
|
4559
|
+
...typeof tokens === "number" && Number.isFinite(tokens) ? { inputTokens: tokens } : {},
|
|
4560
|
+
...typeof searchUnits === "number" && Number.isFinite(searchUnits) ? { searchUnits } : {}
|
|
4561
|
+
};
|
|
4562
|
+
return {
|
|
4563
|
+
results,
|
|
4564
|
+
...typeof json.model === "string" && json.model ? { model: json.model } : {},
|
|
4565
|
+
...Object.keys(rerankUsage).length > 0 ? { usage: rerankUsage } : {}
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4064
4569
|
// src/provider-registry.ts
|
|
4065
4570
|
var ProviderRegistryImpl = class {
|
|
4066
4571
|
providers = /* @__PURE__ */ new Map();
|
|
@@ -4090,6 +4595,11 @@ var ProviderRegistryImpl = class {
|
|
|
4090
4595
|
has(name) {
|
|
4091
4596
|
return this.providers.has(name);
|
|
4092
4597
|
}
|
|
4598
|
+
/** Whether a registered provider offers a capability (`false` for unknown providers). */
|
|
4599
|
+
supports(name, capability) {
|
|
4600
|
+
const entry = this.providers.get(name);
|
|
4601
|
+
return typeof entry?.[capability] === "function";
|
|
4602
|
+
}
|
|
4093
4603
|
/** List all registered provider names. */
|
|
4094
4604
|
list() {
|
|
4095
4605
|
return [...this.providers.keys()];
|
|
@@ -4267,6 +4777,10 @@ function observePreparedContext(before, after, tools) {
|
|
|
4267
4777
|
}
|
|
4268
4778
|
|
|
4269
4779
|
// src/stream.ts
|
|
4780
|
+
var OPENAI_EMBED_BATCH_SIZE = 2048;
|
|
4781
|
+
var OPENROUTER_EMBED_BATCH_SIZE = 64;
|
|
4782
|
+
var LOCAL_EMBED_BATCH_SIZE = 64;
|
|
4783
|
+
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
4270
4784
|
var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
|
4271
4785
|
var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
|
|
4272
4786
|
var GROK_CLI_PROXY_HOST = "cli-chat-proxy.grok.com";
|
|
@@ -4287,10 +4801,20 @@ providerRegistry.register("openai", {
|
|
|
4287
4801
|
return streamOpenAICodex(options);
|
|
4288
4802
|
}
|
|
4289
4803
|
return streamOpenAI(options);
|
|
4290
|
-
}
|
|
4804
|
+
},
|
|
4805
|
+
// OpenAI API-key auth only; ChatGPT-subscription (Codex) tokens have no embeddings API.
|
|
4806
|
+
// OpenAI has no task/input-type parameter, so `inputType` is ignored.
|
|
4807
|
+
embed: (request) => embedOpenAICompatible(request, {
|
|
4808
|
+
provider: "openai",
|
|
4809
|
+
baseUrl: request.baseUrl ?? "https://api.openai.com/v1"
|
|
4810
|
+
}),
|
|
4811
|
+
embedBatchSize: OPENAI_EMBED_BATCH_SIZE
|
|
4291
4812
|
});
|
|
4292
4813
|
providerRegistry.register("gemini", {
|
|
4293
|
-
stream: (options) => streamGemini(options)
|
|
4814
|
+
stream: (options) => streamGemini(options),
|
|
4815
|
+
// Native Gemini API with an AI Studio key — not the Code Assist OAuth endpoint above.
|
|
4816
|
+
embed: (request) => embedGemini(request),
|
|
4817
|
+
embedBatchSize: GEMINI_EMBED_BATCH_SIZE
|
|
4294
4818
|
});
|
|
4295
4819
|
providerRegistry.register("glm", {
|
|
4296
4820
|
stream: (options) => streamOpenAI({
|
|
@@ -4314,7 +4838,19 @@ providerRegistry.register("deepseek", {
|
|
|
4314
4838
|
providerRegistry.register("openrouter", {
|
|
4315
4839
|
stream: (options) => streamOpenAI({
|
|
4316
4840
|
...options,
|
|
4317
|
-
baseUrl: options.baseUrl ??
|
|
4841
|
+
baseUrl: options.baseUrl ?? OPENROUTER_BASE_URL
|
|
4842
|
+
}),
|
|
4843
|
+
embed: (request) => embedOpenAICompatible(request, {
|
|
4844
|
+
provider: "openrouter",
|
|
4845
|
+
baseUrl: request.baseUrl ?? OPENROUTER_BASE_URL,
|
|
4846
|
+
inputTypeFields: (inputType) => ({
|
|
4847
|
+
input_type: inputType === "query" ? "search_query" : "search_document"
|
|
4848
|
+
})
|
|
4849
|
+
}),
|
|
4850
|
+
embedBatchSize: OPENROUTER_EMBED_BATCH_SIZE,
|
|
4851
|
+
rerank: (request) => rerankCohereCompatible(request, {
|
|
4852
|
+
provider: "openrouter",
|
|
4853
|
+
baseUrl: request.baseUrl ?? OPENROUTER_BASE_URL
|
|
4318
4854
|
})
|
|
4319
4855
|
});
|
|
4320
4856
|
providerRegistry.register("huggingface", {
|
|
@@ -4384,19 +4920,36 @@ providerRegistry.register("local", {
|
|
|
4384
4920
|
// vLLM). There is no default endpoint: the baseUrl comes from the endpoint
|
|
4385
4921
|
// credential the discovery layer wrote, so a missing one is a wiring bug, not
|
|
4386
4922
|
// something to paper over with a guess at someone else's port.
|
|
4387
|
-
stream: (options) => {
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4923
|
+
stream: (options) => streamOpenAI({
|
|
4924
|
+
...options,
|
|
4925
|
+
baseUrl: requireLocalBaseUrl(options.baseUrl),
|
|
4926
|
+
model: localWireModelId(options.model),
|
|
4927
|
+
webSearch: false
|
|
4928
|
+
}),
|
|
4929
|
+
// Every supported local server exposes OpenAI-compatible `/v1/embeddings`
|
|
4930
|
+
// (the model must be an embedding model). None has an input-type parameter.
|
|
4931
|
+
embed: (request) => embedOpenAICompatible(request, {
|
|
4932
|
+
provider: "local",
|
|
4933
|
+
baseUrl: requireLocalBaseUrl(request.baseUrl),
|
|
4934
|
+
wireModel: localWireModelId(request.model)
|
|
4935
|
+
}),
|
|
4936
|
+
embedBatchSize: LOCAL_EMBED_BATCH_SIZE,
|
|
4937
|
+
// llama.cpp (`llama-server --rerank`) and vLLM serve `/v1/rerank`; Ollama and
|
|
4938
|
+
// LM Studio don't, and answer with a 404 ProviderError.
|
|
4939
|
+
rerank: (request) => rerankCohereCompatible(request, {
|
|
4940
|
+
provider: "local",
|
|
4941
|
+
baseUrl: requireLocalBaseUrl(request.baseUrl),
|
|
4942
|
+
wireModel: localWireModelId(request.model)
|
|
4943
|
+
})
|
|
4399
4944
|
});
|
|
4945
|
+
function requireLocalBaseUrl(baseUrl) {
|
|
4946
|
+
if (!baseUrl) {
|
|
4947
|
+
throw new GGAIError(
|
|
4948
|
+
"Local provider requires a baseUrl (e.g. http://127.0.0.1:11434/v1). No local endpoint was resolved for this model \u2014 re-scan for local models."
|
|
4949
|
+
);
|
|
4950
|
+
}
|
|
4951
|
+
return baseUrl;
|
|
4952
|
+
}
|
|
4400
4953
|
function stream(options) {
|
|
4401
4954
|
const entry = providerRegistry.get(options.provider);
|
|
4402
4955
|
if (!entry) {
|
|
@@ -4455,6 +5008,121 @@ function messagesContainVideo(messages) {
|
|
|
4455
5008
|
return false;
|
|
4456
5009
|
}
|
|
4457
5010
|
|
|
5011
|
+
// src/embed.ts
|
|
5012
|
+
var CAPABILITY_LABEL = {
|
|
5013
|
+
embed: "embeddings",
|
|
5014
|
+
rerank: "reranking"
|
|
5015
|
+
};
|
|
5016
|
+
function resolveCapability(provider, capability) {
|
|
5017
|
+
const entry = providerRegistry.get(provider);
|
|
5018
|
+
if (!entry) {
|
|
5019
|
+
throw new GGAIError(
|
|
5020
|
+
`Unknown provider: "${provider}". Registered: ${providerRegistry.list().join(", ")}`
|
|
5021
|
+
);
|
|
5022
|
+
}
|
|
5023
|
+
const fn = entry[capability];
|
|
5024
|
+
if (typeof fn !== "function") {
|
|
5025
|
+
const label = CAPABILITY_LABEL[capability];
|
|
5026
|
+
const offering = providerRegistry.list().filter((name) => providerRegistry.supports(name, capability)).sort();
|
|
5027
|
+
throw new GGAIError(`Provider "${provider}" doesn't support ${label}.`, {
|
|
5028
|
+
source: "capability",
|
|
5029
|
+
hint: offering.length > 0 ? `Use a provider that offers ${label}: ${offering.join(", ")}.` : `No registered provider offers ${label}.`
|
|
5030
|
+
});
|
|
5031
|
+
}
|
|
5032
|
+
return fn;
|
|
5033
|
+
}
|
|
5034
|
+
function assertPositiveInteger(name, value) {
|
|
5035
|
+
if (value !== void 0 && !(Number.isInteger(value) && value > 0)) {
|
|
5036
|
+
throw new GGAIError(`${name} must be a positive integer (got ${String(value)}).`);
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
async function embed(options) {
|
|
5040
|
+
const embedFn = resolveCapability(options.provider, "embed");
|
|
5041
|
+
if (!Array.isArray(options.input)) {
|
|
5042
|
+
throw new GGAIError("embed() input must be an array of strings.");
|
|
5043
|
+
}
|
|
5044
|
+
assertPositiveInteger("dimensions", options.dimensions);
|
|
5045
|
+
assertPositiveInteger("batchSize", options.batchSize);
|
|
5046
|
+
if (options.input.length === 0) return { embeddings: [], model: options.model };
|
|
5047
|
+
const entry = providerRegistry.get(options.provider);
|
|
5048
|
+
const batchSize = options.batchSize ?? entry?.embedBatchSize ?? options.input.length;
|
|
5049
|
+
const { normalize, batchSize: _batchSize, ...request } = options;
|
|
5050
|
+
const embeddings = [];
|
|
5051
|
+
let model = options.model;
|
|
5052
|
+
let inputTokens;
|
|
5053
|
+
let expectedLength = options.dimensions;
|
|
5054
|
+
for (let start = 0; start < options.input.length; start += batchSize) {
|
|
5055
|
+
throwIfAborted(options.signal);
|
|
5056
|
+
const batch = options.input.slice(start, start + batchSize);
|
|
5057
|
+
const response = await embedFn({ ...request, input: batch });
|
|
5058
|
+
const invalid = (detail) => new ProviderError(options.provider, `Invalid embeddings response: ${detail}.`);
|
|
5059
|
+
if (!Array.isArray(response.embeddings) || response.embeddings.length !== batch.length) {
|
|
5060
|
+
const count = Array.isArray(response.embeddings) ? response.embeddings.length : 0;
|
|
5061
|
+
throw invalid(`expected ${batch.length} embeddings, got ${count}`);
|
|
5062
|
+
}
|
|
5063
|
+
for (const vector of response.embeddings) {
|
|
5064
|
+
if (!Array.isArray(vector) || vector.length === 0) throw invalid("received an empty vector");
|
|
5065
|
+
if (!vector.every((value) => typeof value === "number" && Number.isFinite(value))) {
|
|
5066
|
+
throw invalid("received a vector with non-numeric values");
|
|
5067
|
+
}
|
|
5068
|
+
if (expectedLength === void 0) {
|
|
5069
|
+
expectedLength = vector.length;
|
|
5070
|
+
} else if (vector.length !== expectedLength) {
|
|
5071
|
+
throw invalid(
|
|
5072
|
+
options.dimensions !== void 0 ? `requested ${options.dimensions} dimensions, got a vector with ${vector.length}` : `vectors have different lengths (${expectedLength} and ${vector.length})`
|
|
5073
|
+
);
|
|
5074
|
+
}
|
|
5075
|
+
embeddings.push(normalize ? l2Normalize(vector) : vector);
|
|
5076
|
+
}
|
|
5077
|
+
if (response.model) model = response.model;
|
|
5078
|
+
if (response.usage) inputTokens = (inputTokens ?? 0) + response.usage.inputTokens;
|
|
5079
|
+
}
|
|
5080
|
+
return {
|
|
5081
|
+
embeddings,
|
|
5082
|
+
model,
|
|
5083
|
+
...inputTokens !== void 0 ? { usage: { inputTokens } } : {}
|
|
5084
|
+
};
|
|
5085
|
+
}
|
|
5086
|
+
async function rerank(options) {
|
|
5087
|
+
const rerankFn = resolveCapability(options.provider, "rerank");
|
|
5088
|
+
if (!Array.isArray(options.documents)) {
|
|
5089
|
+
throw new GGAIError("rerank() documents must be an array of strings.");
|
|
5090
|
+
}
|
|
5091
|
+
assertPositiveInteger("topN", options.topN);
|
|
5092
|
+
if (options.documents.length === 0) return { results: [], model: options.model };
|
|
5093
|
+
throwIfAborted(options.signal);
|
|
5094
|
+
const response = await rerankFn(options);
|
|
5095
|
+
const invalid = (detail) => new ProviderError(options.provider, `Invalid rerank response: ${detail}.`);
|
|
5096
|
+
if (!Array.isArray(response.results)) throw invalid("missing results");
|
|
5097
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5098
|
+
const results = response.results.map(({ index, score }) => {
|
|
5099
|
+
if (!Number.isInteger(index) || index < 0 || index >= options.documents.length) {
|
|
5100
|
+
throw invalid(
|
|
5101
|
+
`index ${String(index)} is out of range for ${options.documents.length} documents`
|
|
5102
|
+
);
|
|
5103
|
+
}
|
|
5104
|
+
if (seen.has(index)) throw invalid(`index ${index} appears more than once`);
|
|
5105
|
+
if (typeof score !== "number" || !Number.isFinite(score)) {
|
|
5106
|
+
throw invalid(`document ${index} has a non-numeric score`);
|
|
5107
|
+
}
|
|
5108
|
+
seen.add(index);
|
|
5109
|
+
return { index, score };
|
|
5110
|
+
});
|
|
5111
|
+
results.sort((a, b) => b.score - a.score || a.index - b.index);
|
|
5112
|
+
return {
|
|
5113
|
+
results: options.topN !== void 0 ? results.slice(0, options.topN) : results,
|
|
5114
|
+
model: response.model ?? options.model,
|
|
5115
|
+
...response.usage ? { usage: response.usage } : {}
|
|
5116
|
+
};
|
|
5117
|
+
}
|
|
5118
|
+
function l2Normalize(vector) {
|
|
5119
|
+
let sumOfSquares = 0;
|
|
5120
|
+
for (const value of vector) sumOfSquares += value * value;
|
|
5121
|
+
if (sumOfSquares === 0) return [...vector];
|
|
5122
|
+
const norm = Math.sqrt(sumOfSquares);
|
|
5123
|
+
return vector.map((value) => value / norm);
|
|
5124
|
+
}
|
|
5125
|
+
|
|
4458
5126
|
// src/chat-error.ts
|
|
4459
5127
|
function causeCode(error) {
|
|
4460
5128
|
if (!(error instanceof Error) || !error.cause || typeof error.cause !== "object") return "";
|
|
@@ -4696,143 +5364,6 @@ Original: ${message}`;
|
|
|
4696
5364
|
return message;
|
|
4697
5365
|
}
|
|
4698
5366
|
|
|
4699
|
-
// src/redaction.ts
|
|
4700
|
-
var REDACTED = "[REDACTED]";
|
|
4701
|
-
var TRUNCATED = "[TRUNCATED]";
|
|
4702
|
-
var CIRCULAR = "[CIRCULAR]";
|
|
4703
|
-
var SENSITIVE_NAME = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret)(?:$|[_-])/i;
|
|
4704
|
-
var ENV_SECRET_ASSIGNMENT = /\b((?:[A-Z0-9]+_)*(?:API_?KEY|ACCESS_TOKEN|REFRESH_TOKEN|TOKEN|KEY|AUTH|AUTHORIZATION|BEARER|CREDENTIALS?|PASSWORD|PASSWD|SECRET))\b(\s*[=:]\s*)(["']?)(?!\$|process\.env|os\.environ|import\.meta|env\.)(?=[^\s,"';}]*\d)([^\s,"';}=$][^\s,"';}]{7,})\3/g;
|
|
4705
|
-
var COMPACT_SECRET_ASSIGNMENT = /\b((?:[a-z0-9]+[_-])*(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|auth|authorization|credentials?|password|passwd|secret)|(?:[a-z0-9]+[_-])+key)=(["']?)(?!\$)([^\s,"'&;}=][^\s,"'&;}]{7,})\2/gi;
|
|
4706
|
-
var URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#@:"'`<>]*:[^\s/?#"'`<>]+@/gi;
|
|
4707
|
-
var URL_USERINFO_TO_FIRST_AT = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]*:[^\s/@]+@/gi;
|
|
4708
|
-
function escaped(value) {
|
|
4709
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4710
|
-
}
|
|
4711
|
-
function normalizedSecrets(secrets) {
|
|
4712
|
-
if (!secrets) return [];
|
|
4713
|
-
return [...new Set([...secrets].filter((value) => value.length >= 8 && value !== REDACTED))].sort(
|
|
4714
|
-
(a, b) => b.length - a.length
|
|
4715
|
-
);
|
|
4716
|
-
}
|
|
4717
|
-
function environmentSecrets(env) {
|
|
4718
|
-
const values = /* @__PURE__ */ new Set();
|
|
4719
|
-
for (const [name, value] of Object.entries(env)) {
|
|
4720
|
-
if (!value || value.length < 8 || value === REDACTED || !SENSITIVE_NAME.test(name)) continue;
|
|
4721
|
-
values.add(value);
|
|
4722
|
-
}
|
|
4723
|
-
return [...values].sort((a, b) => b.length - a.length);
|
|
4724
|
-
}
|
|
4725
|
-
function redactText(text, options = {}) {
|
|
4726
|
-
let result = text;
|
|
4727
|
-
result = result.replace(
|
|
4728
|
-
/-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,
|
|
4729
|
-
REDACTED
|
|
4730
|
-
);
|
|
4731
|
-
result = result.replace(URL_USERINFO, `$1${REDACTED}@`);
|
|
4732
|
-
result = result.replace(URL_USERINFO_TO_FIRST_AT, `$1${REDACTED}@`);
|
|
4733
|
-
result = result.replace(
|
|
4734
|
-
/\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[^\s,;]+/gi,
|
|
4735
|
-
`$1${REDACTED}`
|
|
4736
|
-
);
|
|
4737
|
-
result = result.replace(
|
|
4738
|
-
/\b(bearer|basic)\s+(?![A-Za-z][a-z]{0,14}\b)[A-Za-z0-9+/_.=-]{8,}/gi,
|
|
4739
|
-
`$1 ${REDACTED}`
|
|
4740
|
-
);
|
|
4741
|
-
result = result.replace(/\b(cookie|set-cookie)(\s*:\s*)[^\s=;:]+=[^\r\n]*/gi, `$1$2${REDACTED}`);
|
|
4742
|
-
result = result.replace(
|
|
4743
|
-
/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,
|
|
4744
|
-
REDACTED
|
|
4745
|
-
);
|
|
4746
|
-
result = result.replace(
|
|
4747
|
-
/\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|gh[pousr]_|github_pat_|AIza)[A-Za-z0-9_-]{12,}\b/g,
|
|
4748
|
-
REDACTED
|
|
4749
|
-
);
|
|
4750
|
-
result = result.replace(
|
|
4751
|
-
ENV_SECRET_ASSIGNMENT,
|
|
4752
|
-
(_match, name, separator, quote) => `${name}${separator}${quote}${REDACTED}${quote}`
|
|
4753
|
-
);
|
|
4754
|
-
result = result.replace(
|
|
4755
|
-
COMPACT_SECRET_ASSIGNMENT,
|
|
4756
|
-
(_match, name, quote) => `${name}=${quote}${REDACTED}${quote}`
|
|
4757
|
-
);
|
|
4758
|
-
for (const secret of normalizedSecrets(options.secrets)) {
|
|
4759
|
-
result = result.replace(new RegExp(escaped(secret), "g"), REDACTED);
|
|
4760
|
-
}
|
|
4761
|
-
const maxStringLength = options.maxStringLength ?? 1e6;
|
|
4762
|
-
if (result.length > maxStringLength) {
|
|
4763
|
-
result = `${result.slice(0, maxStringLength)}${TRUNCATED}`;
|
|
4764
|
-
}
|
|
4765
|
-
return result;
|
|
4766
|
-
}
|
|
4767
|
-
function isBinary(value) {
|
|
4768
|
-
return value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob;
|
|
4769
|
-
}
|
|
4770
|
-
function isMediaObject(value) {
|
|
4771
|
-
return (value.type === "image" || value.type === "video") && (typeof value.data === "string" || typeof value.url === "string");
|
|
4772
|
-
}
|
|
4773
|
-
function redactValue(value, options = {}) {
|
|
4774
|
-
const maxDepth = options.maxDepth ?? 20;
|
|
4775
|
-
const maxEntries = options.maxEntries ?? 1e4;
|
|
4776
|
-
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
4777
|
-
let entries = 0;
|
|
4778
|
-
const visit = (current, depth, sensitive = false) => {
|
|
4779
|
-
if (typeof current === "string") {
|
|
4780
|
-
if (sensitive && current.length > 0 && current !== REDACTED) return REDACTED;
|
|
4781
|
-
return redactText(current, options);
|
|
4782
|
-
}
|
|
4783
|
-
if (current === null || current === void 0 || typeof current === "number" || typeof current === "boolean" || typeof current === "bigint") {
|
|
4784
|
-
return current;
|
|
4785
|
-
}
|
|
4786
|
-
if (typeof current !== "object") return current;
|
|
4787
|
-
if (isBinary(current)) return current;
|
|
4788
|
-
if (current instanceof Date) return new Date(current.getTime());
|
|
4789
|
-
if (depth >= maxDepth) return TRUNCATED;
|
|
4790
|
-
if (ancestors.has(current)) return CIRCULAR;
|
|
4791
|
-
ancestors.add(current);
|
|
4792
|
-
try {
|
|
4793
|
-
return cloneObject(current, depth);
|
|
4794
|
-
} finally {
|
|
4795
|
-
ancestors.delete(current);
|
|
4796
|
-
}
|
|
4797
|
-
};
|
|
4798
|
-
const cloneObject = (current, depth) => {
|
|
4799
|
-
if (current instanceof Error) {
|
|
4800
|
-
const error = {
|
|
4801
|
-
name: current.name,
|
|
4802
|
-
message: visit(current.message, depth + 1),
|
|
4803
|
-
stack: visit(current.stack, depth + 1)
|
|
4804
|
-
};
|
|
4805
|
-
for (const [key, child] of Object.entries(current)) {
|
|
4806
|
-
error[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4807
|
-
}
|
|
4808
|
-
return error;
|
|
4809
|
-
}
|
|
4810
|
-
if (Array.isArray(current)) {
|
|
4811
|
-
const clone2 = [];
|
|
4812
|
-
for (const child of current) {
|
|
4813
|
-
if (++entries > maxEntries) {
|
|
4814
|
-
clone2.push(TRUNCATED);
|
|
4815
|
-
break;
|
|
4816
|
-
}
|
|
4817
|
-
clone2.push(visit(child, depth + 1));
|
|
4818
|
-
}
|
|
4819
|
-
return clone2;
|
|
4820
|
-
}
|
|
4821
|
-
const record = current;
|
|
4822
|
-
if (isMediaObject(record)) return { ...record };
|
|
4823
|
-
const clone = {};
|
|
4824
|
-
for (const [key, child] of Object.entries(record)) {
|
|
4825
|
-
if (++entries > maxEntries) {
|
|
4826
|
-
clone[TRUNCATED] = true;
|
|
4827
|
-
break;
|
|
4828
|
-
}
|
|
4829
|
-
clone[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4830
|
-
}
|
|
4831
|
-
return clone;
|
|
4832
|
-
};
|
|
4833
|
-
return visit(value, 0);
|
|
4834
|
-
}
|
|
4835
|
-
|
|
4836
5367
|
// src/providers/palsu.ts
|
|
4837
5368
|
function palsuText(text) {
|
|
4838
5369
|
return { role: "assistant", content: text ? [{ type: "text", text }] : [] };
|
|
@@ -4913,7 +5444,8 @@ function computeCacheUsage(current, previous) {
|
|
|
4913
5444
|
function registerPalsuProvider(config) {
|
|
4914
5445
|
const name = config?.name ?? "palsu";
|
|
4915
5446
|
const responses = [];
|
|
4916
|
-
const state = { callCount: 0 };
|
|
5447
|
+
const state = { callCount: 0, embedCallCount: 0, rerankCallCount: 0 };
|
|
5448
|
+
const embedDimensions = config?.embedDimensions ?? PALSU_DEFAULT_EMBED_DIMENSIONS;
|
|
4917
5449
|
const defaultResponse = config?.defaultResponse ?? palsuText("");
|
|
4918
5450
|
const enableCache = config?.promptCache ?? false;
|
|
4919
5451
|
let lastMessagesSerialized = null;
|
|
@@ -4981,10 +5513,72 @@ function registerPalsuProvider(config) {
|
|
|
4981
5513
|
return yield* simulateStream(message, stopReason, options.signal, cacheUsage);
|
|
4982
5514
|
})();
|
|
4983
5515
|
return new StreamResult(gen, options.signal);
|
|
5516
|
+
},
|
|
5517
|
+
async embed(request) {
|
|
5518
|
+
throwIfAborted(request.signal);
|
|
5519
|
+
state.embedCallCount++;
|
|
5520
|
+
const dimensions = request.dimensions ?? embedDimensions;
|
|
5521
|
+
return {
|
|
5522
|
+
embeddings: request.input.map((text) => palsuEmbedding(text, dimensions)),
|
|
5523
|
+
model: request.model,
|
|
5524
|
+
usage: { inputTokens: request.input.reduce((n, text) => n + tokenize(text).length, 0) }
|
|
5525
|
+
};
|
|
5526
|
+
},
|
|
5527
|
+
...config?.embedBatchSize !== void 0 ? { embedBatchSize: config.embedBatchSize } : {},
|
|
5528
|
+
async rerank(request) {
|
|
5529
|
+
throwIfAborted(request.signal);
|
|
5530
|
+
state.rerankCallCount++;
|
|
5531
|
+
return {
|
|
5532
|
+
results: request.documents.map((document, index) => ({
|
|
5533
|
+
index,
|
|
5534
|
+
score: palsuRerankScore(request.query, document)
|
|
5535
|
+
})),
|
|
5536
|
+
model: request.model
|
|
5537
|
+
};
|
|
4984
5538
|
}
|
|
4985
5539
|
});
|
|
4986
5540
|
return handle;
|
|
4987
5541
|
}
|
|
5542
|
+
var PALSU_DEFAULT_EMBED_DIMENSIONS = 64;
|
|
5543
|
+
function tokenize(text) {
|
|
5544
|
+
return text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
5545
|
+
}
|
|
5546
|
+
function fnv1a(text) {
|
|
5547
|
+
let hash = 2166136261;
|
|
5548
|
+
for (let i = 0; i < text.length; i++) {
|
|
5549
|
+
hash ^= text.charCodeAt(i);
|
|
5550
|
+
hash = Math.imul(hash, 16777619);
|
|
5551
|
+
}
|
|
5552
|
+
return hash >>> 0;
|
|
5553
|
+
}
|
|
5554
|
+
function palsuEmbedding(text, dimensions = PALSU_DEFAULT_EMBED_DIMENSIONS) {
|
|
5555
|
+
const vector = new Array(dimensions).fill(0);
|
|
5556
|
+
const tokens = tokenize(text);
|
|
5557
|
+
if (tokens.length === 0) {
|
|
5558
|
+
vector[0] = 1;
|
|
5559
|
+
return vector;
|
|
5560
|
+
}
|
|
5561
|
+
for (const token of tokens) {
|
|
5562
|
+
const hash = fnv1a(token);
|
|
5563
|
+
const slot = hash % dimensions;
|
|
5564
|
+
vector[slot] = (vector[slot] ?? 0) + (hash >>> 31 === 0 ? 1 : -1);
|
|
5565
|
+
}
|
|
5566
|
+
const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));
|
|
5567
|
+
if (norm === 0) {
|
|
5568
|
+
vector.fill(0);
|
|
5569
|
+
vector[0] = 1;
|
|
5570
|
+
return vector;
|
|
5571
|
+
}
|
|
5572
|
+
return vector.map((value) => value / norm);
|
|
5573
|
+
}
|
|
5574
|
+
function palsuRerankScore(query, document) {
|
|
5575
|
+
const queryTokens = new Set(tokenize(query));
|
|
5576
|
+
if (queryTokens.size === 0) return 0;
|
|
5577
|
+
const documentTokens = new Set(tokenize(document));
|
|
5578
|
+
let hits = 0;
|
|
5579
|
+
for (const token of queryTokens) if (documentTokens.has(token)) hits++;
|
|
5580
|
+
return hits / queryTokens.size;
|
|
5581
|
+
}
|
|
4988
5582
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4989
5583
|
0 && (module.exports = {
|
|
4990
5584
|
EventStream,
|
|
@@ -4996,6 +5590,7 @@ function registerPalsuProvider(config) {
|
|
|
4996
5590
|
clampProviderContextImages,
|
|
4997
5591
|
classifyProviderError,
|
|
4998
5592
|
dropInvalidToolCalls,
|
|
5593
|
+
embed,
|
|
4999
5594
|
environmentSecrets,
|
|
5000
5595
|
formatChatError,
|
|
5001
5596
|
formatError,
|
|
@@ -5006,6 +5601,8 @@ function registerPalsuProvider(config) {
|
|
|
5006
5601
|
isValidToolCallName,
|
|
5007
5602
|
localWireModelId,
|
|
5008
5603
|
palsuAssistantMessage,
|
|
5604
|
+
palsuEmbedding,
|
|
5605
|
+
palsuRerankScore,
|
|
5009
5606
|
palsuText,
|
|
5010
5607
|
palsuThinking,
|
|
5011
5608
|
palsuToolCall,
|
|
@@ -5014,6 +5611,7 @@ function registerPalsuProvider(config) {
|
|
|
5014
5611
|
redactText,
|
|
5015
5612
|
redactValue,
|
|
5016
5613
|
registerPalsuProvider,
|
|
5614
|
+
rerank,
|
|
5017
5615
|
resolveToolSchema,
|
|
5018
5616
|
sanitizeMessagesForWire,
|
|
5019
5617
|
setProviderDiagnostic,
|