@copilotkit/aimock 1.43.0 → 1.44.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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +8 -2
- package/dist/agui-handler.d.ts.map +1 -1
- package/dist/agui-types.d.ts.map +1 -1
- package/dist/bedrock-converse.cjs +32 -4
- package/dist/bedrock-converse.cjs.map +1 -1
- package/dist/bedrock-converse.d.cts.map +1 -1
- package/dist/bedrock-converse.d.ts.map +1 -1
- package/dist/bedrock-converse.js +32 -4
- package/dist/bedrock-converse.js.map +1 -1
- package/dist/chaos.cjs +8 -5
- package/dist/chaos.cjs.map +1 -1
- package/dist/chaos.d.cts.map +1 -1
- package/dist/chaos.d.ts.map +1 -1
- package/dist/chaos.js +9 -6
- package/dist/chaos.js.map +1 -1
- package/dist/cli.cjs +89 -7
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +91 -9
- package/dist/cli.js.map +1 -1
- package/dist/cohere.cjs +27 -1
- package/dist/cohere.cjs.map +1 -1
- package/dist/cohere.js +27 -1
- package/dist/cohere.js.map +1 -1
- package/dist/config-loader.cjs +1 -1
- package/dist/config-loader.d.ts.map +1 -1
- package/dist/config-loader.js +1 -1
- package/dist/constants.cjs +8 -0
- package/dist/constants.cjs.map +1 -1
- package/dist/constants.d.cts +6 -2
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +8 -1
- package/dist/constants.js.map +1 -1
- package/dist/echo-text.cjs +65 -0
- package/dist/echo-text.cjs.map +1 -0
- package/dist/echo-text.d.cts +22 -0
- package/dist/echo-text.d.cts.map +1 -0
- package/dist/echo-text.d.ts +22 -0
- package/dist/echo-text.d.ts.map +1 -0
- package/dist/echo-text.js +65 -0
- package/dist/echo-text.js.map +1 -0
- package/dist/embeddings.cjs +18 -1
- package/dist/embeddings.cjs.map +1 -1
- package/dist/embeddings.d.cts +1 -1
- package/dist/embeddings.d.cts.map +1 -1
- package/dist/embeddings.d.ts +1 -1
- package/dist/embeddings.d.ts.map +1 -1
- package/dist/embeddings.js +18 -1
- package/dist/embeddings.js.map +1 -1
- package/dist/files.cjs +9 -11
- package/dist/files.cjs.map +1 -1
- package/dist/files.d.cts.map +1 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +10 -13
- package/dist/files.js.map +1 -1
- package/dist/fine-tuning.cjs +1 -19
- package/dist/fine-tuning.cjs.map +1 -1
- package/dist/fine-tuning.d.cts.map +1 -1
- package/dist/fine-tuning.d.ts.map +1 -1
- package/dist/fine-tuning.js +2 -20
- package/dist/fine-tuning.js.map +1 -1
- package/dist/fixture-loader-services.cjs +116 -0
- package/dist/fixture-loader-services.cjs.map +1 -0
- package/dist/fixture-loader-services.d.cts +48 -0
- package/dist/fixture-loader-services.d.cts.map +1 -0
- package/dist/fixture-loader-services.d.ts +48 -0
- package/dist/fixture-loader-services.d.ts.map +1 -0
- package/dist/fixture-loader-services.js +114 -0
- package/dist/fixture-loader-services.js.map +1 -0
- package/dist/fixture-loader.cjs +172 -11
- package/dist/fixture-loader.cjs.map +1 -1
- package/dist/fixture-loader.d.cts +95 -1
- package/dist/fixture-loader.d.cts.map +1 -1
- package/dist/fixture-loader.d.ts +95 -1
- package/dist/fixture-loader.d.ts.map +1 -1
- package/dist/fixture-loader.js +168 -12
- package/dist/fixture-loader.js.map +1 -1
- package/dist/gemini-embeddings.cjs +42 -2
- package/dist/gemini-embeddings.cjs.map +1 -1
- package/dist/gemini-embeddings.js +43 -3
- package/dist/gemini-embeddings.js.map +1 -1
- package/dist/gemini-interactions.cjs +38 -0
- package/dist/gemini-interactions.cjs.map +1 -1
- package/dist/gemini-interactions.d.cts.map +1 -1
- package/dist/gemini-interactions.d.ts.map +1 -1
- package/dist/gemini-interactions.js +38 -0
- package/dist/gemini-interactions.js.map +1 -1
- package/dist/gemini.cjs +8 -1
- package/dist/gemini.cjs.map +1 -1
- package/dist/gemini.d.cts.map +1 -1
- package/dist/gemini.d.ts.map +1 -1
- package/dist/gemini.js +8 -1
- package/dist/gemini.js.map +1 -1
- package/dist/helpers.cjs +276 -5
- package/dist/helpers.cjs.map +1 -1
- package/dist/helpers.d.cts.map +1 -1
- package/dist/helpers.d.ts.map +1 -1
- package/dist/helpers.js +275 -7
- package/dist/helpers.js.map +1 -1
- package/dist/images.cjs +3 -2
- package/dist/images.cjs.map +1 -1
- package/dist/images.js +3 -2
- package/dist/images.js.map +1 -1
- package/dist/index.cjs +30 -1
- package/dist/index.d.cts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +9 -3
- package/dist/jest.cjs +15 -10
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +4 -2
- package/dist/jest.d.cts.map +1 -1
- package/dist/jest.d.ts +4 -2
- package/dist/jest.d.ts.map +1 -1
- package/dist/jest.js +16 -11
- package/dist/jest.js.map +1 -1
- package/dist/journal.cjs +20 -0
- package/dist/journal.cjs.map +1 -1
- package/dist/journal.d.cts +16 -0
- package/dist/journal.d.cts.map +1 -1
- package/dist/journal.d.ts +16 -0
- package/dist/journal.d.ts.map +1 -1
- package/dist/journal.js +20 -0
- package/dist/journal.js.map +1 -1
- package/dist/llmock.cjs +82 -10
- package/dist/llmock.cjs.map +1 -1
- package/dist/llmock.d.cts +35 -0
- package/dist/llmock.d.cts.map +1 -1
- package/dist/llmock.d.ts +35 -0
- package/dist/llmock.d.ts.map +1 -1
- package/dist/llmock.js +83 -11
- package/dist/llmock.js.map +1 -1
- package/dist/mcp-fakes-mount.cjs +216 -0
- package/dist/mcp-fakes-mount.cjs.map +1 -0
- package/dist/mcp-fakes-mount.js +210 -0
- package/dist/mcp-fakes-mount.js.map +1 -0
- package/dist/mcp-fakes.cjs +1619 -0
- package/dist/mcp-fakes.cjs.map +1 -0
- package/dist/mcp-fakes.d.cts +101 -0
- package/dist/mcp-fakes.d.cts.map +1 -0
- package/dist/mcp-fakes.d.ts +101 -0
- package/dist/mcp-fakes.d.ts.map +1 -0
- package/dist/mcp-fakes.js +1614 -0
- package/dist/mcp-fakes.js.map +1 -0
- package/dist/mcp-handler.cjs +331 -17
- package/dist/mcp-handler.cjs.map +1 -1
- package/dist/mcp-handler.js +330 -18
- package/dist/mcp-handler.js.map +1 -1
- package/dist/mcp-mock.cjs +217 -49
- package/dist/mcp-mock.cjs.map +1 -1
- package/dist/mcp-mock.d.cts +66 -1
- package/dist/mcp-mock.d.cts.map +1 -1
- package/dist/mcp-mock.d.ts +66 -1
- package/dist/mcp-mock.d.ts.map +1 -1
- package/dist/mcp-mock.js +218 -50
- package/dist/mcp-mock.js.map +1 -1
- package/dist/mcp-stub.cjs +5 -1
- package/dist/mcp-stub.d.cts +3 -1
- package/dist/mcp-stub.d.ts +3 -1
- package/dist/mcp-stub.js +3 -1
- package/dist/mcp-types.d.cts +6 -0
- package/dist/mcp-types.d.ts +6 -0
- package/dist/message-text.cjs +192 -0
- package/dist/message-text.cjs.map +1 -0
- package/dist/message-text.d.cts +31 -0
- package/dist/message-text.d.cts.map +1 -0
- package/dist/message-text.d.ts +31 -0
- package/dist/message-text.d.ts.map +1 -0
- package/dist/message-text.js +184 -0
- package/dist/message-text.js.map +1 -0
- package/dist/messages.cjs +38 -0
- package/dist/messages.cjs.map +1 -1
- package/dist/messages.js +38 -0
- package/dist/messages.js.map +1 -1
- package/dist/metrics.cjs +46 -66
- package/dist/metrics.cjs.map +1 -1
- package/dist/metrics.d.cts +14 -6
- package/dist/metrics.d.cts.map +1 -1
- package/dist/metrics.d.ts +14 -6
- package/dist/metrics.d.ts.map +1 -1
- package/dist/metrics.js +32 -40
- package/dist/metrics.js.map +1 -1
- package/dist/ollama.cjs +5 -1
- package/dist/ollama.cjs.map +1 -1
- package/dist/ollama.d.cts.map +1 -1
- package/dist/ollama.d.ts.map +1 -1
- package/dist/ollama.js +5 -1
- package/dist/ollama.js.map +1 -1
- package/dist/openapi.cjs +3405 -0
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +30 -0
- package/dist/openapi.d.cts.map +1 -0
- package/dist/openapi.d.ts +30 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +3404 -0
- package/dist/openapi.js.map +1 -0
- package/dist/openrouter-video.cjs +4 -4
- package/dist/openrouter-video.cjs.map +1 -1
- package/dist/openrouter-video.d.cts.map +1 -1
- package/dist/openrouter-video.d.ts.map +1 -1
- package/dist/openrouter-video.js +4 -4
- package/dist/openrouter-video.js.map +1 -1
- package/dist/recorder.cjs +9 -1
- package/dist/recorder.cjs.map +1 -1
- package/dist/recorder.d.cts.map +1 -1
- package/dist/recorder.d.ts.map +1 -1
- package/dist/recorder.js +9 -1
- package/dist/recorder.js.map +1 -1
- package/dist/responses.cjs +43 -4
- package/dist/responses.cjs.map +1 -1
- package/dist/responses.d.cts.map +1 -1
- package/dist/responses.d.ts.map +1 -1
- package/dist/responses.js +43 -4
- package/dist/responses.js.map +1 -1
- package/dist/route-registry.cjs +951 -0
- package/dist/route-registry.cjs.map +1 -0
- package/dist/route-registry.d.cts +33 -0
- package/dist/route-registry.d.cts.map +1 -0
- package/dist/route-registry.d.ts +33 -0
- package/dist/route-registry.d.ts.map +1 -0
- package/dist/route-registry.js +866 -0
- package/dist/route-registry.js.map +1 -0
- package/dist/router.cjs +1 -1
- package/dist/router.cjs.map +1 -1
- package/dist/router.js +1 -1
- package/dist/router.js.map +1 -1
- package/dist/server.cjs +590 -191
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +4 -1
- package/dist/server.d.cts.map +1 -1
- package/dist/server.d.ts +4 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +498 -99
- package/dist/server.js.map +1 -1
- package/dist/speech.cjs +4 -2
- package/dist/speech.cjs.map +1 -1
- package/dist/speech.js +4 -2
- package/dist/speech.js.map +1 -1
- package/dist/suite.cjs +1 -1
- package/dist/suite.js +1 -1
- package/dist/transcription.cjs +2 -2
- package/dist/transcription.cjs.map +1 -1
- package/dist/transcription.d.cts.map +1 -1
- package/dist/transcription.d.ts.map +1 -1
- package/dist/transcription.js +2 -2
- package/dist/transcription.js.map +1 -1
- package/dist/types.cjs +18 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.cts +202 -2
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.ts +202 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -0
- package/dist/validate-cli.cjs +89 -31
- package/dist/validate-cli.cjs.map +1 -1
- package/dist/validate-cli.js +91 -33
- package/dist/validate-cli.js.map +1 -1
- package/dist/vector-stores.cjs +1180 -0
- package/dist/vector-stores.cjs.map +1 -0
- package/dist/vector-stores.d.cts +106 -0
- package/dist/vector-stores.d.cts.map +1 -0
- package/dist/vector-stores.d.ts +106 -0
- package/dist/vector-stores.d.ts.map +1 -0
- package/dist/vector-stores.js +1164 -0
- package/dist/vector-stores.js.map +1 -0
- package/dist/vector-types.d.cts.map +1 -1
- package/dist/vector-types.d.ts.map +1 -1
- package/dist/video.cjs +19 -0
- package/dist/video.cjs.map +1 -1
- package/dist/video.d.cts.map +1 -1
- package/dist/video.d.ts.map +1 -1
- package/dist/video.js +19 -0
- package/dist/video.js.map +1 -1
- package/dist/vitest.cjs +15 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +4 -2
- package/dist/vitest.d.cts.map +1 -1
- package/dist/vitest.d.ts +4 -2
- package/dist/vitest.d.ts.map +1 -1
- package/dist/vitest.js +16 -11
- package/dist/vitest.js.map +1 -1
- package/dist/ws-gemini-live.d.ts +2 -2
- package/dist/ws-realtime.d.ts +2 -2
- package/dist/ws-responses.cjs +14 -2
- package/dist/ws-responses.cjs.map +1 -1
- package/dist/ws-responses.js +14 -2
- package/dist/ws-responses.js.map +1 -1
- package/package.json +4 -1
|
@@ -0,0 +1,1164 @@
|
|
|
1
|
+
import { flattenHeaders, isJsonObject, parseStrictIntegerText } from "./helpers.js";
|
|
2
|
+
import { applyChaosAsync } from "./chaos.js";
|
|
3
|
+
import { readMetadata } from "./fine-tuning.js";
|
|
4
|
+
import { getStoredFileBytes, getStoredFileName } from "./files.js";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
6
|
+
|
|
7
|
+
//#region src/vector-stores.ts
|
|
8
|
+
const stores = /* @__PURE__ */ new Map();
|
|
9
|
+
const storeFiles = /* @__PURE__ */ new Map();
|
|
10
|
+
const storePolls = /* @__PURE__ */ new Map();
|
|
11
|
+
const fileOutcomes = /* @__PURE__ */ new Map();
|
|
12
|
+
const fileBatches = /* @__PURE__ */ new Map();
|
|
13
|
+
const batchPolls = /* @__PURE__ */ new Map();
|
|
14
|
+
const batchOutcomes = /* @__PURE__ */ new Map();
|
|
15
|
+
const batchMembers = /* @__PURE__ */ new Map();
|
|
16
|
+
let lastStamp = 0;
|
|
17
|
+
function clearVectorStoreStore() {
|
|
18
|
+
stores.clear();
|
|
19
|
+
storeFiles.clear();
|
|
20
|
+
storePolls.clear();
|
|
21
|
+
fileOutcomes.clear();
|
|
22
|
+
fileBatches.clear();
|
|
23
|
+
batchPolls.clear();
|
|
24
|
+
batchOutcomes.clear();
|
|
25
|
+
batchMembers.clear();
|
|
26
|
+
lastStamp = 0;
|
|
27
|
+
}
|
|
28
|
+
/** Remove every vector attachment when its underlying uploaded File is deleted. */
|
|
29
|
+
function removeVectorStoreFile(fileId) {
|
|
30
|
+
const affectedStores = /* @__PURE__ */ new Set();
|
|
31
|
+
for (const [key, entry] of storeFiles) {
|
|
32
|
+
if (entry.id !== fileId) continue;
|
|
33
|
+
storeFiles.delete(key);
|
|
34
|
+
storePolls.delete(key);
|
|
35
|
+
fileOutcomes.delete(key);
|
|
36
|
+
affectedStores.add(entry.vector_store_id);
|
|
37
|
+
}
|
|
38
|
+
for (const [batchId, members] of batchMembers) {
|
|
39
|
+
const remaining = members.filter((entry) => entry.id !== fileId);
|
|
40
|
+
if (remaining.length === members.length) continue;
|
|
41
|
+
batchMembers.set(batchId, remaining);
|
|
42
|
+
const batch = fileBatches.get(batchId);
|
|
43
|
+
if (!batch) continue;
|
|
44
|
+
batch.file_counts = batchCounts(batchId);
|
|
45
|
+
if (remaining.length === 0 && !TERMINAL_BATCH_STATUSES.has(batch.status)) {
|
|
46
|
+
batch.status = batch.status === "cancelling" ? "cancelled" : "completed";
|
|
47
|
+
batchPolls.delete(batchId);
|
|
48
|
+
batchOutcomes.delete(batchId);
|
|
49
|
+
}
|
|
50
|
+
affectedStores.add(batch.vector_store_id);
|
|
51
|
+
}
|
|
52
|
+
for (const storeId of affectedStores) {
|
|
53
|
+
const store = stores.get(storeId);
|
|
54
|
+
if (store) refreshStoreStatus(store);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const TERMINAL_FILE_STATUSES = new Set([
|
|
58
|
+
"completed",
|
|
59
|
+
"failed",
|
|
60
|
+
"cancelled"
|
|
61
|
+
]);
|
|
62
|
+
const TERMINAL_BATCH_STATUSES = new Set([
|
|
63
|
+
"completed",
|
|
64
|
+
"failed",
|
|
65
|
+
"cancelled"
|
|
66
|
+
]);
|
|
67
|
+
const OUTCOMES = new Set([
|
|
68
|
+
"completed",
|
|
69
|
+
"failed",
|
|
70
|
+
"cancelled"
|
|
71
|
+
]);
|
|
72
|
+
const OUTCOME_HEADER = "x-aimock-vector-outcome";
|
|
73
|
+
const DEFAULT_PAGE_LIMIT = 20;
|
|
74
|
+
const MAX_PAGE_LIMIT = 100;
|
|
75
|
+
function stamp() {
|
|
76
|
+
lastStamp = Math.max(Math.floor(Date.now() / 1e3), lastStamp);
|
|
77
|
+
return lastStamp;
|
|
78
|
+
}
|
|
79
|
+
function underscoreId(prefix) {
|
|
80
|
+
return `${prefix}_${randomBytes(12).toString("base64url")}`;
|
|
81
|
+
}
|
|
82
|
+
function invalid(message) {
|
|
83
|
+
return { error: {
|
|
84
|
+
message,
|
|
85
|
+
type: "invalid_request_error"
|
|
86
|
+
} };
|
|
87
|
+
}
|
|
88
|
+
function writeJson(res, status, payload, setCorsHeaders) {
|
|
89
|
+
setCorsHeaders(res);
|
|
90
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
91
|
+
res.end(JSON.stringify(payload));
|
|
92
|
+
}
|
|
93
|
+
function journalVs(journal, method, path, headers, status) {
|
|
94
|
+
journal.add({
|
|
95
|
+
method,
|
|
96
|
+
path,
|
|
97
|
+
headers,
|
|
98
|
+
body: null,
|
|
99
|
+
service: "vector-stores",
|
|
100
|
+
response: {
|
|
101
|
+
status,
|
|
102
|
+
fixture: null,
|
|
103
|
+
source: "internal"
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function chaosHit(req, journal, defaults, method, path, res, setCorsHeaders) {
|
|
108
|
+
setCorsHeaders(res);
|
|
109
|
+
return await applyChaosAsync(res, null, defaults.chaos, req.headers, req.url, journal, {
|
|
110
|
+
method,
|
|
111
|
+
path,
|
|
112
|
+
headers: flattenHeaders(req.headers),
|
|
113
|
+
body: null,
|
|
114
|
+
service: "vector-stores"
|
|
115
|
+
}, "internal", defaults.registry, defaults.logger);
|
|
116
|
+
}
|
|
117
|
+
function fileKey(storeId, fileId) {
|
|
118
|
+
return `${storeId}:${fileId}`;
|
|
119
|
+
}
|
|
120
|
+
function touch(store) {
|
|
121
|
+
refreshStoreStatus(store);
|
|
122
|
+
if (store.status === "expired") return;
|
|
123
|
+
const now = stamp();
|
|
124
|
+
store.last_active_at = now;
|
|
125
|
+
if (store.expires_after) store.expires_at = now + store.expires_after.days * 86400;
|
|
126
|
+
refreshStoreStatus(store);
|
|
127
|
+
}
|
|
128
|
+
function refreshStoreStatus(store) {
|
|
129
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
130
|
+
const expired = store.expires_at !== void 0 && store.expires_at !== null && now >= store.expires_at;
|
|
131
|
+
const files = [...storeFiles.values()].filter((f) => f.vector_store_id === store.id);
|
|
132
|
+
const pending = files.filter((f) => f.status === "in_progress").length;
|
|
133
|
+
const batchesLive = [...fileBatches.values()].filter((b) => b.vector_store_id === store.id && (b.status === "in_progress" || b.status === "cancelling")).length;
|
|
134
|
+
store.status = expired ? "expired" : pending + batchesLive > 0 ? "in_progress" : "completed";
|
|
135
|
+
let usage = 0;
|
|
136
|
+
let inProgress = 0;
|
|
137
|
+
let completed = 0;
|
|
138
|
+
let failed = 0;
|
|
139
|
+
let cancelled = 0;
|
|
140
|
+
for (const f of files) {
|
|
141
|
+
if (f.status === "completed") usage += f.usage_bytes;
|
|
142
|
+
if (f.status === "in_progress") inProgress += 1;
|
|
143
|
+
else if (f.status === "completed") completed += 1;
|
|
144
|
+
else if (f.status === "failed") failed += 1;
|
|
145
|
+
else cancelled += 1;
|
|
146
|
+
}
|
|
147
|
+
store.usage_bytes = usage;
|
|
148
|
+
store.file_counts = {
|
|
149
|
+
in_progress: inProgress,
|
|
150
|
+
completed,
|
|
151
|
+
failed,
|
|
152
|
+
cancelled,
|
|
153
|
+
total: files.length
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function readExpiresAfter(value) {
|
|
157
|
+
if (value === void 0) return {
|
|
158
|
+
ok: true,
|
|
159
|
+
value: null
|
|
160
|
+
};
|
|
161
|
+
if (!isJsonObject(value)) return {
|
|
162
|
+
ok: false,
|
|
163
|
+
message: "Invalid parameter: 'expires_after' must be an object"
|
|
164
|
+
};
|
|
165
|
+
if (value["anchor"] !== "last_active_at") return {
|
|
166
|
+
ok: false,
|
|
167
|
+
message: "Invalid parameter: 'expires_after.anchor' must be 'last_active_at'"
|
|
168
|
+
};
|
|
169
|
+
const days = value["days"];
|
|
170
|
+
if (typeof days !== "number" || !Number.isInteger(days) || days < 1 || days > 365) return {
|
|
171
|
+
ok: false,
|
|
172
|
+
message: "Invalid parameter: 'expires_after.days' must be an integer between 1 and 365"
|
|
173
|
+
};
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
value: {
|
|
177
|
+
anchor: "last_active_at",
|
|
178
|
+
days
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function readChunkingStrategy(value) {
|
|
183
|
+
if (value === void 0) return {
|
|
184
|
+
ok: true,
|
|
185
|
+
value: null
|
|
186
|
+
};
|
|
187
|
+
if (!isJsonObject(value)) return {
|
|
188
|
+
ok: false,
|
|
189
|
+
message: "Invalid parameter: 'chunking_strategy' must be an object"
|
|
190
|
+
};
|
|
191
|
+
if (value["type"] === "auto") return {
|
|
192
|
+
ok: true,
|
|
193
|
+
value: { type: "auto" }
|
|
194
|
+
};
|
|
195
|
+
if (value["type"] === "static") {
|
|
196
|
+
const inner = value["static"];
|
|
197
|
+
if (!isJsonObject(inner)) return {
|
|
198
|
+
ok: false,
|
|
199
|
+
message: "Invalid parameter: 'chunking_strategy.static' must be an object"
|
|
200
|
+
};
|
|
201
|
+
const maxTokens = inner["max_chunk_size_tokens"];
|
|
202
|
+
const overlap = inner["chunk_overlap_tokens"];
|
|
203
|
+
if (typeof maxTokens !== "number" || !Number.isInteger(maxTokens) || maxTokens < 100 || maxTokens > 4096) return {
|
|
204
|
+
ok: false,
|
|
205
|
+
message: "Invalid parameter: 'chunking_strategy.static.max_chunk_size_tokens' must be an integer between 100 and 4096"
|
|
206
|
+
};
|
|
207
|
+
if (typeof overlap !== "number" || !Number.isInteger(overlap) || overlap < 0 || overlap > 2048 || overlap * 2 > maxTokens) return {
|
|
208
|
+
ok: false,
|
|
209
|
+
message: "Invalid parameter: 'chunking_strategy.static.chunk_overlap_tokens' must be an integer between 0 and 2048 and at most half of max_chunk_size_tokens"
|
|
210
|
+
};
|
|
211
|
+
return {
|
|
212
|
+
ok: true,
|
|
213
|
+
value: {
|
|
214
|
+
type: "static",
|
|
215
|
+
static: {
|
|
216
|
+
max_chunk_size_tokens: maxTokens,
|
|
217
|
+
chunk_overlap_tokens: overlap
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
ok: false,
|
|
224
|
+
message: "Invalid parameter: 'chunking_strategy.type' must be 'auto' or 'static'"
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
function isAttributeValue(value) {
|
|
228
|
+
return typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value);
|
|
229
|
+
}
|
|
230
|
+
function readAttributes(value) {
|
|
231
|
+
if (value === void 0 || value === null) return {
|
|
232
|
+
ok: true,
|
|
233
|
+
value: null
|
|
234
|
+
};
|
|
235
|
+
if (!isJsonObject(value) || Object.keys(value).length > 16) return {
|
|
236
|
+
ok: false,
|
|
237
|
+
message: "Invalid parameter: 'attributes' must be an object with at most 16 pairs or null"
|
|
238
|
+
};
|
|
239
|
+
const entries = [];
|
|
240
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
241
|
+
if ([...key].length > 64 || !isAttributeValue(entry) || typeof entry === "string" && [...entry].length > 512) return {
|
|
242
|
+
ok: false,
|
|
243
|
+
message: "Invalid parameter: 'attributes' keys must be at most 64 characters and values must be strings of at most 512 characters, finite numbers or booleans"
|
|
244
|
+
};
|
|
245
|
+
entries.push([key, entry]);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
ok: true,
|
|
249
|
+
value: Object.fromEntries(entries)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
const MAX_ATTRIBUTE_FILTER_DEPTH = 64;
|
|
253
|
+
function readAttributeFilter(value, depth = 0) {
|
|
254
|
+
if (depth > MAX_ATTRIBUTE_FILTER_DEPTH) return {
|
|
255
|
+
ok: false,
|
|
256
|
+
message: `Invalid parameter: 'filters' nesting must not exceed ${MAX_ATTRIBUTE_FILTER_DEPTH} levels`
|
|
257
|
+
};
|
|
258
|
+
if (value === void 0) return {
|
|
259
|
+
ok: true,
|
|
260
|
+
value: null
|
|
261
|
+
};
|
|
262
|
+
const error = {
|
|
263
|
+
ok: false,
|
|
264
|
+
message: "Invalid parameter: 'filters' must be a comparison (eq, ne, gt, gte, lt, lte) with key and scalar value, or an and/or filter with an array of filters"
|
|
265
|
+
};
|
|
266
|
+
if (!isJsonObject(value)) return error;
|
|
267
|
+
const type = value["type"];
|
|
268
|
+
if (type === "and" || type === "or") {
|
|
269
|
+
if (!Array.isArray(value["filters"])) return error;
|
|
270
|
+
const filters = [];
|
|
271
|
+
for (const item of value["filters"]) {
|
|
272
|
+
const child = readAttributeFilter(item, depth + 1);
|
|
273
|
+
if (!child.ok) return child;
|
|
274
|
+
if (child.value === null) return error;
|
|
275
|
+
filters.push(child.value);
|
|
276
|
+
}
|
|
277
|
+
return {
|
|
278
|
+
ok: true,
|
|
279
|
+
value: {
|
|
280
|
+
type,
|
|
281
|
+
filters
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const key = value["key"];
|
|
286
|
+
const operand = value["value"];
|
|
287
|
+
if ((type === "eq" || type === "ne" || type === "gt" || type === "gte" || type === "lt" || type === "lte") && typeof key === "string" && isAttributeValue(operand)) return {
|
|
288
|
+
ok: true,
|
|
289
|
+
value: {
|
|
290
|
+
type,
|
|
291
|
+
key,
|
|
292
|
+
value: operand
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
return error;
|
|
296
|
+
}
|
|
297
|
+
function matchesAttributeFilter(attributes, filter) {
|
|
298
|
+
if (filter.type === "and") return filter.filters.every((child) => matchesAttributeFilter(attributes, child));
|
|
299
|
+
if (filter.type === "or") return filter.filters.some((child) => matchesAttributeFilter(attributes, child));
|
|
300
|
+
if (!("key" in filter) || attributes === null || !Object.hasOwn(attributes, filter.key)) return false;
|
|
301
|
+
const actual = attributes[filter.key];
|
|
302
|
+
if (typeof actual !== typeof filter.value) return false;
|
|
303
|
+
switch (filter.type) {
|
|
304
|
+
case "eq": return actual === filter.value;
|
|
305
|
+
case "ne": return actual !== filter.value;
|
|
306
|
+
case "gt": return actual > filter.value;
|
|
307
|
+
case "gte": return actual >= filter.value;
|
|
308
|
+
case "lt": return actual < filter.value;
|
|
309
|
+
case "lte": return actual <= filter.value;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function readFileIds(value) {
|
|
313
|
+
if (value === void 0) return {
|
|
314
|
+
ok: true,
|
|
315
|
+
value: []
|
|
316
|
+
};
|
|
317
|
+
if (!Array.isArray(value)) return {
|
|
318
|
+
ok: false,
|
|
319
|
+
message: "Invalid parameter: 'file_ids' must be an array of strings"
|
|
320
|
+
};
|
|
321
|
+
if (value.length > 2e3) return {
|
|
322
|
+
ok: false,
|
|
323
|
+
message: "Invalid parameter: 'file_ids' must contain at most 2000 files"
|
|
324
|
+
};
|
|
325
|
+
for (const entry of value) if (typeof entry !== "string" || entry.length === 0) return {
|
|
326
|
+
ok: false,
|
|
327
|
+
message: "Invalid parameter: 'file_ids' must be an array of strings"
|
|
328
|
+
};
|
|
329
|
+
return {
|
|
330
|
+
ok: true,
|
|
331
|
+
value
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function readOutcome(req) {
|
|
335
|
+
const raw = req.headers[OUTCOME_HEADER];
|
|
336
|
+
const text = (Array.isArray(raw) ? raw[0] : raw)?.trim().toLowerCase();
|
|
337
|
+
if (text === void 0 || text === "") return {
|
|
338
|
+
ok: true,
|
|
339
|
+
value: "completed"
|
|
340
|
+
};
|
|
341
|
+
if (!OUTCOMES.has(text)) return {
|
|
342
|
+
ok: false,
|
|
343
|
+
message: `Invalid ${OUTCOME_HEADER} '${text}'. Expected one of: ${[...OUTCOMES].join(", ")}`
|
|
344
|
+
};
|
|
345
|
+
return {
|
|
346
|
+
ok: true,
|
|
347
|
+
value: text
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
function queryParams(url) {
|
|
351
|
+
const target = url ?? "/";
|
|
352
|
+
const start = target.indexOf("?");
|
|
353
|
+
return new URLSearchParams(start === -1 ? "" : target.slice(start + 1));
|
|
354
|
+
}
|
|
355
|
+
function singleParam(params, name) {
|
|
356
|
+
const all = params.getAll(name);
|
|
357
|
+
if (all.length > 1) return {
|
|
358
|
+
ok: false,
|
|
359
|
+
message: `Invalid parameter: '${name}' was given ${all.length} times; it takes a single value`
|
|
360
|
+
};
|
|
361
|
+
return {
|
|
362
|
+
ok: true,
|
|
363
|
+
value: all.length === 0 ? null : all[0]
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function paginateOrdered(itemsNewestFirst, url) {
|
|
367
|
+
const params = queryParams(url);
|
|
368
|
+
const readLimit = singleParam(params, "limit");
|
|
369
|
+
if (!readLimit.ok) return {
|
|
370
|
+
ok: false,
|
|
371
|
+
message: readLimit.message
|
|
372
|
+
};
|
|
373
|
+
const readOrder = singleParam(params, "order");
|
|
374
|
+
if (!readOrder.ok) return {
|
|
375
|
+
ok: false,
|
|
376
|
+
message: readOrder.message
|
|
377
|
+
};
|
|
378
|
+
const readAfter = singleParam(params, "after");
|
|
379
|
+
if (!readAfter.ok) return {
|
|
380
|
+
ok: false,
|
|
381
|
+
message: readAfter.message
|
|
382
|
+
};
|
|
383
|
+
const readBefore = singleParam(params, "before");
|
|
384
|
+
if (!readBefore.ok) return {
|
|
385
|
+
ok: false,
|
|
386
|
+
message: readBefore.message
|
|
387
|
+
};
|
|
388
|
+
let limit = DEFAULT_PAGE_LIMIT;
|
|
389
|
+
if (readLimit.value !== null) {
|
|
390
|
+
const n = parseStrictIntegerText(readLimit.value);
|
|
391
|
+
if (n === null || n < 1 || n > MAX_PAGE_LIMIT) return {
|
|
392
|
+
ok: false,
|
|
393
|
+
message: `Invalid parameter: 'limit' must be an integer between 1 and ${MAX_PAGE_LIMIT}, got '${readLimit.value}'`
|
|
394
|
+
};
|
|
395
|
+
limit = n;
|
|
396
|
+
}
|
|
397
|
+
const order = readOrder.value ?? "desc";
|
|
398
|
+
if (order !== "asc" && order !== "desc") return {
|
|
399
|
+
ok: false,
|
|
400
|
+
message: "Invalid parameter: 'order' must be 'asc' or 'desc'"
|
|
401
|
+
};
|
|
402
|
+
if (readAfter.value !== null && readBefore.value !== null) return {
|
|
403
|
+
ok: false,
|
|
404
|
+
message: "Invalid parameter: 'after' and 'before' are mutually exclusive"
|
|
405
|
+
};
|
|
406
|
+
const ordered = order === "desc" ? itemsNewestFirst : [...itemsNewestFirst].reverse();
|
|
407
|
+
let start = 0;
|
|
408
|
+
if (readAfter.value !== null) {
|
|
409
|
+
const idx = ordered.findIndex((it) => it.id === readAfter.value);
|
|
410
|
+
if (idx === -1) return {
|
|
411
|
+
ok: false,
|
|
412
|
+
message: `Invalid parameter: 'after' is not a known cursor: '${readAfter.value}'`
|
|
413
|
+
};
|
|
414
|
+
start = idx + 1;
|
|
415
|
+
} else if (readBefore.value !== null) {
|
|
416
|
+
const idx = ordered.findIndex((it) => it.id === readBefore.value);
|
|
417
|
+
if (idx === -1) return {
|
|
418
|
+
ok: false,
|
|
419
|
+
message: `Invalid parameter: 'before' is not a known cursor: '${readBefore.value}'`
|
|
420
|
+
};
|
|
421
|
+
start = Math.max(0, idx - limit);
|
|
422
|
+
const data = ordered.slice(start, idx);
|
|
423
|
+
return {
|
|
424
|
+
ok: true,
|
|
425
|
+
page: {
|
|
426
|
+
object: "list",
|
|
427
|
+
data,
|
|
428
|
+
first_id: data[0]?.id ?? null,
|
|
429
|
+
last_id: data[data.length - 1]?.id ?? null,
|
|
430
|
+
has_more: start > 0
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
const rest = ordered.slice(start);
|
|
435
|
+
const data = rest.slice(0, limit);
|
|
436
|
+
return {
|
|
437
|
+
ok: true,
|
|
438
|
+
page: {
|
|
439
|
+
object: "list",
|
|
440
|
+
data,
|
|
441
|
+
first_id: data[0]?.id ?? null,
|
|
442
|
+
last_id: data[data.length - 1]?.id ?? null,
|
|
443
|
+
has_more: rest.length > limit
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function parseJsonBody(raw) {
|
|
448
|
+
if (raw.trim() === "") return {
|
|
449
|
+
ok: true,
|
|
450
|
+
value: {}
|
|
451
|
+
};
|
|
452
|
+
try {
|
|
453
|
+
return {
|
|
454
|
+
ok: true,
|
|
455
|
+
value: JSON.parse(raw)
|
|
456
|
+
};
|
|
457
|
+
} catch (err) {
|
|
458
|
+
return {
|
|
459
|
+
ok: false,
|
|
460
|
+
message: `Malformed JSON: ${err instanceof Error ? err.message : "unknown"}`
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function attachFile(store, fileId, chunking, outcome, attributes = null) {
|
|
465
|
+
const bytes = getStoredFileBytes(fileId);
|
|
466
|
+
const entry = {
|
|
467
|
+
id: fileId,
|
|
468
|
+
object: "vector_store.file",
|
|
469
|
+
attributes,
|
|
470
|
+
created_at: stamp(),
|
|
471
|
+
vector_store_id: store.id,
|
|
472
|
+
status: "in_progress",
|
|
473
|
+
usage_bytes: bytes?.length ?? 0,
|
|
474
|
+
last_error: null
|
|
475
|
+
};
|
|
476
|
+
if (chunking) entry.chunking_strategy = chunking.type === "auto" ? {
|
|
477
|
+
type: "static",
|
|
478
|
+
static: {
|
|
479
|
+
max_chunk_size_tokens: 800,
|
|
480
|
+
chunk_overlap_tokens: 400
|
|
481
|
+
}
|
|
482
|
+
} : chunking;
|
|
483
|
+
storeFiles.set(fileKey(store.id, fileId), entry);
|
|
484
|
+
storePolls.set(fileKey(store.id, fileId), 0);
|
|
485
|
+
fileOutcomes.set(fileKey(store.id, fileId), outcome);
|
|
486
|
+
touch(store);
|
|
487
|
+
return entry;
|
|
488
|
+
}
|
|
489
|
+
function advanceFile(entry) {
|
|
490
|
+
if (TERMINAL_FILE_STATUSES.has(entry.status)) return entry;
|
|
491
|
+
const key = fileKey(entry.vector_store_id, entry.id);
|
|
492
|
+
const polls = (storePolls.get(key) ?? 0) + 1;
|
|
493
|
+
storePolls.set(key, polls);
|
|
494
|
+
if (polls >= 2) {
|
|
495
|
+
const outcome = fileOutcomes.get(key) ?? "completed";
|
|
496
|
+
entry.status = outcome;
|
|
497
|
+
if (outcome === "failed") entry.last_error = {
|
|
498
|
+
code: "mock_ingest_failed",
|
|
499
|
+
message: "Mock ingestion failed for this file"
|
|
500
|
+
};
|
|
501
|
+
else entry.last_error = null;
|
|
502
|
+
}
|
|
503
|
+
const store = stores.get(entry.vector_store_id);
|
|
504
|
+
if (store) refreshStoreStatus(store);
|
|
505
|
+
return entry;
|
|
506
|
+
}
|
|
507
|
+
function batchCounts(batchId) {
|
|
508
|
+
const members = batchMembers.get(batchId) ?? [];
|
|
509
|
+
let inProgress = 0;
|
|
510
|
+
let completed = 0;
|
|
511
|
+
let failed = 0;
|
|
512
|
+
let cancelled = 0;
|
|
513
|
+
for (const entry of members) if (entry.status === "in_progress") inProgress += 1;
|
|
514
|
+
else if (entry.status === "completed") completed += 1;
|
|
515
|
+
else if (entry.status === "failed") failed += 1;
|
|
516
|
+
else cancelled += 1;
|
|
517
|
+
return {
|
|
518
|
+
in_progress: inProgress,
|
|
519
|
+
completed,
|
|
520
|
+
failed,
|
|
521
|
+
cancelled,
|
|
522
|
+
total: members.length
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
function advanceBatch(batch) {
|
|
526
|
+
if (TERMINAL_BATCH_STATUSES.has(batch.status)) {
|
|
527
|
+
batch.file_counts = batchCounts(batch.id);
|
|
528
|
+
return batch;
|
|
529
|
+
}
|
|
530
|
+
if (batch.status === "cancelling") {
|
|
531
|
+
batch.status = "cancelled";
|
|
532
|
+
for (const entry of batchMembers.get(batch.id) ?? []) if (entry.status === "in_progress") entry.status = "cancelled";
|
|
533
|
+
batch.file_counts = batchCounts(batch.id);
|
|
534
|
+
const store = stores.get(batch.vector_store_id);
|
|
535
|
+
if (store) refreshStoreStatus(store);
|
|
536
|
+
return batch;
|
|
537
|
+
}
|
|
538
|
+
const polls = (batchPolls.get(batch.id) ?? 0) + 1;
|
|
539
|
+
batchPolls.set(batch.id, polls);
|
|
540
|
+
if (polls === 1) batch.status = "in_progress";
|
|
541
|
+
else {
|
|
542
|
+
const outcome = batchOutcomes.get(batch.id) ?? "completed";
|
|
543
|
+
batch.status = outcome;
|
|
544
|
+
for (const entry of batchMembers.get(batch.id) ?? []) if (entry.status === "in_progress") {
|
|
545
|
+
entry.status = outcome === "completed" ? "completed" : outcome;
|
|
546
|
+
if (outcome === "failed") entry.last_error = {
|
|
547
|
+
code: "mock_ingest_failed",
|
|
548
|
+
message: "Mock ingestion failed for this file"
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
batch.file_counts = batchCounts(batch.id);
|
|
553
|
+
const store = stores.get(batch.vector_store_id);
|
|
554
|
+
if (store) refreshStoreStatus(store);
|
|
555
|
+
return batch;
|
|
556
|
+
}
|
|
557
|
+
function scoreFor(query, fileId) {
|
|
558
|
+
let h = 2166136261;
|
|
559
|
+
const text = `${query}::${fileId}`;
|
|
560
|
+
for (let i = 0; i < text.length; i++) h = Math.imul(h ^ text.charCodeAt(i), 16777619);
|
|
561
|
+
return (h >>> 0) % 1e4 / 1e4;
|
|
562
|
+
}
|
|
563
|
+
async function handleVectorStoresCreate(req, res, raw, journal, defaults, setCorsHeaders) {
|
|
564
|
+
const path = req.url ?? "/v1/vector_stores";
|
|
565
|
+
const method = req.method ?? "POST";
|
|
566
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
567
|
+
const parsed = parseJsonBody(raw);
|
|
568
|
+
if (!parsed.ok) {
|
|
569
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
570
|
+
writeJson(res, 400, invalid(parsed.message), setCorsHeaders);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (!isJsonObject(parsed.value)) {
|
|
574
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
575
|
+
writeJson(res, 400, invalid("Request body must be a JSON object"), setCorsHeaders);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const body = parsed.value;
|
|
579
|
+
if (body["name"] !== void 0 && typeof body["name"] !== "string") {
|
|
580
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
581
|
+
writeJson(res, 400, invalid("Invalid parameter: 'name' must be a string"), setCorsHeaders);
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const metadata = readMetadata(body["metadata"]);
|
|
585
|
+
if (!metadata.ok) {
|
|
586
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
587
|
+
writeJson(res, 400, invalid(metadata.message), setCorsHeaders);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const expires = readExpiresAfter(body["expires_after"]);
|
|
591
|
+
if (!expires.ok) {
|
|
592
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
593
|
+
writeJson(res, 400, invalid(expires.message), setCorsHeaders);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
const chunking = readChunkingStrategy(body["chunking_strategy"]);
|
|
597
|
+
if (!chunking.ok) {
|
|
598
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
599
|
+
writeJson(res, 400, invalid(chunking.message), setCorsHeaders);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const fileIds = readFileIds(body["file_ids"]);
|
|
603
|
+
if (!fileIds.ok) {
|
|
604
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
605
|
+
writeJson(res, 400, invalid(fileIds.message), setCorsHeaders);
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
for (const fileId of fileIds.value) if (getStoredFileBytes(fileId) === void 0) {
|
|
609
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
610
|
+
writeJson(res, 404, invalid(`No such file: ${fileId}`), setCorsHeaders);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const outcome = readOutcome(req);
|
|
614
|
+
if (!outcome.ok) {
|
|
615
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
616
|
+
writeJson(res, 400, invalid(outcome.message), setCorsHeaders);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
const now = stamp();
|
|
620
|
+
const id = underscoreId("vs");
|
|
621
|
+
const store = {
|
|
622
|
+
id,
|
|
623
|
+
object: "vector_store",
|
|
624
|
+
created_at: now,
|
|
625
|
+
name: typeof body["name"] === "string" ? body["name"] : `vector-store-${id.slice(3, 9)}`,
|
|
626
|
+
usage_bytes: 0,
|
|
627
|
+
file_counts: {
|
|
628
|
+
in_progress: 0,
|
|
629
|
+
completed: 0,
|
|
630
|
+
failed: 0,
|
|
631
|
+
cancelled: 0,
|
|
632
|
+
total: 0
|
|
633
|
+
},
|
|
634
|
+
status: "completed",
|
|
635
|
+
metadata: metadata.value,
|
|
636
|
+
last_active_at: now,
|
|
637
|
+
expires_at: null
|
|
638
|
+
};
|
|
639
|
+
if (expires.value) {
|
|
640
|
+
store.expires_after = expires.value;
|
|
641
|
+
store.expires_at = now + expires.value.days * 86400;
|
|
642
|
+
}
|
|
643
|
+
if (chunking.value) store.chunking_strategy = chunking.value;
|
|
644
|
+
stores.set(id, store);
|
|
645
|
+
for (const fileId of fileIds.value) attachFile(store, fileId, chunking.value ?? void 0, outcome.value);
|
|
646
|
+
refreshStoreStatus(store);
|
|
647
|
+
defaults.logger.debug(`Vector stores mock: created ${id}`);
|
|
648
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
649
|
+
writeJson(res, 200, store, setCorsHeaders);
|
|
650
|
+
}
|
|
651
|
+
async function handleVectorStoresList(req, res, journal, defaults, setCorsHeaders) {
|
|
652
|
+
const path = req.url ?? "/v1/vector_stores";
|
|
653
|
+
const method = req.method ?? "GET";
|
|
654
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
655
|
+
for (const store of stores.values()) refreshStoreStatus(store);
|
|
656
|
+
const result = paginateOrdered([...stores.values()].reverse(), req.url);
|
|
657
|
+
if (!result.ok || !result.page) {
|
|
658
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
659
|
+
writeJson(res, 400, invalid(result.message ?? "Invalid pagination"), setCorsHeaders);
|
|
660
|
+
return;
|
|
661
|
+
}
|
|
662
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
663
|
+
writeJson(res, 200, result.page, setCorsHeaders);
|
|
664
|
+
}
|
|
665
|
+
async function handleVectorStoresRetrieve(req, res, storeId, journal, defaults, setCorsHeaders) {
|
|
666
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}`;
|
|
667
|
+
const method = req.method ?? "GET";
|
|
668
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
669
|
+
const store = stores.get(storeId);
|
|
670
|
+
if (!store) {
|
|
671
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
672
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
touch(store);
|
|
676
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
677
|
+
writeJson(res, 200, store, setCorsHeaders);
|
|
678
|
+
}
|
|
679
|
+
async function handleVectorStoresModify(req, res, storeId, raw, journal, defaults, setCorsHeaders) {
|
|
680
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}`;
|
|
681
|
+
const method = req.method ?? "POST";
|
|
682
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
683
|
+
const store = stores.get(storeId);
|
|
684
|
+
if (!store) {
|
|
685
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
686
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const parsed = parseJsonBody(raw);
|
|
690
|
+
if (!parsed.ok) {
|
|
691
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
692
|
+
writeJson(res, 400, invalid(parsed.message), setCorsHeaders);
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
if (!isJsonObject(parsed.value)) {
|
|
696
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
697
|
+
writeJson(res, 400, invalid("Request body must be a JSON object"), setCorsHeaders);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const body = parsed.value;
|
|
701
|
+
let name = store.name;
|
|
702
|
+
let metadata = store.metadata;
|
|
703
|
+
let expiresAfter = store.expires_after;
|
|
704
|
+
if (body["name"] !== void 0) {
|
|
705
|
+
if (body["name"] !== null && typeof body["name"] !== "string") {
|
|
706
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
707
|
+
writeJson(res, 400, invalid("Invalid parameter: 'name' must be a string or null"), setCorsHeaders);
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
name = body["name"] ?? "";
|
|
711
|
+
}
|
|
712
|
+
if (body["metadata"] !== void 0) {
|
|
713
|
+
const parsedMetadata = readMetadata(body["metadata"]);
|
|
714
|
+
if (!parsedMetadata.ok) {
|
|
715
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
716
|
+
writeJson(res, 400, invalid(parsedMetadata.message), setCorsHeaders);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
metadata = parsedMetadata.value;
|
|
720
|
+
}
|
|
721
|
+
if (body["expires_after"] !== void 0) if (body["expires_after"] === null) expiresAfter = void 0;
|
|
722
|
+
else {
|
|
723
|
+
const expires = readExpiresAfter(body["expires_after"]);
|
|
724
|
+
if (!expires.ok) {
|
|
725
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
726
|
+
writeJson(res, 400, invalid(expires.message), setCorsHeaders);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (expires.value) expiresAfter = expires.value;
|
|
730
|
+
}
|
|
731
|
+
store.name = name;
|
|
732
|
+
store.metadata = metadata;
|
|
733
|
+
if (body["expires_after"] !== void 0) if (expiresAfter) {
|
|
734
|
+
store.expires_after = expiresAfter;
|
|
735
|
+
store.expires_at = stamp() + expiresAfter.days * 86400;
|
|
736
|
+
} else {
|
|
737
|
+
delete store.expires_after;
|
|
738
|
+
store.expires_at = null;
|
|
739
|
+
}
|
|
740
|
+
touch(store);
|
|
741
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
742
|
+
writeJson(res, 200, store, setCorsHeaders);
|
|
743
|
+
}
|
|
744
|
+
async function handleVectorStoresDelete(req, res, storeId, journal, defaults, setCorsHeaders) {
|
|
745
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}`;
|
|
746
|
+
const method = req.method ?? "DELETE";
|
|
747
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
748
|
+
if (!stores.get(storeId)) {
|
|
749
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
750
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
stores.delete(storeId);
|
|
754
|
+
for (const key of [...storeFiles.keys()]) if (key.startsWith(`${storeId}:`)) {
|
|
755
|
+
storeFiles.delete(key);
|
|
756
|
+
storePolls.delete(key);
|
|
757
|
+
fileOutcomes.delete(key);
|
|
758
|
+
}
|
|
759
|
+
for (const [batchId, batch] of [...fileBatches.entries()]) if (batch.vector_store_id === storeId) {
|
|
760
|
+
fileBatches.delete(batchId);
|
|
761
|
+
batchPolls.delete(batchId);
|
|
762
|
+
batchOutcomes.delete(batchId);
|
|
763
|
+
batchMembers.delete(batchId);
|
|
764
|
+
}
|
|
765
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
766
|
+
writeJson(res, 200, {
|
|
767
|
+
id: storeId,
|
|
768
|
+
object: "vector_store.deleted",
|
|
769
|
+
deleted: true
|
|
770
|
+
}, setCorsHeaders);
|
|
771
|
+
}
|
|
772
|
+
async function handleVectorStoreFilesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders) {
|
|
773
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/files`;
|
|
774
|
+
const method = req.method ?? "POST";
|
|
775
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
776
|
+
const store = stores.get(storeId);
|
|
777
|
+
if (!store) {
|
|
778
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
779
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const parsed = parseJsonBody(raw);
|
|
783
|
+
if (!parsed.ok) {
|
|
784
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
785
|
+
writeJson(res, 400, invalid(parsed.message), setCorsHeaders);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
if (!isJsonObject(parsed.value)) {
|
|
789
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
790
|
+
writeJson(res, 400, invalid("Request body must be a JSON object"), setCorsHeaders);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const fileId = parsed.value["file_id"];
|
|
794
|
+
if (typeof fileId !== "string" || fileId.length === 0) {
|
|
795
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
796
|
+
writeJson(res, 400, invalid("Invalid parameter: 'file_id' must be a non-empty string"), setCorsHeaders);
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
if (getStoredFileBytes(fileId) === void 0) {
|
|
800
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
801
|
+
writeJson(res, 404, invalid(`No such file: ${fileId}`), setCorsHeaders);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
if (storeFiles.has(fileKey(storeId, fileId))) {
|
|
805
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
806
|
+
writeJson(res, 400, invalid(`File ${fileId} is already attached to vector store ${storeId}`), setCorsHeaders);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const chunking = readChunkingStrategy(parsed.value["chunking_strategy"]);
|
|
810
|
+
if (!chunking.ok) {
|
|
811
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
812
|
+
writeJson(res, 400, invalid(chunking.message), setCorsHeaders);
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const attributes = readAttributes(parsed.value["attributes"]);
|
|
816
|
+
if (!attributes.ok) {
|
|
817
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
818
|
+
writeJson(res, 400, invalid(attributes.message), setCorsHeaders);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const outcome = readOutcome(req);
|
|
822
|
+
if (!outcome.ok) {
|
|
823
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
824
|
+
writeJson(res, 400, invalid(outcome.message), setCorsHeaders);
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const entry = attachFile(store, fileId, chunking.value ?? void 0, outcome.value, attributes.value);
|
|
828
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
829
|
+
writeJson(res, 200, entry, setCorsHeaders);
|
|
830
|
+
}
|
|
831
|
+
async function handleVectorStoreFilesList(req, res, storeId, journal, defaults, setCorsHeaders) {
|
|
832
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/files`;
|
|
833
|
+
const method = req.method ?? "GET";
|
|
834
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
835
|
+
if (!stores.get(storeId)) {
|
|
836
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
837
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const readFilter = singleParam(queryParams(req.url), "filter");
|
|
841
|
+
if (!readFilter.ok) {
|
|
842
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
843
|
+
writeJson(res, 400, invalid(readFilter.message), setCorsHeaders);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
if (readFilter.value !== null && !TERMINAL_FILE_STATUSES.has(readFilter.value) && readFilter.value !== "in_progress") {
|
|
847
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
848
|
+
writeJson(res, 400, invalid("Invalid parameter: 'filter' must be 'in_progress', 'completed', 'failed' or 'cancelled'"), setCorsHeaders);
|
|
849
|
+
return;
|
|
850
|
+
}
|
|
851
|
+
let data = [...storeFiles.values()].filter((f) => f.vector_store_id === storeId).sort((a, b) => b.created_at - a.created_at || (a.id < b.id ? 1 : -1));
|
|
852
|
+
if (readFilter.value !== null) data = data.filter((f) => f.status === readFilter.value);
|
|
853
|
+
const result = paginateOrdered(data, req.url);
|
|
854
|
+
if (!result.ok || !result.page) {
|
|
855
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
856
|
+
writeJson(res, 400, invalid(result.message ?? "Invalid pagination"), setCorsHeaders);
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
860
|
+
writeJson(res, 200, result.page, setCorsHeaders);
|
|
861
|
+
}
|
|
862
|
+
async function handleVectorStoreFilesRetrieve(req, res, storeId, fileId, journal, defaults, setCorsHeaders) {
|
|
863
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/files/${fileId}`;
|
|
864
|
+
const method = req.method ?? "GET";
|
|
865
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
866
|
+
if (!stores.has(storeId)) {
|
|
867
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
868
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
const entry = storeFiles.get(fileKey(storeId, fileId));
|
|
872
|
+
if (!entry) {
|
|
873
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
874
|
+
writeJson(res, 404, invalid(`No such vector store file: ${fileId}`), setCorsHeaders);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
878
|
+
writeJson(res, 200, advanceFile(entry), setCorsHeaders);
|
|
879
|
+
}
|
|
880
|
+
async function handleVectorStoreFilesDelete(req, res, storeId, fileId, journal, defaults, setCorsHeaders) {
|
|
881
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/files/${fileId}`;
|
|
882
|
+
const method = req.method ?? "DELETE";
|
|
883
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
884
|
+
const store = stores.get(storeId);
|
|
885
|
+
if (!store) {
|
|
886
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
887
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
const key = fileKey(storeId, fileId);
|
|
891
|
+
if (!storeFiles.has(key)) {
|
|
892
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
893
|
+
writeJson(res, 404, invalid(`No such vector store file: ${fileId}`), setCorsHeaders);
|
|
894
|
+
return;
|
|
895
|
+
}
|
|
896
|
+
storeFiles.delete(key);
|
|
897
|
+
storePolls.delete(key);
|
|
898
|
+
fileOutcomes.delete(key);
|
|
899
|
+
touch(store);
|
|
900
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
901
|
+
writeJson(res, 200, {
|
|
902
|
+
id: fileId,
|
|
903
|
+
object: "vector_store.file.deleted",
|
|
904
|
+
deleted: true
|
|
905
|
+
}, setCorsHeaders);
|
|
906
|
+
}
|
|
907
|
+
async function handleVectorFileBatchesCreate(req, res, storeId, raw, journal, defaults, setCorsHeaders) {
|
|
908
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/file_batches`;
|
|
909
|
+
const method = req.method ?? "POST";
|
|
910
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
911
|
+
const store = stores.get(storeId);
|
|
912
|
+
if (!store) {
|
|
913
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
914
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
const parsed = parseJsonBody(raw);
|
|
918
|
+
if (!parsed.ok) {
|
|
919
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
920
|
+
writeJson(res, 400, invalid(parsed.message), setCorsHeaders);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
923
|
+
if (!isJsonObject(parsed.value)) {
|
|
924
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
925
|
+
writeJson(res, 400, invalid("Request body must be a JSON object"), setCorsHeaders);
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
const fileIds = readFileIds(parsed.value["file_ids"]);
|
|
929
|
+
if (!fileIds.ok) {
|
|
930
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
931
|
+
writeJson(res, 400, invalid(fileIds.message), setCorsHeaders);
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
if (fileIds.value.length === 0) {
|
|
935
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
936
|
+
writeJson(res, 400, invalid("Invalid parameter: 'file_ids' must contain at least one file"), setCorsHeaders);
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
if (new Set(fileIds.value).size !== fileIds.value.length) {
|
|
940
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
941
|
+
writeJson(res, 400, invalid("Invalid parameter: 'file_ids' must not contain duplicate file IDs"), setCorsHeaders);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
const chunking = readChunkingStrategy(parsed.value["chunking_strategy"]);
|
|
945
|
+
if (!chunking.ok) {
|
|
946
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
947
|
+
writeJson(res, 400, invalid(chunking.message), setCorsHeaders);
|
|
948
|
+
return;
|
|
949
|
+
}
|
|
950
|
+
for (const fileId of fileIds.value) if (getStoredFileBytes(fileId) === void 0) {
|
|
951
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
952
|
+
writeJson(res, 404, invalid(`No such file: ${fileId}`), setCorsHeaders);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const attributes = readAttributes(parsed.value["attributes"]);
|
|
956
|
+
if (!attributes.ok) {
|
|
957
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
958
|
+
writeJson(res, 400, invalid(attributes.message), setCorsHeaders);
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
const outcome = readOutcome(req);
|
|
962
|
+
if (!outcome.ok) {
|
|
963
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
964
|
+
writeJson(res, 400, invalid(outcome.message), setCorsHeaders);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const id = underscoreId("vsfb");
|
|
968
|
+
const batch = {
|
|
969
|
+
id,
|
|
970
|
+
object: "vector_store.file_batch",
|
|
971
|
+
created_at: stamp(),
|
|
972
|
+
vector_store_id: storeId,
|
|
973
|
+
status: "in_progress",
|
|
974
|
+
file_counts: {
|
|
975
|
+
in_progress: fileIds.value.length,
|
|
976
|
+
completed: 0,
|
|
977
|
+
failed: 0,
|
|
978
|
+
cancelled: 0,
|
|
979
|
+
total: fileIds.value.length
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
fileBatches.set(id, batch);
|
|
983
|
+
batchPolls.set(id, 0);
|
|
984
|
+
batchOutcomes.set(id, outcome.value);
|
|
985
|
+
const members = [];
|
|
986
|
+
for (const fileId of fileIds.value) {
|
|
987
|
+
const entry = storeFiles.get(fileKey(storeId, fileId)) ?? attachFile(store, fileId, chunking.value ?? void 0, outcome.value, attributes.value);
|
|
988
|
+
members.push(entry);
|
|
989
|
+
}
|
|
990
|
+
batchMembers.set(id, members);
|
|
991
|
+
batch.file_counts = batchCounts(id);
|
|
992
|
+
refreshStoreStatus(store);
|
|
993
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
994
|
+
writeJson(res, 200, batch, setCorsHeaders);
|
|
995
|
+
}
|
|
996
|
+
async function handleVectorFileBatchesRetrieve(req, res, storeId, batchId, journal, defaults, setCorsHeaders) {
|
|
997
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/file_batches/${batchId}`;
|
|
998
|
+
const method = req.method ?? "GET";
|
|
999
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
1000
|
+
if (!stores.has(storeId)) {
|
|
1001
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
1002
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
1003
|
+
return;
|
|
1004
|
+
}
|
|
1005
|
+
const batch = fileBatches.get(batchId);
|
|
1006
|
+
if (!batch || batch.vector_store_id !== storeId) {
|
|
1007
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
1008
|
+
writeJson(res, 404, invalid(`No such file batch: ${batchId}`), setCorsHeaders);
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
1012
|
+
writeJson(res, 200, advanceBatch(batch), setCorsHeaders);
|
|
1013
|
+
}
|
|
1014
|
+
async function handleVectorFileBatchesCancel(req, res, storeId, batchId, journal, defaults, setCorsHeaders) {
|
|
1015
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/file_batches/${batchId}/cancel`;
|
|
1016
|
+
const method = req.method ?? "POST";
|
|
1017
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
1018
|
+
if (!stores.has(storeId)) {
|
|
1019
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
1020
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const batch = fileBatches.get(batchId);
|
|
1024
|
+
if (!batch || batch.vector_store_id !== storeId) {
|
|
1025
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
1026
|
+
writeJson(res, 404, invalid(`No such file batch: ${batchId}`), setCorsHeaders);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (TERMINAL_BATCH_STATUSES.has(batch.status)) {
|
|
1030
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1031
|
+
writeJson(res, 400, invalid(`Cannot cancel a file batch with status ${batch.status}.`), setCorsHeaders);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
if (batch.status !== "cancelling") batch.status = "cancelling";
|
|
1035
|
+
batch.file_counts = batchCounts(batch.id);
|
|
1036
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
1037
|
+
writeJson(res, 200, batch, setCorsHeaders);
|
|
1038
|
+
}
|
|
1039
|
+
async function handleVectorFileBatchesFiles(req, res, storeId, batchId, journal, defaults, setCorsHeaders) {
|
|
1040
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/file_batches/${batchId}/files`;
|
|
1041
|
+
const method = req.method ?? "GET";
|
|
1042
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
1043
|
+
if (!stores.has(storeId)) {
|
|
1044
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
1045
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
const batch = fileBatches.get(batchId);
|
|
1049
|
+
if (!batch || batch.vector_store_id !== storeId) {
|
|
1050
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
1051
|
+
writeJson(res, 404, invalid(`No such file batch: ${batchId}`), setCorsHeaders);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
const readFilter = singleParam(queryParams(req.url), "filter");
|
|
1055
|
+
if (!readFilter.ok) {
|
|
1056
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1057
|
+
writeJson(res, 400, invalid(readFilter.message), setCorsHeaders);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
if (readFilter.value !== null && !TERMINAL_FILE_STATUSES.has(readFilter.value) && readFilter.value !== "in_progress") {
|
|
1061
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1062
|
+
writeJson(res, 400, invalid("Invalid parameter: 'filter' must be 'in_progress', 'completed', 'failed' or 'cancelled'"), setCorsHeaders);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
let data = [...batchMembers.get(batchId) ?? []].sort((a, b) => b.created_at - a.created_at || (a.id < b.id ? 1 : -1));
|
|
1066
|
+
if (readFilter.value !== null) data = data.filter((f) => f.status === readFilter.value);
|
|
1067
|
+
const result = paginateOrdered(data, req.url);
|
|
1068
|
+
if (!result.ok || !result.page) {
|
|
1069
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1070
|
+
writeJson(res, 400, invalid(result.message ?? "Invalid pagination"), setCorsHeaders);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
1074
|
+
writeJson(res, 200, result.page, setCorsHeaders);
|
|
1075
|
+
}
|
|
1076
|
+
async function handleVectorStoresSearch(req, res, storeId, raw, journal, defaults, setCorsHeaders) {
|
|
1077
|
+
const path = req.url ?? `/v1/vector_stores/${storeId}/search`;
|
|
1078
|
+
const method = req.method ?? "POST";
|
|
1079
|
+
if (await chaosHit(req, journal, defaults, method, path, res, setCorsHeaders)) return;
|
|
1080
|
+
const store = stores.get(storeId);
|
|
1081
|
+
if (!store) {
|
|
1082
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 404);
|
|
1083
|
+
writeJson(res, 404, invalid(`No such vector store: ${storeId}`), setCorsHeaders);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
const parsed = parseJsonBody(raw);
|
|
1087
|
+
if (!parsed.ok) {
|
|
1088
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1089
|
+
writeJson(res, 400, invalid(parsed.message), setCorsHeaders);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
if (!isJsonObject(parsed.value)) {
|
|
1093
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1094
|
+
writeJson(res, 400, invalid("Request body must be a JSON object"), setCorsHeaders);
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1097
|
+
const body = parsed.value;
|
|
1098
|
+
const query = body["query"];
|
|
1099
|
+
const queries = typeof query === "string" ? [query] : query;
|
|
1100
|
+
if (!Array.isArray(queries) || queries.length === 0 || !queries.every((entry) => typeof entry === "string" && entry.length > 0)) {
|
|
1101
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1102
|
+
writeJson(res, 400, invalid("Invalid parameter: 'query' must be a non-empty string or a non-empty array of non-empty strings"), setCorsHeaders);
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
const normalizedQuery = queries.join("\n");
|
|
1106
|
+
let maxResults = 10;
|
|
1107
|
+
if (body["max_num_results"] !== void 0) {
|
|
1108
|
+
const n = body["max_num_results"];
|
|
1109
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 50) {
|
|
1110
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1111
|
+
writeJson(res, 400, invalid("Invalid parameter: 'max_num_results' must be an integer between 1 and 50"), setCorsHeaders);
|
|
1112
|
+
return;
|
|
1113
|
+
}
|
|
1114
|
+
maxResults = n;
|
|
1115
|
+
}
|
|
1116
|
+
let threshold = 0;
|
|
1117
|
+
if (body["ranking_options"] !== void 0) {
|
|
1118
|
+
if (!isJsonObject(body["ranking_options"])) {
|
|
1119
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1120
|
+
writeJson(res, 400, invalid("Invalid parameter: 'ranking_options' must be an object"), setCorsHeaders);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1123
|
+
const score = body["ranking_options"]["score_threshold"];
|
|
1124
|
+
if (score !== void 0) {
|
|
1125
|
+
if (typeof score !== "number" || score < 0 || score > 1) {
|
|
1126
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1127
|
+
writeJson(res, 400, invalid("Invalid parameter: 'ranking_options.score_threshold' must be a number between 0 and 1"), setCorsHeaders);
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
threshold = score;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
const filters = readAttributeFilter(body["filters"]);
|
|
1134
|
+
if (!filters.ok) {
|
|
1135
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 400);
|
|
1136
|
+
writeJson(res, 400, invalid(filters.message), setCorsHeaders);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const ranked = [...storeFiles.values()].filter((f) => f.vector_store_id === storeId && f.status === "completed" && (filters.value === null || matchesAttributeFilter(f.attributes, filters.value))).map((f) => ({
|
|
1140
|
+
file: f,
|
|
1141
|
+
score: scoreFor(normalizedQuery, f.id)
|
|
1142
|
+
})).filter((entry) => entry.score >= threshold).sort((a, b) => b.score - a.score).slice(0, maxResults).map((entry) => ({
|
|
1143
|
+
file_id: entry.file.id,
|
|
1144
|
+
attributes: entry.file.attributes,
|
|
1145
|
+
filename: getStoredFileName(entry.file.id) ?? `file-${entry.file.id.slice(0, 8)}.txt`,
|
|
1146
|
+
score: Math.round(entry.score * 1e4) / 1e4,
|
|
1147
|
+
content: [{
|
|
1148
|
+
type: "text",
|
|
1149
|
+
text: `Mock chunk from ${entry.file.id} answering "${normalizedQuery.slice(0, 120)}"`
|
|
1150
|
+
}]
|
|
1151
|
+
}));
|
|
1152
|
+
touch(store);
|
|
1153
|
+
journalVs(journal, method, path, flattenHeaders(req.headers), 200);
|
|
1154
|
+
writeJson(res, 200, {
|
|
1155
|
+
object: "vector_store.search_results_page",
|
|
1156
|
+
search_query: query,
|
|
1157
|
+
data: ranked,
|
|
1158
|
+
has_more: false
|
|
1159
|
+
}, setCorsHeaders);
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
//#endregion
|
|
1163
|
+
export { clearVectorStoreStore, handleVectorFileBatchesCancel, handleVectorFileBatchesCreate, handleVectorFileBatchesFiles, handleVectorFileBatchesRetrieve, handleVectorStoreFilesCreate, handleVectorStoreFilesDelete, handleVectorStoreFilesList, handleVectorStoreFilesRetrieve, handleVectorStoresCreate, handleVectorStoresDelete, handleVectorStoresList, handleVectorStoresModify, handleVectorStoresRetrieve, handleVectorStoresSearch, removeVectorStoreFile };
|
|
1164
|
+
//# sourceMappingURL=vector-stores.js.map
|