@lobu/embeddings 7.1.0 → 8.0.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/dist/internal/scrub-secrets.d.ts +14 -0
- package/dist/internal/scrub-secrets.d.ts.map +1 -0
- package/dist/internal/scrub-secrets.js +23 -0
- package/dist/internal/scrub-secrets.js.map +1 -0
- package/dist/openai.d.ts.map +1 -1
- package/dist/openai.js +13 -27
- package/dist/openai.js.map +1 -1
- package/dist/server.js +44 -48
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip anything that looks like an API key / bearer token from text before it
|
|
3
|
+
* leaves the process. Shared by the OpenAI client (sanitizing upstream error
|
|
4
|
+
* payloads) and the HTTP server (defense-in-depth scrub on outbound error
|
|
5
|
+
* messages). Accepts an optional explicit secret list — typically the API key
|
|
6
|
+
* and service token currently in process env — which is matched literally
|
|
7
|
+
* before the generic patterns run.
|
|
8
|
+
*
|
|
9
|
+
* Lives under `src/internal/` and is NOT re-exported from `src/index.ts`, so
|
|
10
|
+
* `@lobu/embeddings` does not surface this helper to npm consumers. Internal
|
|
11
|
+
* to the package only.
|
|
12
|
+
*/
|
|
13
|
+
export declare function scrubSecrets(text: string, knownSecrets?: readonly string[]): string;
|
|
14
|
+
//# sourceMappingURL=scrub-secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-secrets.d.ts","sourceRoot":"","sources":["../../src/internal/scrub-secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAC3B,IAAI,EAAE,MAAM,EACZ,YAAY,GAAE,SAAS,MAAM,EAAO,GAClC,MAAM,CAWR"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip anything that looks like an API key / bearer token from text before it
|
|
3
|
+
* leaves the process. Shared by the OpenAI client (sanitizing upstream error
|
|
4
|
+
* payloads) and the HTTP server (defense-in-depth scrub on outbound error
|
|
5
|
+
* messages). Accepts an optional explicit secret list — typically the API key
|
|
6
|
+
* and service token currently in process env — which is matched literally
|
|
7
|
+
* before the generic patterns run.
|
|
8
|
+
*
|
|
9
|
+
* Lives under `src/internal/` and is NOT re-exported from `src/index.ts`, so
|
|
10
|
+
* `@lobu/embeddings` does not surface this helper to npm consumers. Internal
|
|
11
|
+
* to the package only.
|
|
12
|
+
*/
|
|
13
|
+
export function scrubSecrets(text, knownSecrets = []) {
|
|
14
|
+
let cleaned = text;
|
|
15
|
+
for (const secret of knownSecrets) {
|
|
16
|
+
if (secret)
|
|
17
|
+
cleaned = cleaned.split(secret).join("[redacted]");
|
|
18
|
+
}
|
|
19
|
+
return cleaned
|
|
20
|
+
.replace(/\b(sk|sk-proj|rk|pk|api[_-]?key)[-_][A-Za-z0-9_-]{12,}/gi, "[redacted]")
|
|
21
|
+
.replace(/\bbearer\s+[A-Za-z0-9._-]+/gi, "bearer [redacted]");
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=scrub-secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub-secrets.js","sourceRoot":"","sources":["../../src/internal/scrub-secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAC3B,IAAY,EACZ,eAAkC,EAAE;IAEpC,IAAI,OAAO,GAAG,IAAI,CAAC;IACnB,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;QACnC,IAAI,MAAM;YAAE,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO;SACZ,OAAO,CACP,0DAA0D,EAC1D,YAAY,CACZ;SACA,OAAO,CAAC,8BAA8B,EAAE,mBAAmB,CAAC,CAAC;AAChE,CAAC"}
|
package/dist/openai.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"openai.d.ts","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK3C;AAED;;;GAGG;AACH,qBAAa,4BAA6B,SAAQ,KAAK;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;gBACf,SAAS,EAAE,MAAM;CAK7B;AAED,wBAAsB,wBAAwB,CAAC,MAAM,EAAE;IACtD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CA+FtB"}
|
package/dist/openai.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { normalizeEmbeddings, validateEmbeddingDimensions } from
|
|
1
|
+
import { normalizeEmbeddings, validateEmbeddingDimensions, } from "./embedding-utils.js";
|
|
2
|
+
import { scrubSecrets } from "./internal/scrub-secrets.js";
|
|
2
3
|
/**
|
|
3
4
|
* Thrown when the upstream OpenAI-compatible API returns a non-2xx response.
|
|
4
5
|
* Carries the upstream HTTP status so callers can map it appropriately.
|
|
@@ -7,7 +8,7 @@ export class OpenAIEmbeddingsHTTPError extends Error {
|
|
|
7
8
|
status;
|
|
8
9
|
constructor(status, message) {
|
|
9
10
|
super(message);
|
|
10
|
-
this.name =
|
|
11
|
+
this.name = "OpenAIEmbeddingsHTTPError";
|
|
11
12
|
this.status = status;
|
|
12
13
|
}
|
|
13
14
|
}
|
|
@@ -19,25 +20,10 @@ export class OpenAIEmbeddingsTimeoutError extends Error {
|
|
|
19
20
|
timeoutMs;
|
|
20
21
|
constructor(timeoutMs) {
|
|
21
22
|
super(`OpenAI embeddings request timed out after ${timeoutMs}ms`);
|
|
22
|
-
this.name =
|
|
23
|
+
this.name = "OpenAIEmbeddingsTimeoutError";
|
|
23
24
|
this.timeoutMs = timeoutMs;
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
|
-
/**
|
|
27
|
-
* Strip anything that looks like an API key / bearer token from an upstream
|
|
28
|
-
* error body before it leaves the process. Some "OpenAI-compatible" endpoints
|
|
29
|
-
* echo the Authorization header in error payloads.
|
|
30
|
-
*/
|
|
31
|
-
function sanitizeUpstreamError(text, apiKey) {
|
|
32
|
-
let cleaned = text;
|
|
33
|
-
if (apiKey) {
|
|
34
|
-
cleaned = cleaned.split(apiKey).join('[redacted]');
|
|
35
|
-
}
|
|
36
|
-
cleaned = cleaned
|
|
37
|
-
.replace(/\b(sk|sk-proj|rk|pk|api[_-]?key)[-_][A-Za-z0-9_-]{12,}/gi, '[redacted]')
|
|
38
|
-
.replace(/\bbearer\s+[A-Za-z0-9._-]+/gi, 'bearer [redacted]');
|
|
39
|
-
return cleaned.slice(0, 300);
|
|
40
|
-
}
|
|
41
27
|
export async function generateOpenAIEmbeddings(config) {
|
|
42
28
|
const controller = new AbortController();
|
|
43
29
|
let timedOut = false;
|
|
@@ -48,9 +34,9 @@ export async function generateOpenAIEmbeddings(config) {
|
|
|
48
34
|
let response;
|
|
49
35
|
try {
|
|
50
36
|
response = await fetch(config.apiUrl, {
|
|
51
|
-
method:
|
|
37
|
+
method: "POST",
|
|
52
38
|
headers: {
|
|
53
|
-
|
|
39
|
+
"Content-Type": "application/json",
|
|
54
40
|
Authorization: `Bearer ${config.apiKey}`,
|
|
55
41
|
},
|
|
56
42
|
body: JSON.stringify({
|
|
@@ -72,14 +58,14 @@ export async function generateOpenAIEmbeddings(config) {
|
|
|
72
58
|
if (!response.ok) {
|
|
73
59
|
// A truncated/aborted upstream may make response.text() throw; still emit
|
|
74
60
|
// a typed error with the status.
|
|
75
|
-
let errorText =
|
|
61
|
+
let errorText = "";
|
|
76
62
|
try {
|
|
77
63
|
errorText = await response.text();
|
|
78
64
|
}
|
|
79
65
|
catch {
|
|
80
|
-
errorText =
|
|
66
|
+
errorText = "<unreadable response body>";
|
|
81
67
|
}
|
|
82
|
-
throw new OpenAIEmbeddingsHTTPError(response.status, `OpenAI embeddings error (${response.status}): ${
|
|
68
|
+
throw new OpenAIEmbeddingsHTTPError(response.status, `OpenAI embeddings error (${response.status}): ${scrubSecrets(errorText, [config.apiKey]).slice(0, 300)}`);
|
|
83
69
|
}
|
|
84
70
|
let payload;
|
|
85
71
|
try {
|
|
@@ -90,7 +76,7 @@ export async function generateOpenAIEmbeddings(config) {
|
|
|
90
76
|
throw new Error(`OpenAI embeddings response was not valid JSON: ${detail}`);
|
|
91
77
|
}
|
|
92
78
|
if (!Array.isArray(payload.data)) {
|
|
93
|
-
throw new Error(
|
|
79
|
+
throw new Error("OpenAI embeddings response missing data array");
|
|
94
80
|
}
|
|
95
81
|
if (payload.data.length !== config.texts.length) {
|
|
96
82
|
throw new Error(`OpenAI embeddings response returned ${payload.data.length} embeddings for ${config.texts.length} texts`);
|
|
@@ -99,11 +85,11 @@ export async function generateOpenAIEmbeddings(config) {
|
|
|
99
85
|
// so callers can reorder. Reorder before returning to keep vectors aligned.
|
|
100
86
|
const embeddings = new Array(payload.data.length);
|
|
101
87
|
for (const item of payload.data) {
|
|
102
|
-
if (typeof item?.index !==
|
|
88
|
+
if (typeof item?.index !== "number" ||
|
|
103
89
|
item.index < 0 ||
|
|
104
90
|
item.index >= embeddings.length ||
|
|
105
91
|
!Array.isArray(item.embedding)) {
|
|
106
|
-
throw new Error(
|
|
92
|
+
throw new Error("OpenAI embeddings response item missing index/embedding");
|
|
107
93
|
}
|
|
108
94
|
if (embeddings[item.index] !== undefined) {
|
|
109
95
|
throw new Error(`OpenAI embeddings response has duplicate index ${item.index}`);
|
|
@@ -111,7 +97,7 @@ export async function generateOpenAIEmbeddings(config) {
|
|
|
111
97
|
embeddings[item.index] = item.embedding;
|
|
112
98
|
}
|
|
113
99
|
for (const embedding of embeddings) {
|
|
114
|
-
validateEmbeddingDimensions(embedding, config.expectedDimensions,
|
|
100
|
+
validateEmbeddingDimensions(embedding, config.expectedDimensions, "OpenAI embeddings response");
|
|
115
101
|
}
|
|
116
102
|
return config.normalize ? normalizeEmbeddings(embeddings) : embeddings;
|
|
117
103
|
}
|
package/dist/openai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"openai.js","sourceRoot":"","sources":["../src/openai.ts"],"names":[],"mappings":"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"}
|
package/dist/server.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { timingSafeEqual } from
|
|
2
|
-
import { serve } from
|
|
3
|
-
import { Hono } from
|
|
4
|
-
import { validateEmbeddingDimensions } from
|
|
5
|
-
import { batchGenerateLocalEmbeddings, getLocalModelName } from
|
|
6
|
-
import {
|
|
1
|
+
import { timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { serve } from "@hono/node-server";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
import { validateEmbeddingDimensions } from "./embedding-utils.js";
|
|
5
|
+
import { batchGenerateLocalEmbeddings, getLocalModelName, } from "./embeddings.js";
|
|
6
|
+
import { scrubSecrets } from "./internal/scrub-secrets.js";
|
|
7
|
+
import { generateOpenAIEmbeddings, OpenAIEmbeddingsTimeoutError, } from "./openai.js";
|
|
7
8
|
const DEFAULT_PORT = 8790;
|
|
8
9
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
9
10
|
const DEFAULT_DIMENSIONS = 768;
|
|
@@ -16,54 +17,48 @@ const MAX_TEXT_BYTES = 32 * 1024;
|
|
|
16
17
|
const MODEL_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._/:-]{0,127}$/;
|
|
17
18
|
const app = new Hono();
|
|
18
19
|
function resolveNumber(value, fallback) {
|
|
19
|
-
const parsed = Number.parseInt(value ||
|
|
20
|
+
const parsed = Number.parseInt(value || "", 10);
|
|
20
21
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
21
22
|
}
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
cleaned = cleaned.split(apiKey).join('[redacted]');
|
|
28
|
-
}
|
|
29
|
-
if (serviceToken) {
|
|
30
|
-
cleaned = cleaned.split(serviceToken).join('[redacted]');
|
|
31
|
-
}
|
|
32
|
-
return cleaned
|
|
33
|
-
.replace(/\b(sk|sk-proj|rk|pk|api[_-]?key)[-_][A-Za-z0-9_-]{12,}/gi, '[redacted]')
|
|
34
|
-
.replace(/\bbearer\s+[A-Za-z0-9._-]+/gi, 'bearer [redacted]');
|
|
23
|
+
function scrubServerSecrets(message) {
|
|
24
|
+
return scrubSecrets(message, [
|
|
25
|
+
process.env.EMBEDDINGS_API_KEY ?? "",
|
|
26
|
+
process.env.EMBEDDINGS_SERVICE_TOKEN ?? "",
|
|
27
|
+
]);
|
|
35
28
|
}
|
|
36
29
|
function parseTexts(payload) {
|
|
37
30
|
if (!Array.isArray(payload.texts)) {
|
|
38
|
-
return { error:
|
|
31
|
+
return { error: "texts must be an array of strings" };
|
|
39
32
|
}
|
|
40
33
|
if (payload.texts.length > MAX_TEXTS_PER_REQUEST) {
|
|
41
|
-
return {
|
|
34
|
+
return {
|
|
35
|
+
error: `texts cannot contain more than ${MAX_TEXTS_PER_REQUEST} entries`,
|
|
36
|
+
};
|
|
42
37
|
}
|
|
43
38
|
const texts = [];
|
|
44
39
|
for (const value of payload.texts) {
|
|
45
|
-
if (typeof value !==
|
|
46
|
-
return { error:
|
|
40
|
+
if (typeof value !== "string") {
|
|
41
|
+
return { error: "texts must be an array of strings" };
|
|
47
42
|
}
|
|
48
43
|
const trimmed = value.trim();
|
|
49
44
|
if (!trimmed) {
|
|
50
|
-
return { error:
|
|
45
|
+
return { error: "texts cannot contain empty strings" };
|
|
51
46
|
}
|
|
52
|
-
if (Buffer.byteLength(trimmed,
|
|
47
|
+
if (Buffer.byteLength(trimmed, "utf8") > MAX_TEXT_BYTES) {
|
|
53
48
|
return { error: `each text must be at most ${MAX_TEXT_BYTES} bytes` };
|
|
54
49
|
}
|
|
55
50
|
texts.push(trimmed);
|
|
56
51
|
}
|
|
57
52
|
if (texts.length === 0) {
|
|
58
|
-
return { error:
|
|
53
|
+
return { error: "texts cannot be empty" };
|
|
59
54
|
}
|
|
60
55
|
return { texts };
|
|
61
56
|
}
|
|
62
57
|
// Constant-time bearer-token compare. timingSafeEqual requires equal-length
|
|
63
58
|
// buffers; reject mismatched lengths up front (length itself isn't secret).
|
|
64
59
|
function tokensMatch(provided, expected) {
|
|
65
|
-
const a = Buffer.from(provided,
|
|
66
|
-
const b = Buffer.from(expected,
|
|
60
|
+
const a = Buffer.from(provided, "utf8");
|
|
61
|
+
const b = Buffer.from(expected, "utf8");
|
|
67
62
|
if (a.length !== b.length) {
|
|
68
63
|
return false;
|
|
69
64
|
}
|
|
@@ -74,19 +69,19 @@ function requireAuth(request) {
|
|
|
74
69
|
if (!token) {
|
|
75
70
|
return null;
|
|
76
71
|
}
|
|
77
|
-
const header = request.headers.get(
|
|
72
|
+
const header = request.headers.get("authorization") || "";
|
|
78
73
|
const match = header.match(/^Bearer\s+(.+)$/i);
|
|
79
74
|
if (!match || !tokensMatch(match[1], token)) {
|
|
80
|
-
return
|
|
75
|
+
return "Unauthorized";
|
|
81
76
|
}
|
|
82
77
|
return null;
|
|
83
78
|
}
|
|
84
|
-
app.get(
|
|
85
|
-
const backend = (process.env.EMBEDDINGS_BACKEND ||
|
|
86
|
-
const model = backend ===
|
|
79
|
+
app.get("/health", (c) => {
|
|
80
|
+
const backend = (process.env.EMBEDDINGS_BACKEND || "local").toLowerCase();
|
|
81
|
+
const model = backend === "openai" ? process.env.EMBEDDINGS_MODEL : getLocalModelName();
|
|
87
82
|
return c.json({ ok: true, backend, model });
|
|
88
83
|
});
|
|
89
|
-
app.post(
|
|
84
|
+
app.post("/api/embeddings", async (c) => {
|
|
90
85
|
const authError = requireAuth(c.req.raw);
|
|
91
86
|
if (authError) {
|
|
92
87
|
return c.json({ error: authError }, 401);
|
|
@@ -96,30 +91,31 @@ app.post('/api/embeddings', async (c) => {
|
|
|
96
91
|
payload = (await c.req.json());
|
|
97
92
|
}
|
|
98
93
|
catch {
|
|
99
|
-
return c.json({ error:
|
|
94
|
+
return c.json({ error: "Invalid JSON payload" }, 400);
|
|
100
95
|
}
|
|
101
96
|
const parsed = parseTexts(payload);
|
|
102
|
-
if (
|
|
97
|
+
if ("error" in parsed) {
|
|
103
98
|
return c.json({ error: parsed.error }, 400);
|
|
104
99
|
}
|
|
105
|
-
const backend = (process.env.EMBEDDINGS_BACKEND ||
|
|
100
|
+
const backend = (process.env.EMBEDDINGS_BACKEND || "local").toLowerCase();
|
|
106
101
|
const expectedDimensions = resolveNumber(process.env.EMBEDDINGS_DIMENSIONS, DEFAULT_DIMENSIONS);
|
|
107
102
|
const timeoutMs = resolveNumber(process.env.EMBEDDINGS_TIMEOUT_MS, DEFAULT_TIMEOUT_MS);
|
|
108
|
-
const normalize = process.env.EMBEDDINGS_NORMALIZE !==
|
|
103
|
+
const normalize = process.env.EMBEDDINGS_NORMALIZE !== "false";
|
|
109
104
|
try {
|
|
110
|
-
if (backend ===
|
|
105
|
+
if (backend === "openai") {
|
|
111
106
|
const apiKey = process.env.EMBEDDINGS_API_KEY;
|
|
112
107
|
if (!apiKey) {
|
|
113
|
-
return c.json({ error:
|
|
108
|
+
return c.json({ error: "EMBEDDINGS_API_KEY is required for openai backend" }, 500);
|
|
114
109
|
}
|
|
115
|
-
const apiUrl = process.env.EMBEDDINGS_API_URL ||
|
|
110
|
+
const apiUrl = process.env.EMBEDDINGS_API_URL ||
|
|
111
|
+
"https://api.openai.com/v1/embeddings";
|
|
116
112
|
const model = payload.model || process.env.EMBEDDINGS_MODEL;
|
|
117
113
|
if (!model) {
|
|
118
|
-
return c.json({ error:
|
|
114
|
+
return c.json({ error: "EMBEDDINGS_MODEL is required for openai backend" }, 500);
|
|
119
115
|
}
|
|
120
116
|
// Client-supplied model strings are validated; env-var fallback is trusted.
|
|
121
117
|
if (payload.model && !MODEL_NAME_PATTERN.test(payload.model)) {
|
|
122
|
-
return c.json({ error:
|
|
118
|
+
return c.json({ error: "invalid model identifier" }, 400);
|
|
123
119
|
}
|
|
124
120
|
const embeddings = await generateOpenAIEmbeddings({
|
|
125
121
|
texts: parsed.texts,
|
|
@@ -139,7 +135,7 @@ app.post('/api/embeddings', async (c) => {
|
|
|
139
135
|
const batchSize = resolveNumber(process.env.EMBEDDINGS_BATCH_SIZE, DEFAULT_BATCH_SIZE);
|
|
140
136
|
const embeddings = await batchGenerateLocalEmbeddings(parsed.texts, batchSize);
|
|
141
137
|
for (const embedding of embeddings) {
|
|
142
|
-
validateEmbeddingDimensions(embedding, expectedDimensions,
|
|
138
|
+
validateEmbeddingDimensions(embedding, expectedDimensions, "Local embeddings response");
|
|
143
139
|
}
|
|
144
140
|
return c.json({
|
|
145
141
|
model: getLocalModelName(),
|
|
@@ -148,10 +144,10 @@ app.post('/api/embeddings', async (c) => {
|
|
|
148
144
|
});
|
|
149
145
|
}
|
|
150
146
|
catch (error) {
|
|
151
|
-
const rawMessage = error instanceof Error ? error.message :
|
|
147
|
+
const rawMessage = error instanceof Error ? error.message : "Embedding generation failed";
|
|
152
148
|
// Defense-in-depth scrub before logging/returning.
|
|
153
|
-
const message =
|
|
154
|
-
console.error(
|
|
149
|
+
const message = scrubServerSecrets(rawMessage);
|
|
150
|
+
console.error("[EmbeddingsService] Error:", message);
|
|
155
151
|
if (error instanceof OpenAIEmbeddingsTimeoutError) {
|
|
156
152
|
return c.json({ error: message }, 504);
|
|
157
153
|
}
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|