@getjack/jack 0.1.23 → 0.1.25
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/package.json +1 -1
- package/src/commands/services.ts +156 -36
- package/src/lib/control-plane.ts +86 -0
- package/src/lib/services/db-execute.ts +100 -1
- package/src/lib/services/db-list.ts +34 -7
- package/src/lib/services/vectorize-config.ts +569 -0
- package/src/lib/services/vectorize-create.ts +166 -0
- package/src/lib/services/vectorize-delete.ts +54 -0
- package/src/lib/services/vectorize-info.ts +52 -0
- package/src/lib/services/vectorize-list.ts +56 -0
- package/src/lib/version-check.ts +2 -2
- package/src/mcp/test-utils.ts +47 -2
- package/src/mcp/tools/index.ts +282 -0
- package/templates/AI-BINDINGS.md +181 -0
- package/templates/CLAUDE.md +30 -0
- package/templates/ai-chat/.jack.json +3 -4
- package/templates/ai-chat/src/index.ts +45 -5
- package/templates/ai-chat/src/jack-ai.ts +96 -0
- package/templates/semantic-search/.jack.json +3 -4
- package/templates/semantic-search/src/index.ts +70 -12
- package/templates/semantic-search/src/jack-ai.ts +96 -0
- package/templates/semantic-search/src/jack-vectorize.ts +165 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jack AI Client - Drop-in replacement for Cloudflare AI binding.
|
|
3
|
+
*
|
|
4
|
+
* This wrapper provides the same interface as env.AI but routes calls
|
|
5
|
+
* through jack's binding proxy for metering and quota enforcement.
|
|
6
|
+
*
|
|
7
|
+
* Usage in templates:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createJackAI } from "./jack-ai";
|
|
10
|
+
*
|
|
11
|
+
* interface Env {
|
|
12
|
+
* __AI_PROXY: Fetcher; // Service binding to binding-proxy worker
|
|
13
|
+
* __JACK_PROJECT_ID: string; // Injected by control plane
|
|
14
|
+
* __JACK_ORG_ID: string; // Injected by control plane
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* export default {
|
|
18
|
+
* async fetch(request: Request, env: Env) {
|
|
19
|
+
* const AI = createJackAI(env);
|
|
20
|
+
* const result = await AI.run("@cf/meta/llama-3.2-1b-instruct", { messages });
|
|
21
|
+
* // Works exactly like env.AI.run()
|
|
22
|
+
* }
|
|
23
|
+
* };
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The wrapper is transparent - it accepts the same parameters as env.AI.run()
|
|
27
|
+
* and returns the same response types, including streaming.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface JackAIEnv {
|
|
31
|
+
__AI_PROXY: Fetcher;
|
|
32
|
+
__JACK_PROJECT_ID: string;
|
|
33
|
+
__JACK_ORG_ID: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a Jack AI client that mirrors the Cloudflare AI binding interface.
|
|
38
|
+
*
|
|
39
|
+
* @param env - Worker environment with jack proxy bindings
|
|
40
|
+
* @returns AI-compatible object with run() method
|
|
41
|
+
*/
|
|
42
|
+
export function createJackAI(env: JackAIEnv): {
|
|
43
|
+
run: <T = unknown>(
|
|
44
|
+
model: string,
|
|
45
|
+
inputs: unknown,
|
|
46
|
+
options?: unknown,
|
|
47
|
+
) => Promise<T | ReadableStream>;
|
|
48
|
+
} {
|
|
49
|
+
return {
|
|
50
|
+
async run<T = unknown>(
|
|
51
|
+
model: string,
|
|
52
|
+
inputs: unknown,
|
|
53
|
+
options?: unknown,
|
|
54
|
+
): Promise<T | ReadableStream> {
|
|
55
|
+
const response = await env.__AI_PROXY.fetch("http://internal/ai/run", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
"X-Jack-Project-ID": env.__JACK_PROJECT_ID,
|
|
60
|
+
"X-Jack-Org-ID": env.__JACK_ORG_ID,
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify({ model, inputs, options }),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Handle quota exceeded
|
|
66
|
+
if (response.status === 429) {
|
|
67
|
+
const error = await response.json();
|
|
68
|
+
const quotaError = new Error((error as { message?: string }).message || "AI quota exceeded");
|
|
69
|
+
(quotaError as Error & { code: string }).code = "AI_QUOTA_EXCEEDED";
|
|
70
|
+
(quotaError as Error & { resetIn?: number }).resetIn = (error as { resetIn?: number }).resetIn;
|
|
71
|
+
throw quotaError;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Handle other errors
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const error = await response.json();
|
|
77
|
+
throw new Error((error as { error?: string }).error || "AI request failed");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle streaming response
|
|
81
|
+
const contentType = response.headers.get("Content-Type");
|
|
82
|
+
if (contentType?.includes("text/event-stream")) {
|
|
83
|
+
return response.body as ReadableStream;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle JSON response
|
|
87
|
+
return response.json() as Promise<T>;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Type-safe wrapper that infers return types based on model.
|
|
94
|
+
* For advanced users who want full type safety.
|
|
95
|
+
*/
|
|
96
|
+
export type JackAI = ReturnType<typeof createJackAI>;
|
|
@@ -7,18 +7,17 @@
|
|
|
7
7
|
"keywords": ["rag", "semantic", "search", "embeddings", "vectorize", "vector", "similarity"],
|
|
8
8
|
"examples": ["RAG app", "document search", "semantic search", "knowledge base"]
|
|
9
9
|
},
|
|
10
|
+
"agentContext": "Local development requires 'wrangler dev --remote' for AI and Vectorize bindings.",
|
|
10
11
|
"hooks": {
|
|
11
12
|
"postDeploy": [
|
|
12
13
|
{ "action": "clipboard", "text": "{{url}}", "message": "URL copied" },
|
|
13
14
|
{
|
|
14
15
|
"action": "box",
|
|
15
|
-
"title": "
|
|
16
|
+
"title": "{{name}}",
|
|
16
17
|
"lines": [
|
|
17
18
|
"{{url}}",
|
|
18
19
|
"",
|
|
19
|
-
"
|
|
20
|
-
"",
|
|
21
|
-
"Note: Local dev requires 'wrangler dev --remote'"
|
|
20
|
+
"jack open to view in browser"
|
|
22
21
|
]
|
|
23
22
|
}
|
|
24
23
|
]
|
|
@@ -1,10 +1,64 @@
|
|
|
1
|
+
import { createJackAI, type JackAI } from "./jack-ai";
|
|
2
|
+
import { createJackVectorize, type JackVectorize } from "./jack-vectorize";
|
|
3
|
+
|
|
1
4
|
interface Env {
|
|
2
|
-
|
|
3
|
-
|
|
5
|
+
// Direct bindings (for local dev with wrangler)
|
|
6
|
+
AI?: Ai;
|
|
7
|
+
VECTORS?: VectorizeIndex;
|
|
8
|
+
// Jack proxy bindings (injected in jack cloud)
|
|
9
|
+
__AI_PROXY?: Fetcher;
|
|
10
|
+
__VECTORIZE_PROXY?: Fetcher;
|
|
11
|
+
__JACK_PROJECT_ID?: string;
|
|
12
|
+
__JACK_ORG_ID?: string;
|
|
13
|
+
// Other bindings
|
|
4
14
|
DB: D1Database;
|
|
5
15
|
ASSETS: Fetcher;
|
|
6
16
|
}
|
|
7
17
|
|
|
18
|
+
// Index name must match wrangler.jsonc vectorize config
|
|
19
|
+
const VECTORIZE_INDEX_NAME = "jack-template-vectors";
|
|
20
|
+
|
|
21
|
+
// Minimal AI interface for embedding generation
|
|
22
|
+
type AIClient = {
|
|
23
|
+
run: (model: string, inputs: { text: string }) => Promise<{ data: number[][] } | unknown>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
function getAI(env: Env): AIClient {
|
|
27
|
+
// Prefer jack cloud proxy if available (for metering)
|
|
28
|
+
if (env.__AI_PROXY && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
|
|
29
|
+
return createJackAI(env as Required<Pick<Env, "__AI_PROXY" | "__JACK_PROJECT_ID" | "__JACK_ORG_ID">>) as AIClient;
|
|
30
|
+
}
|
|
31
|
+
// Fallback to direct binding for local dev
|
|
32
|
+
if (env.AI) {
|
|
33
|
+
return env.AI as unknown as AIClient;
|
|
34
|
+
}
|
|
35
|
+
throw new Error("No AI binding available");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Minimal Vectorize interface
|
|
39
|
+
type VectorizeClient = {
|
|
40
|
+
insert: (vectors: { id: string; values: number[]; metadata?: Record<string, unknown> }[]) => Promise<unknown>;
|
|
41
|
+
query: (
|
|
42
|
+
vector: number[],
|
|
43
|
+
options?: { topK?: number; returnMetadata?: "none" | "indexed" | "all" },
|
|
44
|
+
) => Promise<{ matches: { id: string; score: number; metadata?: Record<string, unknown> }[] }>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function getVectorize(env: Env): VectorizeClient {
|
|
48
|
+
// Prefer jack cloud proxy if available (for metering)
|
|
49
|
+
if (env.__VECTORIZE_PROXY && env.__JACK_PROJECT_ID && env.__JACK_ORG_ID) {
|
|
50
|
+
return createJackVectorize(
|
|
51
|
+
env as Required<Pick<Env, "__VECTORIZE_PROXY" | "__JACK_PROJECT_ID" | "__JACK_ORG_ID">>,
|
|
52
|
+
VECTORIZE_INDEX_NAME,
|
|
53
|
+
) as VectorizeClient;
|
|
54
|
+
}
|
|
55
|
+
// Fallback to direct binding for local dev
|
|
56
|
+
if (env.VECTORS) {
|
|
57
|
+
return env.VECTORS as unknown as VectorizeClient;
|
|
58
|
+
}
|
|
59
|
+
throw new Error("No Vectorize binding available");
|
|
60
|
+
}
|
|
61
|
+
|
|
8
62
|
// Rate limiting: 10 requests per minute per IP
|
|
9
63
|
const RATE_LIMIT = 10;
|
|
10
64
|
const RATE_WINDOW_MS = 60_000;
|
|
@@ -29,17 +83,17 @@ function checkRateLimit(ip: string): boolean {
|
|
|
29
83
|
|
|
30
84
|
/**
|
|
31
85
|
* Extract embedding vector from AI response
|
|
32
|
-
* Handles
|
|
86
|
+
* Handles response from @cf/baai/bge-base-en-v1.5
|
|
33
87
|
*/
|
|
34
|
-
function getEmbeddingVector(response:
|
|
88
|
+
function getEmbeddingVector(response: unknown): number[] | null {
|
|
35
89
|
if (
|
|
36
90
|
response &&
|
|
37
91
|
typeof response === "object" &&
|
|
38
92
|
"data" in response &&
|
|
39
|
-
Array.isArray(response.data) &&
|
|
40
|
-
response.data.length > 0
|
|
93
|
+
Array.isArray((response as { data: unknown }).data) &&
|
|
94
|
+
(response as { data: unknown[] }).data.length > 0
|
|
41
95
|
) {
|
|
42
|
-
return response
|
|
96
|
+
return (response as { data: number[][] }).data[0];
|
|
43
97
|
}
|
|
44
98
|
return null;
|
|
45
99
|
}
|
|
@@ -72,8 +126,9 @@ export default {
|
|
|
72
126
|
return Response.json({ error: "Missing id or content" }, { status: 400 });
|
|
73
127
|
}
|
|
74
128
|
|
|
75
|
-
// Generate embedding using
|
|
76
|
-
const
|
|
129
|
+
// Generate embedding using Cloudflare AI
|
|
130
|
+
const ai = getAI(env);
|
|
131
|
+
const embedding = await ai.run("@cf/baai/bge-base-en-v1.5", {
|
|
77
132
|
text: content,
|
|
78
133
|
});
|
|
79
134
|
|
|
@@ -83,7 +138,8 @@ export default {
|
|
|
83
138
|
}
|
|
84
139
|
|
|
85
140
|
// Store in Vectorize
|
|
86
|
-
|
|
141
|
+
const vectors = getVectorize(env);
|
|
142
|
+
await vectors.insert([
|
|
87
143
|
{
|
|
88
144
|
id,
|
|
89
145
|
values: embeddingVector,
|
|
@@ -117,7 +173,8 @@ export default {
|
|
|
117
173
|
}
|
|
118
174
|
|
|
119
175
|
// Generate query embedding
|
|
120
|
-
const
|
|
176
|
+
const ai = getAI(env);
|
|
177
|
+
const embedding = await ai.run("@cf/baai/bge-base-en-v1.5", {
|
|
121
178
|
text: query,
|
|
122
179
|
});
|
|
123
180
|
|
|
@@ -127,7 +184,8 @@ export default {
|
|
|
127
184
|
}
|
|
128
185
|
|
|
129
186
|
// Search Vectorize
|
|
130
|
-
const
|
|
187
|
+
const vectors = getVectorize(env);
|
|
188
|
+
const results = await vectors.query(embeddingVector, {
|
|
131
189
|
topK: limit,
|
|
132
190
|
returnMetadata: "all",
|
|
133
191
|
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jack AI Client - Drop-in replacement for Cloudflare AI binding.
|
|
3
|
+
*
|
|
4
|
+
* This wrapper provides the same interface as env.AI but routes calls
|
|
5
|
+
* through jack's binding proxy for metering and quota enforcement.
|
|
6
|
+
*
|
|
7
|
+
* Usage in templates:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createJackAI } from "./jack-ai";
|
|
10
|
+
*
|
|
11
|
+
* interface Env {
|
|
12
|
+
* __AI_PROXY: Fetcher; // Service binding to binding-proxy worker
|
|
13
|
+
* __JACK_PROJECT_ID: string; // Injected by control plane
|
|
14
|
+
* __JACK_ORG_ID: string; // Injected by control plane
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* export default {
|
|
18
|
+
* async fetch(request: Request, env: Env) {
|
|
19
|
+
* const AI = createJackAI(env);
|
|
20
|
+
* const result = await AI.run("@cf/meta/llama-3.2-1b-instruct", { messages });
|
|
21
|
+
* // Works exactly like env.AI.run()
|
|
22
|
+
* }
|
|
23
|
+
* };
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The wrapper is transparent - it accepts the same parameters as env.AI.run()
|
|
27
|
+
* and returns the same response types, including streaming.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface JackAIEnv {
|
|
31
|
+
__AI_PROXY: Fetcher;
|
|
32
|
+
__JACK_PROJECT_ID: string;
|
|
33
|
+
__JACK_ORG_ID: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Creates a Jack AI client that mirrors the Cloudflare AI binding interface.
|
|
38
|
+
*
|
|
39
|
+
* @param env - Worker environment with jack proxy bindings
|
|
40
|
+
* @returns AI-compatible object with run() method
|
|
41
|
+
*/
|
|
42
|
+
export function createJackAI(env: JackAIEnv): {
|
|
43
|
+
run: <T = unknown>(
|
|
44
|
+
model: string,
|
|
45
|
+
inputs: unknown,
|
|
46
|
+
options?: unknown,
|
|
47
|
+
) => Promise<T | ReadableStream>;
|
|
48
|
+
} {
|
|
49
|
+
return {
|
|
50
|
+
async run<T = unknown>(
|
|
51
|
+
model: string,
|
|
52
|
+
inputs: unknown,
|
|
53
|
+
options?: unknown,
|
|
54
|
+
): Promise<T | ReadableStream> {
|
|
55
|
+
const response = await env.__AI_PROXY.fetch("http://internal/ai/run", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
"X-Jack-Project-ID": env.__JACK_PROJECT_ID,
|
|
60
|
+
"X-Jack-Org-ID": env.__JACK_ORG_ID,
|
|
61
|
+
},
|
|
62
|
+
body: JSON.stringify({ model, inputs, options }),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Handle quota exceeded
|
|
66
|
+
if (response.status === 429) {
|
|
67
|
+
const error = await response.json();
|
|
68
|
+
const quotaError = new Error((error as { message?: string }).message || "AI quota exceeded");
|
|
69
|
+
(quotaError as Error & { code: string }).code = "AI_QUOTA_EXCEEDED";
|
|
70
|
+
(quotaError as Error & { resetIn?: number }).resetIn = (error as { resetIn?: number }).resetIn;
|
|
71
|
+
throw quotaError;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Handle other errors
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const error = await response.json();
|
|
77
|
+
throw new Error((error as { error?: string }).error || "AI request failed");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Handle streaming response
|
|
81
|
+
const contentType = response.headers.get("Content-Type");
|
|
82
|
+
if (contentType?.includes("text/event-stream")) {
|
|
83
|
+
return response.body as ReadableStream;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Handle JSON response
|
|
87
|
+
return response.json() as Promise<T>;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Type-safe wrapper that infers return types based on model.
|
|
94
|
+
* For advanced users who want full type safety.
|
|
95
|
+
*/
|
|
96
|
+
export type JackAI = ReturnType<typeof createJackAI>;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jack Vectorize Client - Drop-in replacement for Cloudflare Vectorize binding.
|
|
3
|
+
*
|
|
4
|
+
* This wrapper provides the same interface as env.VECTORS but routes calls
|
|
5
|
+
* through jack's binding proxy for metering and quota enforcement.
|
|
6
|
+
*
|
|
7
|
+
* Usage in templates:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { createJackVectorize } from "./jack-vectorize";
|
|
10
|
+
*
|
|
11
|
+
* interface Env {
|
|
12
|
+
* __VECTORIZE_PROXY: Fetcher; // Service binding to binding-proxy worker
|
|
13
|
+
* __JACK_PROJECT_ID: string; // Injected by control plane
|
|
14
|
+
* __JACK_ORG_ID: string; // Injected by control plane
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* export default {
|
|
18
|
+
* async fetch(request: Request, env: Env) {
|
|
19
|
+
* const VECTORS = createJackVectorize(env, "my-index");
|
|
20
|
+
* const results = await VECTORS.query(vector, { topK: 10 });
|
|
21
|
+
* // Works exactly like env.VECTORS.query()
|
|
22
|
+
* }
|
|
23
|
+
* };
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* The wrapper is transparent - it accepts the same parameters as the native
|
|
27
|
+
* VectorizeIndex and returns the same response types.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
interface JackVectorizeEnv {
|
|
31
|
+
__VECTORIZE_PROXY: Fetcher;
|
|
32
|
+
__JACK_PROJECT_ID: string;
|
|
33
|
+
__JACK_ORG_ID: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface VectorizeQueryOptions {
|
|
37
|
+
topK?: number;
|
|
38
|
+
filter?: Record<string, unknown>;
|
|
39
|
+
returnValues?: boolean;
|
|
40
|
+
returnMetadata?: "none" | "indexed" | "all";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface VectorizeVector {
|
|
44
|
+
id: string;
|
|
45
|
+
values: number[];
|
|
46
|
+
metadata?: Record<string, unknown>;
|
|
47
|
+
namespace?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface VectorizeMatch {
|
|
51
|
+
id: string;
|
|
52
|
+
score: number;
|
|
53
|
+
values?: number[];
|
|
54
|
+
metadata?: Record<string, unknown>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface VectorizeQueryResult {
|
|
58
|
+
matches: VectorizeMatch[];
|
|
59
|
+
count: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
interface VectorizeMutationResult {
|
|
63
|
+
mutationId: string;
|
|
64
|
+
count: number;
|
|
65
|
+
ids: string[];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface VectorizeIndexDetails {
|
|
69
|
+
dimensions: number;
|
|
70
|
+
metric: "cosine" | "euclidean" | "dot-product";
|
|
71
|
+
vectorCount: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Creates a Jack Vectorize client that mirrors the Cloudflare VectorizeIndex interface.
|
|
76
|
+
*
|
|
77
|
+
* @param env - Worker environment with jack proxy bindings
|
|
78
|
+
* @param indexName - Name of the Vectorize index
|
|
79
|
+
* @returns VectorizeIndex-compatible object
|
|
80
|
+
*/
|
|
81
|
+
export function createJackVectorize(
|
|
82
|
+
env: JackVectorizeEnv,
|
|
83
|
+
indexName: string,
|
|
84
|
+
): {
|
|
85
|
+
query: (vector: number[], options?: VectorizeQueryOptions) => Promise<VectorizeQueryResult>;
|
|
86
|
+
upsert: (vectors: VectorizeVector[]) => Promise<VectorizeMutationResult>;
|
|
87
|
+
insert: (vectors: VectorizeVector[]) => Promise<VectorizeMutationResult>;
|
|
88
|
+
deleteByIds: (ids: string[]) => Promise<VectorizeMutationResult>;
|
|
89
|
+
getByIds: (ids: string[]) => Promise<VectorizeVector[]>;
|
|
90
|
+
describe: () => Promise<VectorizeIndexDetails>;
|
|
91
|
+
} {
|
|
92
|
+
async function proxyRequest<T>(operation: string, params: unknown): Promise<T> {
|
|
93
|
+
const response = await env.__VECTORIZE_PROXY.fetch("http://internal/vectorize", {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: {
|
|
96
|
+
"Content-Type": "application/json",
|
|
97
|
+
"X-Jack-Project-ID": env.__JACK_PROJECT_ID,
|
|
98
|
+
"X-Jack-Org-ID": env.__JACK_ORG_ID,
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
operation,
|
|
102
|
+
index_name: indexName,
|
|
103
|
+
params,
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Handle quota exceeded
|
|
108
|
+
if (response.status === 429) {
|
|
109
|
+
const error = (await response.json()) as { message?: string; code?: string; resetIn?: number };
|
|
110
|
+
const quotaError = new Error(error.message || "Vectorize quota exceeded");
|
|
111
|
+
(quotaError as Error & { code: string }).code = error.code || "VECTORIZE_QUOTA_EXCEEDED";
|
|
112
|
+
(quotaError as Error & { resetIn?: number }).resetIn = error.resetIn;
|
|
113
|
+
throw quotaError;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle rate limit
|
|
117
|
+
if (response.status === 429) {
|
|
118
|
+
const error = (await response.json()) as { message?: string };
|
|
119
|
+
throw new Error(error.message || "Rate limit exceeded");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Handle other errors
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const error = (await response.json()) as { error?: string };
|
|
125
|
+
throw new Error(error.error || `Vectorize ${operation} failed`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return response.json() as Promise<T>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
async query(vector: number[], options?: VectorizeQueryOptions): Promise<VectorizeQueryResult> {
|
|
133
|
+
return proxyRequest<VectorizeQueryResult>("query", {
|
|
134
|
+
vector,
|
|
135
|
+
...options,
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
async upsert(vectors: VectorizeVector[]): Promise<VectorizeMutationResult> {
|
|
140
|
+
return proxyRequest<VectorizeMutationResult>("upsert", { vectors });
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
async insert(vectors: VectorizeVector[]): Promise<VectorizeMutationResult> {
|
|
144
|
+
// insert is typically an alias for upsert in Vectorize
|
|
145
|
+
return proxyRequest<VectorizeMutationResult>("upsert", { vectors });
|
|
146
|
+
},
|
|
147
|
+
|
|
148
|
+
async deleteByIds(ids: string[]): Promise<VectorizeMutationResult> {
|
|
149
|
+
return proxyRequest<VectorizeMutationResult>("deleteByIds", { ids });
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async getByIds(ids: string[]): Promise<VectorizeVector[]> {
|
|
153
|
+
return proxyRequest<VectorizeVector[]>("getByIds", { ids });
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
async describe(): Promise<VectorizeIndexDetails> {
|
|
157
|
+
return proxyRequest<VectorizeIndexDetails>("describe", {});
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Type alias for the Jack Vectorize client
|
|
164
|
+
*/
|
|
165
|
+
export type JackVectorize = ReturnType<typeof createJackVectorize>;
|