@kenkaiiii/gg-ai 5.76.0 → 5.77.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 +749 -165
- 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 +745 -165
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -38,7 +38,7 @@ var ProviderError = class extends GGAIError {
|
|
|
38
38
|
resetsAt;
|
|
39
39
|
constructor(provider, message, options) {
|
|
40
40
|
super(message, {
|
|
41
|
-
source: "provider",
|
|
41
|
+
source: options?.source ?? "provider",
|
|
42
42
|
requestId: options?.requestId,
|
|
43
43
|
hint: options?.hint,
|
|
44
44
|
cause: options?.cause
|
|
@@ -435,6 +435,46 @@ var StreamResult = class _StreamResult {
|
|
|
435
435
|
}
|
|
436
436
|
};
|
|
437
437
|
|
|
438
|
+
// src/utils/provider-options.ts
|
|
439
|
+
var RESERVED_BODY_FIELDS = /* @__PURE__ */ new Set([
|
|
440
|
+
"contents",
|
|
441
|
+
"dimensions",
|
|
442
|
+
"documents",
|
|
443
|
+
"encoding_format",
|
|
444
|
+
"input",
|
|
445
|
+
"messages",
|
|
446
|
+
"model",
|
|
447
|
+
"query",
|
|
448
|
+
"requests",
|
|
449
|
+
"stream",
|
|
450
|
+
"system",
|
|
451
|
+
"tools",
|
|
452
|
+
"top_n",
|
|
453
|
+
// Never copy prototype-shaped keys onto a request body.
|
|
454
|
+
"__proto__",
|
|
455
|
+
"constructor",
|
|
456
|
+
"prototype"
|
|
457
|
+
]);
|
|
458
|
+
function mergeProviderOptions(body, providerOptions) {
|
|
459
|
+
if (!providerOptions) return body;
|
|
460
|
+
const base = body;
|
|
461
|
+
const extra = {};
|
|
462
|
+
for (const key of Object.keys(providerOptions).sort()) {
|
|
463
|
+
if (RESERVED_BODY_FIELDS.has(key)) continue;
|
|
464
|
+
if (Object.hasOwn(base, key) && base[key] !== void 0) continue;
|
|
465
|
+
const value = providerOptions[key];
|
|
466
|
+
if (value === void 0) continue;
|
|
467
|
+
Object.defineProperty(extra, key, {
|
|
468
|
+
value,
|
|
469
|
+
enumerable: true,
|
|
470
|
+
writable: true,
|
|
471
|
+
configurable: true
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
if (Object.keys(extra).length === 0) return body;
|
|
475
|
+
return { ...base, ...extra };
|
|
476
|
+
}
|
|
477
|
+
|
|
438
478
|
// src/utils/zod-to-json-schema.ts
|
|
439
479
|
import { z } from "zod";
|
|
440
480
|
var schemaCache = /* @__PURE__ */ new WeakMap();
|
|
@@ -1677,7 +1717,7 @@ async function* runStream(options) {
|
|
|
1677
1717
|
}
|
|
1678
1718
|
maxTokens = 1;
|
|
1679
1719
|
}
|
|
1680
|
-
const
|
|
1720
|
+
const baseParams = {
|
|
1681
1721
|
model: options.model,
|
|
1682
1722
|
max_tokens: maxTokens,
|
|
1683
1723
|
messages,
|
|
@@ -1720,6 +1760,7 @@ async function* runStream(options) {
|
|
|
1720
1760
|
...useServerFallback ? { fallbacks: "default" } : {},
|
|
1721
1761
|
stream: useStreaming
|
|
1722
1762
|
};
|
|
1763
|
+
const params = mergeProviderOptions(baseParams, options.providerOptions);
|
|
1723
1764
|
const hasAdaptiveThinking = isAdaptiveThinkingModel(options.model);
|
|
1724
1765
|
const betaHeaders = [
|
|
1725
1766
|
...isOAuth ? ["claude-code-20250219", "oauth-2025-04-20"] : [],
|
|
@@ -2425,20 +2466,21 @@ async function* runStream2(options) {
|
|
|
2425
2466
|
params.thinking = { type: "disabled" };
|
|
2426
2467
|
}
|
|
2427
2468
|
}
|
|
2469
|
+
const requestParams = mergeProviderOptions(params, options.providerOptions);
|
|
2428
2470
|
if (getEnvironment()?.GGAI_DUMP_REQUEST) {
|
|
2429
2471
|
const fs = await import("fs");
|
|
2430
2472
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2431
2473
|
const dumpPath = `/tmp/ggai-request-${ts}.json`;
|
|
2432
|
-
fs.writeFileSync(dumpPath, JSON.stringify(
|
|
2474
|
+
fs.writeFileSync(dumpPath, JSON.stringify(requestParams, null, 2));
|
|
2433
2475
|
fs.appendFileSync(
|
|
2434
2476
|
"/tmp/ggai-requests.log",
|
|
2435
|
-
`[${ts}] ${dumpPath} messages=${
|
|
2477
|
+
`[${ts}] ${dumpPath} messages=${requestParams.messages.length}
|
|
2436
2478
|
`
|
|
2437
2479
|
);
|
|
2438
2480
|
}
|
|
2439
2481
|
if (!useStreaming) {
|
|
2440
2482
|
try {
|
|
2441
|
-
const completion = await client.chat.completions.create(
|
|
2483
|
+
const completion = await client.chat.completions.create(requestParams, {
|
|
2442
2484
|
signal: options.signal ?? void 0
|
|
2443
2485
|
});
|
|
2444
2486
|
yield* synthesizeEventsFromCompletion(completion, !!options.thinking, endpointKey);
|
|
@@ -2449,7 +2491,7 @@ async function* runStream2(options) {
|
|
|
2449
2491
|
}
|
|
2450
2492
|
let stream2;
|
|
2451
2493
|
try {
|
|
2452
|
-
stream2 = await client.chat.completions.create(
|
|
2494
|
+
stream2 = await client.chat.completions.create(requestParams, {
|
|
2453
2495
|
signal: options.signal ?? void 0
|
|
2454
2496
|
});
|
|
2455
2497
|
} catch (err) {
|
|
@@ -2937,7 +2979,9 @@ async function* runStream3(options, retriedWithoutReasoning = false) {
|
|
|
2937
2979
|
headers["session_id"] = transportSessionId;
|
|
2938
2980
|
headers["x-client-request-id"] = transportSessionId;
|
|
2939
2981
|
}
|
|
2940
|
-
const encodedRequest = await encodeCodexRequest(
|
|
2982
|
+
const encodedRequest = await encodeCodexRequest(
|
|
2983
|
+
mergeProviderOptions(body, options.providerOptions)
|
|
2984
|
+
);
|
|
2941
2985
|
if (encodedRequest.compressed) headers["Content-Encoding"] = "zstd";
|
|
2942
2986
|
providerDiag("codex_request_body", {
|
|
2943
2987
|
rawBytes: encodedRequest.rawBytes,
|
|
@@ -3652,11 +3696,11 @@ function stripUnsupportedSchemaFields(value) {
|
|
|
3652
3696
|
["exclusiveMinimum", "minimum", 1],
|
|
3653
3697
|
["exclusiveMaximum", "maximum", -1]
|
|
3654
3698
|
]) {
|
|
3655
|
-
const
|
|
3656
|
-
if (
|
|
3699
|
+
const bound2 = value[key];
|
|
3700
|
+
if (bound2 === void 0) continue;
|
|
3657
3701
|
delete value[key];
|
|
3658
|
-
if (typeof
|
|
3659
|
-
value[target] = value.type === "integer" ?
|
|
3702
|
+
if (typeof bound2 === "number" && value[target] === void 0) {
|
|
3703
|
+
value[target] = value.type === "integer" ? bound2 + step : bound2;
|
|
3660
3704
|
}
|
|
3661
3705
|
}
|
|
3662
3706
|
for (const item of Object.values(value)) {
|
|
@@ -3750,7 +3794,7 @@ function buildRequestPlan(options, method) {
|
|
|
3750
3794
|
throw new ProviderError("gemini", formatUnsupportedModelMessage(options.model));
|
|
3751
3795
|
}
|
|
3752
3796
|
const projectId = getGoogleProject(options);
|
|
3753
|
-
const request = buildGenerateRequest(options);
|
|
3797
|
+
const request = mergeProviderOptions(buildGenerateRequest(options), options.providerOptions);
|
|
3754
3798
|
return {
|
|
3755
3799
|
url: getCodeAssistEndpoint(method),
|
|
3756
3800
|
headers: {
|
|
@@ -3988,6 +4032,449 @@ function toError3(err) {
|
|
|
3988
4032
|
return new ProviderError("gemini", String(err));
|
|
3989
4033
|
}
|
|
3990
4034
|
|
|
4035
|
+
// src/redaction.ts
|
|
4036
|
+
var REDACTED = "[REDACTED]";
|
|
4037
|
+
var TRUNCATED = "[TRUNCATED]";
|
|
4038
|
+
var CIRCULAR = "[CIRCULAR]";
|
|
4039
|
+
var SENSITIVE_NAME = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret)(?:$|[_-])/i;
|
|
4040
|
+
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;
|
|
4041
|
+
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;
|
|
4042
|
+
var URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#@:"'`<>]*:[^\s/?#"'`<>]+@/gi;
|
|
4043
|
+
var URL_USERINFO_TO_FIRST_AT = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]*:[^\s/@]+@/gi;
|
|
4044
|
+
function escaped(value) {
|
|
4045
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4046
|
+
}
|
|
4047
|
+
function normalizedSecrets(secrets) {
|
|
4048
|
+
if (!secrets) return [];
|
|
4049
|
+
return [...new Set([...secrets].filter((value) => value.length >= 8 && value !== REDACTED))].sort(
|
|
4050
|
+
(a, b) => b.length - a.length
|
|
4051
|
+
);
|
|
4052
|
+
}
|
|
4053
|
+
function environmentSecrets(env) {
|
|
4054
|
+
const values = /* @__PURE__ */ new Set();
|
|
4055
|
+
for (const [name, value] of Object.entries(env)) {
|
|
4056
|
+
if (!value || value.length < 8 || value === REDACTED || !SENSITIVE_NAME.test(name)) continue;
|
|
4057
|
+
values.add(value);
|
|
4058
|
+
}
|
|
4059
|
+
return [...values].sort((a, b) => b.length - a.length);
|
|
4060
|
+
}
|
|
4061
|
+
function redactText(text, options = {}) {
|
|
4062
|
+
let result = text;
|
|
4063
|
+
result = result.replace(
|
|
4064
|
+
/-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,
|
|
4065
|
+
REDACTED
|
|
4066
|
+
);
|
|
4067
|
+
result = result.replace(URL_USERINFO, `$1${REDACTED}@`);
|
|
4068
|
+
result = result.replace(URL_USERINFO_TO_FIRST_AT, `$1${REDACTED}@`);
|
|
4069
|
+
result = result.replace(
|
|
4070
|
+
/\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[^\s,;]+/gi,
|
|
4071
|
+
`$1${REDACTED}`
|
|
4072
|
+
);
|
|
4073
|
+
result = result.replace(
|
|
4074
|
+
/\b(bearer|basic)\s+(?![A-Za-z][a-z]{0,14}\b)[A-Za-z0-9+/_.=-]{8,}/gi,
|
|
4075
|
+
`$1 ${REDACTED}`
|
|
4076
|
+
);
|
|
4077
|
+
result = result.replace(/\b(cookie|set-cookie)(\s*:\s*)[^\s=;:]+=[^\r\n]*/gi, `$1$2${REDACTED}`);
|
|
4078
|
+
result = result.replace(
|
|
4079
|
+
/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,
|
|
4080
|
+
REDACTED
|
|
4081
|
+
);
|
|
4082
|
+
result = result.replace(
|
|
4083
|
+
/\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|gh[pousr]_|github_pat_|AIza)[A-Za-z0-9_-]{12,}\b/g,
|
|
4084
|
+
REDACTED
|
|
4085
|
+
);
|
|
4086
|
+
result = result.replace(
|
|
4087
|
+
ENV_SECRET_ASSIGNMENT,
|
|
4088
|
+
(_match, name, separator, quote) => `${name}${separator}${quote}${REDACTED}${quote}`
|
|
4089
|
+
);
|
|
4090
|
+
result = result.replace(
|
|
4091
|
+
COMPACT_SECRET_ASSIGNMENT,
|
|
4092
|
+
(_match, name, quote) => `${name}=${quote}${REDACTED}${quote}`
|
|
4093
|
+
);
|
|
4094
|
+
for (const secret of normalizedSecrets(options.secrets)) {
|
|
4095
|
+
result = result.replace(new RegExp(escaped(secret), "g"), REDACTED);
|
|
4096
|
+
}
|
|
4097
|
+
const maxStringLength = options.maxStringLength ?? 1e6;
|
|
4098
|
+
if (result.length > maxStringLength) {
|
|
4099
|
+
result = `${result.slice(0, maxStringLength)}${TRUNCATED}`;
|
|
4100
|
+
}
|
|
4101
|
+
return result;
|
|
4102
|
+
}
|
|
4103
|
+
function isBinary(value) {
|
|
4104
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob;
|
|
4105
|
+
}
|
|
4106
|
+
function isMediaObject(value) {
|
|
4107
|
+
return (value.type === "image" || value.type === "video") && (typeof value.data === "string" || typeof value.url === "string");
|
|
4108
|
+
}
|
|
4109
|
+
function redactValue(value, options = {}) {
|
|
4110
|
+
const maxDepth = options.maxDepth ?? 20;
|
|
4111
|
+
const maxEntries = options.maxEntries ?? 1e4;
|
|
4112
|
+
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
4113
|
+
let entries = 0;
|
|
4114
|
+
const visit = (current, depth, sensitive = false) => {
|
|
4115
|
+
if (typeof current === "string") {
|
|
4116
|
+
if (sensitive && current.length > 0 && current !== REDACTED) return REDACTED;
|
|
4117
|
+
return redactText(current, options);
|
|
4118
|
+
}
|
|
4119
|
+
if (current === null || current === void 0 || typeof current === "number" || typeof current === "boolean" || typeof current === "bigint") {
|
|
4120
|
+
return current;
|
|
4121
|
+
}
|
|
4122
|
+
if (typeof current !== "object") return current;
|
|
4123
|
+
if (isBinary(current)) return current;
|
|
4124
|
+
if (current instanceof Date) return new Date(current.getTime());
|
|
4125
|
+
if (depth >= maxDepth) return TRUNCATED;
|
|
4126
|
+
if (ancestors.has(current)) return CIRCULAR;
|
|
4127
|
+
ancestors.add(current);
|
|
4128
|
+
try {
|
|
4129
|
+
return cloneObject(current, depth);
|
|
4130
|
+
} finally {
|
|
4131
|
+
ancestors.delete(current);
|
|
4132
|
+
}
|
|
4133
|
+
};
|
|
4134
|
+
const cloneObject = (current, depth) => {
|
|
4135
|
+
if (current instanceof Error) {
|
|
4136
|
+
const error = {
|
|
4137
|
+
name: current.name,
|
|
4138
|
+
message: visit(current.message, depth + 1),
|
|
4139
|
+
stack: visit(current.stack, depth + 1)
|
|
4140
|
+
};
|
|
4141
|
+
for (const [key, child] of Object.entries(current)) {
|
|
4142
|
+
error[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4143
|
+
}
|
|
4144
|
+
return error;
|
|
4145
|
+
}
|
|
4146
|
+
if (Array.isArray(current)) {
|
|
4147
|
+
const clone2 = [];
|
|
4148
|
+
for (const child of current) {
|
|
4149
|
+
if (++entries > maxEntries) {
|
|
4150
|
+
clone2.push(TRUNCATED);
|
|
4151
|
+
break;
|
|
4152
|
+
}
|
|
4153
|
+
clone2.push(visit(child, depth + 1));
|
|
4154
|
+
}
|
|
4155
|
+
return clone2;
|
|
4156
|
+
}
|
|
4157
|
+
const record = current;
|
|
4158
|
+
if (isMediaObject(record)) return { ...record };
|
|
4159
|
+
const clone = {};
|
|
4160
|
+
for (const [key, child] of Object.entries(record)) {
|
|
4161
|
+
if (++entries > maxEntries) {
|
|
4162
|
+
clone[TRUNCATED] = true;
|
|
4163
|
+
break;
|
|
4164
|
+
}
|
|
4165
|
+
clone[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4166
|
+
}
|
|
4167
|
+
return clone;
|
|
4168
|
+
};
|
|
4169
|
+
return visit(value, 0);
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
// src/utils/json-post.ts
|
|
4173
|
+
var MAX_ERROR_MESSAGE_CHARS = 240;
|
|
4174
|
+
function abortError() {
|
|
4175
|
+
return new DOMException("Aborted", "AbortError");
|
|
4176
|
+
}
|
|
4177
|
+
function throwIfAborted(signal) {
|
|
4178
|
+
if (signal?.aborted) throw abortError();
|
|
4179
|
+
}
|
|
4180
|
+
async function postJson(request) {
|
|
4181
|
+
const { provider, signal } = request;
|
|
4182
|
+
const secrets = request.secrets?.filter((s) => typeof s === "string" && s !== "");
|
|
4183
|
+
const clean = (text2) => bound(redactText(text2, { secrets }));
|
|
4184
|
+
const fetchImpl = request.fetch ?? globalThis.fetch;
|
|
4185
|
+
throwIfAborted(signal);
|
|
4186
|
+
let response;
|
|
4187
|
+
try {
|
|
4188
|
+
response = await fetchImpl(request.url, {
|
|
4189
|
+
method: "POST",
|
|
4190
|
+
headers: request.headers,
|
|
4191
|
+
body: JSON.stringify(request.body),
|
|
4192
|
+
signal
|
|
4193
|
+
});
|
|
4194
|
+
} catch (err) {
|
|
4195
|
+
if (signal?.aborted || isAbortError2(err)) throw abortError();
|
|
4196
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
4197
|
+
throw new GGAIError(`Couldn't reach ${provider}: ${clean(detail)}`, {
|
|
4198
|
+
source: "network",
|
|
4199
|
+
cause: err
|
|
4200
|
+
});
|
|
4201
|
+
}
|
|
4202
|
+
const headerRequestId = readHeader(
|
|
4203
|
+
response.headers,
|
|
4204
|
+
"x-request-id",
|
|
4205
|
+
"request-id",
|
|
4206
|
+
"openai-request-id"
|
|
4207
|
+
);
|
|
4208
|
+
let text;
|
|
4209
|
+
try {
|
|
4210
|
+
text = await response.text();
|
|
4211
|
+
} catch (err) {
|
|
4212
|
+
if (signal?.aborted || isAbortError2(err)) throw abortError();
|
|
4213
|
+
throw new GGAIError(`Lost the connection to ${provider} while reading its response.`, {
|
|
4214
|
+
source: "network",
|
|
4215
|
+
cause: err
|
|
4216
|
+
});
|
|
4217
|
+
}
|
|
4218
|
+
if (!response.ok) {
|
|
4219
|
+
throw httpError(provider, response, text, headerRequestId, clean);
|
|
4220
|
+
}
|
|
4221
|
+
try {
|
|
4222
|
+
return {
|
|
4223
|
+
json: JSON.parse(text),
|
|
4224
|
+
...headerRequestId ? { requestId: headerRequestId } : {}
|
|
4225
|
+
};
|
|
4226
|
+
} catch {
|
|
4227
|
+
throw new ProviderError(provider, `${provider} returned a response that isn't valid JSON.`, {
|
|
4228
|
+
statusCode: response.status,
|
|
4229
|
+
...headerRequestId ? { requestId: headerRequestId } : {}
|
|
4230
|
+
});
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
function httpError(provider, response, text, headerRequestId, clean) {
|
|
4234
|
+
const status = response.status;
|
|
4235
|
+
const parsed = parseErrorBody(text);
|
|
4236
|
+
const rawMessage = parsed.message ?? (parsed.isJson ? "" : text.trim());
|
|
4237
|
+
const message = !rawMessage ? emptyProviderErrorMessage(status) : isRawHtmlErrorEcho(rawMessage) ? providerHtmlErrorMessage(status) : clean(rawMessage);
|
|
4238
|
+
const requestId = headerRequestId ?? parsed.requestId;
|
|
4239
|
+
const base = {
|
|
4240
|
+
statusCode: status,
|
|
4241
|
+
...requestId ? { requestId } : {}
|
|
4242
|
+
};
|
|
4243
|
+
if (status === 401 || status === 403) {
|
|
4244
|
+
return new ProviderError(provider, message, { ...base, source: "auth" });
|
|
4245
|
+
}
|
|
4246
|
+
const codeType = `${parsed.code ?? ""} ${parsed.type ?? ""}`.toLowerCase();
|
|
4247
|
+
if (status === 402 || codeType.includes("insufficient_quota") || isHardBillingMessage(message)) {
|
|
4248
|
+
const stamped = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
|
|
4249
|
+
return new ProviderError(provider, stamped, base);
|
|
4250
|
+
}
|
|
4251
|
+
if (status === 429) {
|
|
4252
|
+
const retryAfter = Number(readHeader(response.headers, "retry-after"));
|
|
4253
|
+
const resetsAt = Number.isFinite(retryAfter) && retryAfter > 0 ? Math.floor(Date.now() / 1e3) + Math.ceil(retryAfter) : void 0;
|
|
4254
|
+
return new ProviderError(provider, message, {
|
|
4255
|
+
...base,
|
|
4256
|
+
...resetsAt !== void 0 ? { resetsAt } : {}
|
|
4257
|
+
});
|
|
4258
|
+
}
|
|
4259
|
+
return new ProviderError(provider, message, base);
|
|
4260
|
+
}
|
|
4261
|
+
function parseErrorBody(text) {
|
|
4262
|
+
let parsed;
|
|
4263
|
+
try {
|
|
4264
|
+
parsed = JSON.parse(text);
|
|
4265
|
+
} catch {
|
|
4266
|
+
return { isJson: false };
|
|
4267
|
+
}
|
|
4268
|
+
if (!isRecord(parsed)) return { isJson: true };
|
|
4269
|
+
const error = parsed.error;
|
|
4270
|
+
const errorObj = isRecord(error) ? error : void 0;
|
|
4271
|
+
const message = stringField(errorObj, "message") ?? (typeof error === "string" && error.trim() ? error.trim() : void 0) ?? stringField(parsed, "message") ?? stringField(parsed, "detail");
|
|
4272
|
+
const requestId = stringField(parsed, "request_id") ?? stringField(errorObj, "request_id");
|
|
4273
|
+
const code = stringField(errorObj, "code") ?? stringField(errorObj, "status");
|
|
4274
|
+
const type = stringField(errorObj, "type");
|
|
4275
|
+
return {
|
|
4276
|
+
isJson: true,
|
|
4277
|
+
...message ? { message } : {},
|
|
4278
|
+
...requestId ? { requestId } : {},
|
|
4279
|
+
...code ? { code } : {},
|
|
4280
|
+
...type ? { type } : {}
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
function stringField(record, key) {
|
|
4284
|
+
const value = record?.[key];
|
|
4285
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
4286
|
+
}
|
|
4287
|
+
function isRecord(value) {
|
|
4288
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4289
|
+
}
|
|
4290
|
+
function isAbortError2(err) {
|
|
4291
|
+
return (err instanceof Error || err instanceof DOMException) && err.name === "AbortError";
|
|
4292
|
+
}
|
|
4293
|
+
function bound(text) {
|
|
4294
|
+
const trimmed = text.trim();
|
|
4295
|
+
return trimmed.length > MAX_ERROR_MESSAGE_CHARS ? `${trimmed.slice(0, MAX_ERROR_MESSAGE_CHARS)}\u2026` : trimmed;
|
|
4296
|
+
}
|
|
4297
|
+
function joinUrl(baseUrl, path) {
|
|
4298
|
+
return `${baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
// src/providers/gemini-embeddings.ts
|
|
4302
|
+
var GEMINI_API_BASE_URL = "https://generativelanguage.googleapis.com/v1beta";
|
|
4303
|
+
var GEMINI_EMBED_BATCH_SIZE = 100;
|
|
4304
|
+
var TASK_TYPES = {
|
|
4305
|
+
query: "RETRIEVAL_QUERY",
|
|
4306
|
+
document: "RETRIEVAL_DOCUMENT"
|
|
4307
|
+
};
|
|
4308
|
+
function supportsTaskType(model) {
|
|
4309
|
+
return !/^gemini-embedding-2(?:$|[-.@])/.test(model);
|
|
4310
|
+
}
|
|
4311
|
+
async function embedGemini(request) {
|
|
4312
|
+
if (!request.apiKey) {
|
|
4313
|
+
throw new GGAIError("Gemini embeddings need a Gemini API key (apiKey).", {
|
|
4314
|
+
source: "auth",
|
|
4315
|
+
hint: "Create a key in Google AI Studio. Gemini Code Assist sign-in can't be used for embeddings."
|
|
4316
|
+
});
|
|
4317
|
+
}
|
|
4318
|
+
const model = request.model.replace(/^models\//, "");
|
|
4319
|
+
const resource = `models/${model}`;
|
|
4320
|
+
const taskType = request.inputType && supportsTaskType(model) ? TASK_TYPES[request.inputType] : void 0;
|
|
4321
|
+
const embedContentConfig = {
|
|
4322
|
+
...taskType ? { taskType } : {},
|
|
4323
|
+
...request.dimensions !== void 0 ? { outputDimensionality: request.dimensions } : {}
|
|
4324
|
+
};
|
|
4325
|
+
const body = mergeProviderOptions(
|
|
4326
|
+
{
|
|
4327
|
+
requests: request.input.map((text) => ({
|
|
4328
|
+
model: resource,
|
|
4329
|
+
content: { parts: [{ text }] },
|
|
4330
|
+
...Object.keys(embedContentConfig).length > 0 ? { embedContentConfig } : {}
|
|
4331
|
+
}))
|
|
4332
|
+
},
|
|
4333
|
+
request.providerOptions
|
|
4334
|
+
);
|
|
4335
|
+
const { json, requestId } = await postJson({
|
|
4336
|
+
provider: "gemini",
|
|
4337
|
+
url: joinUrl(request.baseUrl ?? GEMINI_API_BASE_URL, `${resource}:batchEmbedContents`),
|
|
4338
|
+
headers: { "Content-Type": "application/json", "x-goog-api-key": request.apiKey },
|
|
4339
|
+
body,
|
|
4340
|
+
fetch: request.fetch,
|
|
4341
|
+
signal: request.signal,
|
|
4342
|
+
secrets: [request.apiKey]
|
|
4343
|
+
});
|
|
4344
|
+
const invalid = (detail) => new ProviderError("gemini", `Invalid embeddings response: ${detail}.`, {
|
|
4345
|
+
...requestId ? { requestId } : {}
|
|
4346
|
+
});
|
|
4347
|
+
if (!isRecord(json) || !Array.isArray(json.embeddings)) {
|
|
4348
|
+
throw invalid("missing `embeddings` array");
|
|
4349
|
+
}
|
|
4350
|
+
const embeddings = json.embeddings.map((entry) => {
|
|
4351
|
+
if (!isRecord(entry) || !Array.isArray(entry.values)) {
|
|
4352
|
+
throw invalid("an entry has no `values` array");
|
|
4353
|
+
}
|
|
4354
|
+
return entry.values;
|
|
4355
|
+
});
|
|
4356
|
+
const usage = json.usageMetadata;
|
|
4357
|
+
const promptTokens = isRecord(usage) ? usage.promptTokenCount : void 0;
|
|
4358
|
+
return {
|
|
4359
|
+
embeddings,
|
|
4360
|
+
model,
|
|
4361
|
+
...typeof promptTokens === "number" && Number.isFinite(promptTokens) ? { usage: { inputTokens: promptTokens } } : {}
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
|
|
4365
|
+
// src/providers/openai-compat-retrieval.ts
|
|
4366
|
+
function jsonHeaders(apiKey) {
|
|
4367
|
+
return {
|
|
4368
|
+
"Content-Type": "application/json",
|
|
4369
|
+
...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
4370
|
+
};
|
|
4371
|
+
}
|
|
4372
|
+
async function embedOpenAICompatible(request, config) {
|
|
4373
|
+
const body = mergeProviderOptions(
|
|
4374
|
+
{
|
|
4375
|
+
model: config.wireModel ?? request.model,
|
|
4376
|
+
input: request.input,
|
|
4377
|
+
// gg-ai parses float arrays only; never let a server default to base64.
|
|
4378
|
+
encoding_format: "float",
|
|
4379
|
+
...request.dimensions !== void 0 ? { dimensions: request.dimensions } : {},
|
|
4380
|
+
...request.inputType && config.inputTypeFields ? config.inputTypeFields(request.inputType) : {}
|
|
4381
|
+
},
|
|
4382
|
+
request.providerOptions
|
|
4383
|
+
);
|
|
4384
|
+
const { json, requestId } = await postJson({
|
|
4385
|
+
provider: config.provider,
|
|
4386
|
+
url: joinUrl(config.baseUrl, "embeddings"),
|
|
4387
|
+
headers: jsonHeaders(request.apiKey),
|
|
4388
|
+
body,
|
|
4389
|
+
fetch: request.fetch,
|
|
4390
|
+
signal: request.signal,
|
|
4391
|
+
secrets: [request.apiKey]
|
|
4392
|
+
});
|
|
4393
|
+
const invalid = (detail) => new ProviderError(config.provider, `Invalid embeddings response: ${detail}.`, {
|
|
4394
|
+
...requestId ? { requestId } : {}
|
|
4395
|
+
});
|
|
4396
|
+
if (!isRecord(json) || !Array.isArray(json.data)) throw invalid("missing `data` array");
|
|
4397
|
+
const items = json.data.map((item) => {
|
|
4398
|
+
if (!isRecord(item)) throw invalid("an entry in `data` is not an object");
|
|
4399
|
+
const index = item.index;
|
|
4400
|
+
if (index !== void 0 && !(typeof index === "number" && Number.isInteger(index))) {
|
|
4401
|
+
throw invalid("an entry has a non-integer `index`");
|
|
4402
|
+
}
|
|
4403
|
+
if (!Array.isArray(item.embedding)) {
|
|
4404
|
+
throw invalid("an entry's `embedding` is not an array of numbers");
|
|
4405
|
+
}
|
|
4406
|
+
return { index, embedding: item.embedding };
|
|
4407
|
+
});
|
|
4408
|
+
const withIndex = items.filter((item) => item.index !== void 0).length;
|
|
4409
|
+
let embeddings;
|
|
4410
|
+
if (withIndex === 0) {
|
|
4411
|
+
embeddings = items.map((item) => item.embedding);
|
|
4412
|
+
} else if (withIndex !== items.length) {
|
|
4413
|
+
throw invalid("some entries have an `index` and some don't");
|
|
4414
|
+
} else {
|
|
4415
|
+
const slots = new Array(items.length);
|
|
4416
|
+
for (const item of items) {
|
|
4417
|
+
const index = item.index;
|
|
4418
|
+
if (index < 0 || index >= items.length || slots[index] !== void 0) {
|
|
4419
|
+
throw invalid(`unexpected or duplicate index ${index}`);
|
|
4420
|
+
}
|
|
4421
|
+
slots[index] = item.embedding;
|
|
4422
|
+
}
|
|
4423
|
+
embeddings = slots;
|
|
4424
|
+
}
|
|
4425
|
+
const usage = json.usage;
|
|
4426
|
+
const promptTokens = isRecord(usage) ? usage.prompt_tokens : void 0;
|
|
4427
|
+
return {
|
|
4428
|
+
embeddings,
|
|
4429
|
+
...typeof json.model === "string" && json.model ? { model: json.model } : {},
|
|
4430
|
+
...typeof promptTokens === "number" && Number.isFinite(promptTokens) ? { usage: { inputTokens: promptTokens } } : {}
|
|
4431
|
+
};
|
|
4432
|
+
}
|
|
4433
|
+
async function rerankCohereCompatible(request, config) {
|
|
4434
|
+
const body = mergeProviderOptions(
|
|
4435
|
+
{
|
|
4436
|
+
model: config.wireModel ?? request.model,
|
|
4437
|
+
query: request.query,
|
|
4438
|
+
documents: request.documents,
|
|
4439
|
+
...request.topN !== void 0 ? { top_n: request.topN } : {}
|
|
4440
|
+
},
|
|
4441
|
+
request.providerOptions
|
|
4442
|
+
);
|
|
4443
|
+
const { json, requestId } = await postJson({
|
|
4444
|
+
provider: config.provider,
|
|
4445
|
+
url: joinUrl(config.baseUrl, "rerank"),
|
|
4446
|
+
headers: jsonHeaders(request.apiKey),
|
|
4447
|
+
body,
|
|
4448
|
+
fetch: request.fetch,
|
|
4449
|
+
signal: request.signal,
|
|
4450
|
+
secrets: [request.apiKey]
|
|
4451
|
+
});
|
|
4452
|
+
const invalid = (detail) => new ProviderError(config.provider, `Invalid rerank response: ${detail}.`, {
|
|
4453
|
+
...requestId ? { requestId } : {}
|
|
4454
|
+
});
|
|
4455
|
+
if (!isRecord(json) || !Array.isArray(json.results)) throw invalid("missing `results` array");
|
|
4456
|
+
const results = json.results.map((item) => {
|
|
4457
|
+
if (!isRecord(item)) throw invalid("an entry in `results` is not an object");
|
|
4458
|
+
const { index, relevance_score: score } = item;
|
|
4459
|
+
if (typeof index !== "number" || typeof score !== "number") {
|
|
4460
|
+
throw invalid("an entry is missing a numeric `index` or `relevance_score`");
|
|
4461
|
+
}
|
|
4462
|
+
return { index, score };
|
|
4463
|
+
});
|
|
4464
|
+
const usage = json.usage;
|
|
4465
|
+
const tokens = isRecord(usage) ? usage.prompt_tokens ?? usage.total_tokens : void 0;
|
|
4466
|
+
const searchUnits = isRecord(usage) ? usage.search_units : void 0;
|
|
4467
|
+
const rerankUsage = {
|
|
4468
|
+
...typeof tokens === "number" && Number.isFinite(tokens) ? { inputTokens: tokens } : {},
|
|
4469
|
+
...typeof searchUnits === "number" && Number.isFinite(searchUnits) ? { searchUnits } : {}
|
|
4470
|
+
};
|
|
4471
|
+
return {
|
|
4472
|
+
results,
|
|
4473
|
+
...typeof json.model === "string" && json.model ? { model: json.model } : {},
|
|
4474
|
+
...Object.keys(rerankUsage).length > 0 ? { usage: rerankUsage } : {}
|
|
4475
|
+
};
|
|
4476
|
+
}
|
|
4477
|
+
|
|
3991
4478
|
// src/provider-registry.ts
|
|
3992
4479
|
var ProviderRegistryImpl = class {
|
|
3993
4480
|
providers = /* @__PURE__ */ new Map();
|
|
@@ -4017,6 +4504,11 @@ var ProviderRegistryImpl = class {
|
|
|
4017
4504
|
has(name) {
|
|
4018
4505
|
return this.providers.has(name);
|
|
4019
4506
|
}
|
|
4507
|
+
/** Whether a registered provider offers a capability (`false` for unknown providers). */
|
|
4508
|
+
supports(name, capability) {
|
|
4509
|
+
const entry = this.providers.get(name);
|
|
4510
|
+
return typeof entry?.[capability] === "function";
|
|
4511
|
+
}
|
|
4020
4512
|
/** List all registered provider names. */
|
|
4021
4513
|
list() {
|
|
4022
4514
|
return [...this.providers.keys()];
|
|
@@ -4194,6 +4686,10 @@ function observePreparedContext(before, after, tools) {
|
|
|
4194
4686
|
}
|
|
4195
4687
|
|
|
4196
4688
|
// src/stream.ts
|
|
4689
|
+
var OPENAI_EMBED_BATCH_SIZE = 2048;
|
|
4690
|
+
var OPENROUTER_EMBED_BATCH_SIZE = 64;
|
|
4691
|
+
var LOCAL_EMBED_BATCH_SIZE = 64;
|
|
4692
|
+
var OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
|
|
4197
4693
|
var GLM_CODING_BASE_URL = "https://api.z.ai/api/coding/paas/v4";
|
|
4198
4694
|
var KIMI_CODE_USER_AGENT = `kimi-code-cli/${process.env.KIMI_CODE_VERSION ?? "1.0.11"}`;
|
|
4199
4695
|
var GROK_CLI_PROXY_HOST = "cli-chat-proxy.grok.com";
|
|
@@ -4214,10 +4710,20 @@ providerRegistry.register("openai", {
|
|
|
4214
4710
|
return streamOpenAICodex(options);
|
|
4215
4711
|
}
|
|
4216
4712
|
return streamOpenAI(options);
|
|
4217
|
-
}
|
|
4713
|
+
},
|
|
4714
|
+
// OpenAI API-key auth only; ChatGPT-subscription (Codex) tokens have no embeddings API.
|
|
4715
|
+
// OpenAI has no task/input-type parameter, so `inputType` is ignored.
|
|
4716
|
+
embed: (request) => embedOpenAICompatible(request, {
|
|
4717
|
+
provider: "openai",
|
|
4718
|
+
baseUrl: request.baseUrl ?? "https://api.openai.com/v1"
|
|
4719
|
+
}),
|
|
4720
|
+
embedBatchSize: OPENAI_EMBED_BATCH_SIZE
|
|
4218
4721
|
});
|
|
4219
4722
|
providerRegistry.register("gemini", {
|
|
4220
|
-
stream: (options) => streamGemini(options)
|
|
4723
|
+
stream: (options) => streamGemini(options),
|
|
4724
|
+
// Native Gemini API with an AI Studio key — not the Code Assist OAuth endpoint above.
|
|
4725
|
+
embed: (request) => embedGemini(request),
|
|
4726
|
+
embedBatchSize: GEMINI_EMBED_BATCH_SIZE
|
|
4221
4727
|
});
|
|
4222
4728
|
providerRegistry.register("glm", {
|
|
4223
4729
|
stream: (options) => streamOpenAI({
|
|
@@ -4241,7 +4747,19 @@ providerRegistry.register("deepseek", {
|
|
|
4241
4747
|
providerRegistry.register("openrouter", {
|
|
4242
4748
|
stream: (options) => streamOpenAI({
|
|
4243
4749
|
...options,
|
|
4244
|
-
baseUrl: options.baseUrl ??
|
|
4750
|
+
baseUrl: options.baseUrl ?? OPENROUTER_BASE_URL
|
|
4751
|
+
}),
|
|
4752
|
+
embed: (request) => embedOpenAICompatible(request, {
|
|
4753
|
+
provider: "openrouter",
|
|
4754
|
+
baseUrl: request.baseUrl ?? OPENROUTER_BASE_URL,
|
|
4755
|
+
inputTypeFields: (inputType) => ({
|
|
4756
|
+
input_type: inputType === "query" ? "search_query" : "search_document"
|
|
4757
|
+
})
|
|
4758
|
+
}),
|
|
4759
|
+
embedBatchSize: OPENROUTER_EMBED_BATCH_SIZE,
|
|
4760
|
+
rerank: (request) => rerankCohereCompatible(request, {
|
|
4761
|
+
provider: "openrouter",
|
|
4762
|
+
baseUrl: request.baseUrl ?? OPENROUTER_BASE_URL
|
|
4245
4763
|
})
|
|
4246
4764
|
});
|
|
4247
4765
|
providerRegistry.register("huggingface", {
|
|
@@ -4311,19 +4829,36 @@ providerRegistry.register("local", {
|
|
|
4311
4829
|
// vLLM). There is no default endpoint: the baseUrl comes from the endpoint
|
|
4312
4830
|
// credential the discovery layer wrote, so a missing one is a wiring bug, not
|
|
4313
4831
|
// something to paper over with a guess at someone else's port.
|
|
4314
|
-
stream: (options) => {
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4832
|
+
stream: (options) => streamOpenAI({
|
|
4833
|
+
...options,
|
|
4834
|
+
baseUrl: requireLocalBaseUrl(options.baseUrl),
|
|
4835
|
+
model: localWireModelId(options.model),
|
|
4836
|
+
webSearch: false
|
|
4837
|
+
}),
|
|
4838
|
+
// Every supported local server exposes OpenAI-compatible `/v1/embeddings`
|
|
4839
|
+
// (the model must be an embedding model). None has an input-type parameter.
|
|
4840
|
+
embed: (request) => embedOpenAICompatible(request, {
|
|
4841
|
+
provider: "local",
|
|
4842
|
+
baseUrl: requireLocalBaseUrl(request.baseUrl),
|
|
4843
|
+
wireModel: localWireModelId(request.model)
|
|
4844
|
+
}),
|
|
4845
|
+
embedBatchSize: LOCAL_EMBED_BATCH_SIZE,
|
|
4846
|
+
// llama.cpp (`llama-server --rerank`) and vLLM serve `/v1/rerank`; Ollama and
|
|
4847
|
+
// LM Studio don't, and answer with a 404 ProviderError.
|
|
4848
|
+
rerank: (request) => rerankCohereCompatible(request, {
|
|
4849
|
+
provider: "local",
|
|
4850
|
+
baseUrl: requireLocalBaseUrl(request.baseUrl),
|
|
4851
|
+
wireModel: localWireModelId(request.model)
|
|
4852
|
+
})
|
|
4326
4853
|
});
|
|
4854
|
+
function requireLocalBaseUrl(baseUrl) {
|
|
4855
|
+
if (!baseUrl) {
|
|
4856
|
+
throw new GGAIError(
|
|
4857
|
+
"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."
|
|
4858
|
+
);
|
|
4859
|
+
}
|
|
4860
|
+
return baseUrl;
|
|
4861
|
+
}
|
|
4327
4862
|
function stream(options) {
|
|
4328
4863
|
const entry = providerRegistry.get(options.provider);
|
|
4329
4864
|
if (!entry) {
|
|
@@ -4382,6 +4917,121 @@ function messagesContainVideo(messages) {
|
|
|
4382
4917
|
return false;
|
|
4383
4918
|
}
|
|
4384
4919
|
|
|
4920
|
+
// src/embed.ts
|
|
4921
|
+
var CAPABILITY_LABEL = {
|
|
4922
|
+
embed: "embeddings",
|
|
4923
|
+
rerank: "reranking"
|
|
4924
|
+
};
|
|
4925
|
+
function resolveCapability(provider, capability) {
|
|
4926
|
+
const entry = providerRegistry.get(provider);
|
|
4927
|
+
if (!entry) {
|
|
4928
|
+
throw new GGAIError(
|
|
4929
|
+
`Unknown provider: "${provider}". Registered: ${providerRegistry.list().join(", ")}`
|
|
4930
|
+
);
|
|
4931
|
+
}
|
|
4932
|
+
const fn = entry[capability];
|
|
4933
|
+
if (typeof fn !== "function") {
|
|
4934
|
+
const label = CAPABILITY_LABEL[capability];
|
|
4935
|
+
const offering = providerRegistry.list().filter((name) => providerRegistry.supports(name, capability)).sort();
|
|
4936
|
+
throw new GGAIError(`Provider "${provider}" doesn't support ${label}.`, {
|
|
4937
|
+
source: "capability",
|
|
4938
|
+
hint: offering.length > 0 ? `Use a provider that offers ${label}: ${offering.join(", ")}.` : `No registered provider offers ${label}.`
|
|
4939
|
+
});
|
|
4940
|
+
}
|
|
4941
|
+
return fn;
|
|
4942
|
+
}
|
|
4943
|
+
function assertPositiveInteger(name, value) {
|
|
4944
|
+
if (value !== void 0 && !(Number.isInteger(value) && value > 0)) {
|
|
4945
|
+
throw new GGAIError(`${name} must be a positive integer (got ${String(value)}).`);
|
|
4946
|
+
}
|
|
4947
|
+
}
|
|
4948
|
+
async function embed(options) {
|
|
4949
|
+
const embedFn = resolveCapability(options.provider, "embed");
|
|
4950
|
+
if (!Array.isArray(options.input)) {
|
|
4951
|
+
throw new GGAIError("embed() input must be an array of strings.");
|
|
4952
|
+
}
|
|
4953
|
+
assertPositiveInteger("dimensions", options.dimensions);
|
|
4954
|
+
assertPositiveInteger("batchSize", options.batchSize);
|
|
4955
|
+
if (options.input.length === 0) return { embeddings: [], model: options.model };
|
|
4956
|
+
const entry = providerRegistry.get(options.provider);
|
|
4957
|
+
const batchSize = options.batchSize ?? entry?.embedBatchSize ?? options.input.length;
|
|
4958
|
+
const { normalize, batchSize: _batchSize, ...request } = options;
|
|
4959
|
+
const embeddings = [];
|
|
4960
|
+
let model = options.model;
|
|
4961
|
+
let inputTokens;
|
|
4962
|
+
let expectedLength = options.dimensions;
|
|
4963
|
+
for (let start = 0; start < options.input.length; start += batchSize) {
|
|
4964
|
+
throwIfAborted(options.signal);
|
|
4965
|
+
const batch = options.input.slice(start, start + batchSize);
|
|
4966
|
+
const response = await embedFn({ ...request, input: batch });
|
|
4967
|
+
const invalid = (detail) => new ProviderError(options.provider, `Invalid embeddings response: ${detail}.`);
|
|
4968
|
+
if (!Array.isArray(response.embeddings) || response.embeddings.length !== batch.length) {
|
|
4969
|
+
const count = Array.isArray(response.embeddings) ? response.embeddings.length : 0;
|
|
4970
|
+
throw invalid(`expected ${batch.length} embeddings, got ${count}`);
|
|
4971
|
+
}
|
|
4972
|
+
for (const vector of response.embeddings) {
|
|
4973
|
+
if (!Array.isArray(vector) || vector.length === 0) throw invalid("received an empty vector");
|
|
4974
|
+
if (!vector.every((value) => typeof value === "number" && Number.isFinite(value))) {
|
|
4975
|
+
throw invalid("received a vector with non-numeric values");
|
|
4976
|
+
}
|
|
4977
|
+
if (expectedLength === void 0) {
|
|
4978
|
+
expectedLength = vector.length;
|
|
4979
|
+
} else if (vector.length !== expectedLength) {
|
|
4980
|
+
throw invalid(
|
|
4981
|
+
options.dimensions !== void 0 ? `requested ${options.dimensions} dimensions, got a vector with ${vector.length}` : `vectors have different lengths (${expectedLength} and ${vector.length})`
|
|
4982
|
+
);
|
|
4983
|
+
}
|
|
4984
|
+
embeddings.push(normalize ? l2Normalize(vector) : vector);
|
|
4985
|
+
}
|
|
4986
|
+
if (response.model) model = response.model;
|
|
4987
|
+
if (response.usage) inputTokens = (inputTokens ?? 0) + response.usage.inputTokens;
|
|
4988
|
+
}
|
|
4989
|
+
return {
|
|
4990
|
+
embeddings,
|
|
4991
|
+
model,
|
|
4992
|
+
...inputTokens !== void 0 ? { usage: { inputTokens } } : {}
|
|
4993
|
+
};
|
|
4994
|
+
}
|
|
4995
|
+
async function rerank(options) {
|
|
4996
|
+
const rerankFn = resolveCapability(options.provider, "rerank");
|
|
4997
|
+
if (!Array.isArray(options.documents)) {
|
|
4998
|
+
throw new GGAIError("rerank() documents must be an array of strings.");
|
|
4999
|
+
}
|
|
5000
|
+
assertPositiveInteger("topN", options.topN);
|
|
5001
|
+
if (options.documents.length === 0) return { results: [], model: options.model };
|
|
5002
|
+
throwIfAborted(options.signal);
|
|
5003
|
+
const response = await rerankFn(options);
|
|
5004
|
+
const invalid = (detail) => new ProviderError(options.provider, `Invalid rerank response: ${detail}.`);
|
|
5005
|
+
if (!Array.isArray(response.results)) throw invalid("missing results");
|
|
5006
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5007
|
+
const results = response.results.map(({ index, score }) => {
|
|
5008
|
+
if (!Number.isInteger(index) || index < 0 || index >= options.documents.length) {
|
|
5009
|
+
throw invalid(
|
|
5010
|
+
`index ${String(index)} is out of range for ${options.documents.length} documents`
|
|
5011
|
+
);
|
|
5012
|
+
}
|
|
5013
|
+
if (seen.has(index)) throw invalid(`index ${index} appears more than once`);
|
|
5014
|
+
if (typeof score !== "number" || !Number.isFinite(score)) {
|
|
5015
|
+
throw invalid(`document ${index} has a non-numeric score`);
|
|
5016
|
+
}
|
|
5017
|
+
seen.add(index);
|
|
5018
|
+
return { index, score };
|
|
5019
|
+
});
|
|
5020
|
+
results.sort((a, b) => b.score - a.score || a.index - b.index);
|
|
5021
|
+
return {
|
|
5022
|
+
results: options.topN !== void 0 ? results.slice(0, options.topN) : results,
|
|
5023
|
+
model: response.model ?? options.model,
|
|
5024
|
+
...response.usage ? { usage: response.usage } : {}
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
function l2Normalize(vector) {
|
|
5028
|
+
let sumOfSquares = 0;
|
|
5029
|
+
for (const value of vector) sumOfSquares += value * value;
|
|
5030
|
+
if (sumOfSquares === 0) return [...vector];
|
|
5031
|
+
const norm = Math.sqrt(sumOfSquares);
|
|
5032
|
+
return vector.map((value) => value / norm);
|
|
5033
|
+
}
|
|
5034
|
+
|
|
4385
5035
|
// src/chat-error.ts
|
|
4386
5036
|
function causeCode(error) {
|
|
4387
5037
|
if (!(error instanceof Error) || !error.cause || typeof error.cause !== "object") return "";
|
|
@@ -4623,143 +5273,6 @@ Original: ${message}`;
|
|
|
4623
5273
|
return message;
|
|
4624
5274
|
}
|
|
4625
5275
|
|
|
4626
|
-
// src/redaction.ts
|
|
4627
|
-
var REDACTED = "[REDACTED]";
|
|
4628
|
-
var TRUNCATED = "[TRUNCATED]";
|
|
4629
|
-
var CIRCULAR = "[CIRCULAR]";
|
|
4630
|
-
var SENSITIVE_NAME = /(?:^|[_-])(?:api[_-]?key|access[_-]?token|refresh[_-]?token|token|key|auth(?:orization)?|bearer|cookie|credential|private[_-]?key|password|passwd|secret)(?:$|[_-])/i;
|
|
4631
|
-
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;
|
|
4632
|
-
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;
|
|
4633
|
-
var URL_USERINFO = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/?#@:"'`<>]*:[^\s/?#"'`<>]+@/gi;
|
|
4634
|
-
var URL_USERINFO_TO_FIRST_AT = /\b([a-z][a-z0-9+.-]*:\/\/)[^\s/@:]*:[^\s/@]+@/gi;
|
|
4635
|
-
function escaped(value) {
|
|
4636
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4637
|
-
}
|
|
4638
|
-
function normalizedSecrets(secrets) {
|
|
4639
|
-
if (!secrets) return [];
|
|
4640
|
-
return [...new Set([...secrets].filter((value) => value.length >= 8 && value !== REDACTED))].sort(
|
|
4641
|
-
(a, b) => b.length - a.length
|
|
4642
|
-
);
|
|
4643
|
-
}
|
|
4644
|
-
function environmentSecrets(env) {
|
|
4645
|
-
const values = /* @__PURE__ */ new Set();
|
|
4646
|
-
for (const [name, value] of Object.entries(env)) {
|
|
4647
|
-
if (!value || value.length < 8 || value === REDACTED || !SENSITIVE_NAME.test(name)) continue;
|
|
4648
|
-
values.add(value);
|
|
4649
|
-
}
|
|
4650
|
-
return [...values].sort((a, b) => b.length - a.length);
|
|
4651
|
-
}
|
|
4652
|
-
function redactText(text, options = {}) {
|
|
4653
|
-
let result = text;
|
|
4654
|
-
result = result.replace(
|
|
4655
|
-
/-----BEGIN (?:[A-Z0-9 ]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9 ]+ )?PRIVATE KEY-----/g,
|
|
4656
|
-
REDACTED
|
|
4657
|
-
);
|
|
4658
|
-
result = result.replace(URL_USERINFO, `$1${REDACTED}@`);
|
|
4659
|
-
result = result.replace(URL_USERINFO_TO_FIRST_AT, `$1${REDACTED}@`);
|
|
4660
|
-
result = result.replace(
|
|
4661
|
-
/\b(authorization\s*[:=]\s*)(?:bearer|basic)\s+[^\s,;]+/gi,
|
|
4662
|
-
`$1${REDACTED}`
|
|
4663
|
-
);
|
|
4664
|
-
result = result.replace(
|
|
4665
|
-
/\b(bearer|basic)\s+(?![A-Za-z][a-z]{0,14}\b)[A-Za-z0-9+/_.=-]{8,}/gi,
|
|
4666
|
-
`$1 ${REDACTED}`
|
|
4667
|
-
);
|
|
4668
|
-
result = result.replace(/\b(cookie|set-cookie)(\s*:\s*)[^\s=;:]+=[^\r\n]*/gi, `$1$2${REDACTED}`);
|
|
4669
|
-
result = result.replace(
|
|
4670
|
-
/\beyJ[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\.[A-Za-z0-9_-]{6,}\b/g,
|
|
4671
|
-
REDACTED
|
|
4672
|
-
);
|
|
4673
|
-
result = result.replace(
|
|
4674
|
-
/\b(?:sk-(?:ant-|proj-)?|xox[baprs]-|gh[pousr]_|github_pat_|AIza)[A-Za-z0-9_-]{12,}\b/g,
|
|
4675
|
-
REDACTED
|
|
4676
|
-
);
|
|
4677
|
-
result = result.replace(
|
|
4678
|
-
ENV_SECRET_ASSIGNMENT,
|
|
4679
|
-
(_match, name, separator, quote) => `${name}${separator}${quote}${REDACTED}${quote}`
|
|
4680
|
-
);
|
|
4681
|
-
result = result.replace(
|
|
4682
|
-
COMPACT_SECRET_ASSIGNMENT,
|
|
4683
|
-
(_match, name, quote) => `${name}=${quote}${REDACTED}${quote}`
|
|
4684
|
-
);
|
|
4685
|
-
for (const secret of normalizedSecrets(options.secrets)) {
|
|
4686
|
-
result = result.replace(new RegExp(escaped(secret), "g"), REDACTED);
|
|
4687
|
-
}
|
|
4688
|
-
const maxStringLength = options.maxStringLength ?? 1e6;
|
|
4689
|
-
if (result.length > maxStringLength) {
|
|
4690
|
-
result = `${result.slice(0, maxStringLength)}${TRUNCATED}`;
|
|
4691
|
-
}
|
|
4692
|
-
return result;
|
|
4693
|
-
}
|
|
4694
|
-
function isBinary(value) {
|
|
4695
|
-
return value instanceof ArrayBuffer || ArrayBuffer.isView(value) || typeof Blob !== "undefined" && value instanceof Blob;
|
|
4696
|
-
}
|
|
4697
|
-
function isMediaObject(value) {
|
|
4698
|
-
return (value.type === "image" || value.type === "video") && (typeof value.data === "string" || typeof value.url === "string");
|
|
4699
|
-
}
|
|
4700
|
-
function redactValue(value, options = {}) {
|
|
4701
|
-
const maxDepth = options.maxDepth ?? 20;
|
|
4702
|
-
const maxEntries = options.maxEntries ?? 1e4;
|
|
4703
|
-
const ancestors = /* @__PURE__ */ new WeakSet();
|
|
4704
|
-
let entries = 0;
|
|
4705
|
-
const visit = (current, depth, sensitive = false) => {
|
|
4706
|
-
if (typeof current === "string") {
|
|
4707
|
-
if (sensitive && current.length > 0 && current !== REDACTED) return REDACTED;
|
|
4708
|
-
return redactText(current, options);
|
|
4709
|
-
}
|
|
4710
|
-
if (current === null || current === void 0 || typeof current === "number" || typeof current === "boolean" || typeof current === "bigint") {
|
|
4711
|
-
return current;
|
|
4712
|
-
}
|
|
4713
|
-
if (typeof current !== "object") return current;
|
|
4714
|
-
if (isBinary(current)) return current;
|
|
4715
|
-
if (current instanceof Date) return new Date(current.getTime());
|
|
4716
|
-
if (depth >= maxDepth) return TRUNCATED;
|
|
4717
|
-
if (ancestors.has(current)) return CIRCULAR;
|
|
4718
|
-
ancestors.add(current);
|
|
4719
|
-
try {
|
|
4720
|
-
return cloneObject(current, depth);
|
|
4721
|
-
} finally {
|
|
4722
|
-
ancestors.delete(current);
|
|
4723
|
-
}
|
|
4724
|
-
};
|
|
4725
|
-
const cloneObject = (current, depth) => {
|
|
4726
|
-
if (current instanceof Error) {
|
|
4727
|
-
const error = {
|
|
4728
|
-
name: current.name,
|
|
4729
|
-
message: visit(current.message, depth + 1),
|
|
4730
|
-
stack: visit(current.stack, depth + 1)
|
|
4731
|
-
};
|
|
4732
|
-
for (const [key, child] of Object.entries(current)) {
|
|
4733
|
-
error[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4734
|
-
}
|
|
4735
|
-
return error;
|
|
4736
|
-
}
|
|
4737
|
-
if (Array.isArray(current)) {
|
|
4738
|
-
const clone2 = [];
|
|
4739
|
-
for (const child of current) {
|
|
4740
|
-
if (++entries > maxEntries) {
|
|
4741
|
-
clone2.push(TRUNCATED);
|
|
4742
|
-
break;
|
|
4743
|
-
}
|
|
4744
|
-
clone2.push(visit(child, depth + 1));
|
|
4745
|
-
}
|
|
4746
|
-
return clone2;
|
|
4747
|
-
}
|
|
4748
|
-
const record = current;
|
|
4749
|
-
if (isMediaObject(record)) return { ...record };
|
|
4750
|
-
const clone = {};
|
|
4751
|
-
for (const [key, child] of Object.entries(record)) {
|
|
4752
|
-
if (++entries > maxEntries) {
|
|
4753
|
-
clone[TRUNCATED] = true;
|
|
4754
|
-
break;
|
|
4755
|
-
}
|
|
4756
|
-
clone[key] = visit(child, depth + 1, SENSITIVE_NAME.test(key));
|
|
4757
|
-
}
|
|
4758
|
-
return clone;
|
|
4759
|
-
};
|
|
4760
|
-
return visit(value, 0);
|
|
4761
|
-
}
|
|
4762
|
-
|
|
4763
5276
|
// src/providers/palsu.ts
|
|
4764
5277
|
function palsuText(text) {
|
|
4765
5278
|
return { role: "assistant", content: text ? [{ type: "text", text }] : [] };
|
|
@@ -4840,7 +5353,8 @@ function computeCacheUsage(current, previous) {
|
|
|
4840
5353
|
function registerPalsuProvider(config) {
|
|
4841
5354
|
const name = config?.name ?? "palsu";
|
|
4842
5355
|
const responses = [];
|
|
4843
|
-
const state = { callCount: 0 };
|
|
5356
|
+
const state = { callCount: 0, embedCallCount: 0, rerankCallCount: 0 };
|
|
5357
|
+
const embedDimensions = config?.embedDimensions ?? PALSU_DEFAULT_EMBED_DIMENSIONS;
|
|
4844
5358
|
const defaultResponse = config?.defaultResponse ?? palsuText("");
|
|
4845
5359
|
const enableCache = config?.promptCache ?? false;
|
|
4846
5360
|
let lastMessagesSerialized = null;
|
|
@@ -4908,10 +5422,72 @@ function registerPalsuProvider(config) {
|
|
|
4908
5422
|
return yield* simulateStream(message, stopReason, options.signal, cacheUsage);
|
|
4909
5423
|
})();
|
|
4910
5424
|
return new StreamResult(gen, options.signal);
|
|
5425
|
+
},
|
|
5426
|
+
async embed(request) {
|
|
5427
|
+
throwIfAborted(request.signal);
|
|
5428
|
+
state.embedCallCount++;
|
|
5429
|
+
const dimensions = request.dimensions ?? embedDimensions;
|
|
5430
|
+
return {
|
|
5431
|
+
embeddings: request.input.map((text) => palsuEmbedding(text, dimensions)),
|
|
5432
|
+
model: request.model,
|
|
5433
|
+
usage: { inputTokens: request.input.reduce((n, text) => n + tokenize(text).length, 0) }
|
|
5434
|
+
};
|
|
5435
|
+
},
|
|
5436
|
+
...config?.embedBatchSize !== void 0 ? { embedBatchSize: config.embedBatchSize } : {},
|
|
5437
|
+
async rerank(request) {
|
|
5438
|
+
throwIfAborted(request.signal);
|
|
5439
|
+
state.rerankCallCount++;
|
|
5440
|
+
return {
|
|
5441
|
+
results: request.documents.map((document, index) => ({
|
|
5442
|
+
index,
|
|
5443
|
+
score: palsuRerankScore(request.query, document)
|
|
5444
|
+
})),
|
|
5445
|
+
model: request.model
|
|
5446
|
+
};
|
|
4911
5447
|
}
|
|
4912
5448
|
});
|
|
4913
5449
|
return handle;
|
|
4914
5450
|
}
|
|
5451
|
+
var PALSU_DEFAULT_EMBED_DIMENSIONS = 64;
|
|
5452
|
+
function tokenize(text) {
|
|
5453
|
+
return text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
5454
|
+
}
|
|
5455
|
+
function fnv1a(text) {
|
|
5456
|
+
let hash = 2166136261;
|
|
5457
|
+
for (let i = 0; i < text.length; i++) {
|
|
5458
|
+
hash ^= text.charCodeAt(i);
|
|
5459
|
+
hash = Math.imul(hash, 16777619);
|
|
5460
|
+
}
|
|
5461
|
+
return hash >>> 0;
|
|
5462
|
+
}
|
|
5463
|
+
function palsuEmbedding(text, dimensions = PALSU_DEFAULT_EMBED_DIMENSIONS) {
|
|
5464
|
+
const vector = new Array(dimensions).fill(0);
|
|
5465
|
+
const tokens = tokenize(text);
|
|
5466
|
+
if (tokens.length === 0) {
|
|
5467
|
+
vector[0] = 1;
|
|
5468
|
+
return vector;
|
|
5469
|
+
}
|
|
5470
|
+
for (const token of tokens) {
|
|
5471
|
+
const hash = fnv1a(token);
|
|
5472
|
+
const slot = hash % dimensions;
|
|
5473
|
+
vector[slot] = (vector[slot] ?? 0) + (hash >>> 31 === 0 ? 1 : -1);
|
|
5474
|
+
}
|
|
5475
|
+
const norm = Math.sqrt(vector.reduce((sum, value) => sum + value * value, 0));
|
|
5476
|
+
if (norm === 0) {
|
|
5477
|
+
vector.fill(0);
|
|
5478
|
+
vector[0] = 1;
|
|
5479
|
+
return vector;
|
|
5480
|
+
}
|
|
5481
|
+
return vector.map((value) => value / norm);
|
|
5482
|
+
}
|
|
5483
|
+
function palsuRerankScore(query, document) {
|
|
5484
|
+
const queryTokens = new Set(tokenize(query));
|
|
5485
|
+
if (queryTokens.size === 0) return 0;
|
|
5486
|
+
const documentTokens = new Set(tokenize(document));
|
|
5487
|
+
let hits = 0;
|
|
5488
|
+
for (const token of queryTokens) if (documentTokens.has(token)) hits++;
|
|
5489
|
+
return hits / queryTokens.size;
|
|
5490
|
+
}
|
|
4915
5491
|
export {
|
|
4916
5492
|
EventStream,
|
|
4917
5493
|
GGAIError,
|
|
@@ -4922,6 +5498,7 @@ export {
|
|
|
4922
5498
|
clampProviderContextImages,
|
|
4923
5499
|
classifyProviderError,
|
|
4924
5500
|
dropInvalidToolCalls,
|
|
5501
|
+
embed,
|
|
4925
5502
|
environmentSecrets,
|
|
4926
5503
|
formatChatError,
|
|
4927
5504
|
formatError,
|
|
@@ -4932,6 +5509,8 @@ export {
|
|
|
4932
5509
|
isValidToolCallName,
|
|
4933
5510
|
localWireModelId,
|
|
4934
5511
|
palsuAssistantMessage,
|
|
5512
|
+
palsuEmbedding,
|
|
5513
|
+
palsuRerankScore,
|
|
4935
5514
|
palsuText,
|
|
4936
5515
|
palsuThinking,
|
|
4937
5516
|
palsuToolCall,
|
|
@@ -4940,6 +5519,7 @@ export {
|
|
|
4940
5519
|
redactText,
|
|
4941
5520
|
redactValue,
|
|
4942
5521
|
registerPalsuProvider,
|
|
5522
|
+
rerank,
|
|
4943
5523
|
resolveToolSchema,
|
|
4944
5524
|
sanitizeMessagesForWire,
|
|
4945
5525
|
setProviderDiagnostic,
|