Skip to content

Commit 64aed1c

Browse files
committed
feat(provider): add Qdrant Cloud REST provider
1 parent 28c2078 commit 64aed1c

5 files changed

Lines changed: 864 additions & 0 deletions

File tree

src/providers/qdrant/actions.ts

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import type { ProviderActionDefinition } from "../../core/provider-definition.ts";
2+
3+
import { s } from "../../core/json-schema.ts";
4+
import { defineProviderAction } from "../../core/provider-definition.ts";
5+
6+
const service = "qdrant";
7+
8+
const collectionNameSchema = s.nonEmptyString("The Qdrant collection name.");
9+
const pointIdSchema = s.union([s.nonNegativeInteger("A non-negative numeric point ID."), s.uuid("A UUID point ID.")]);
10+
const vectorSchema = s.array("A dense unnamed vector.", s.number("One vector component."), { minItems: 1 });
11+
const payloadSchema = s.looseObject("A JSON object stored with the point.");
12+
const filterSchema = s.looseObject("A Qdrant filter. Nested conditions are validated by Qdrant.", {
13+
must: s.unknown("Conditions that must match."),
14+
must_not: s.unknown("Conditions that must not match."),
15+
should: s.unknown("Conditions where at least one should match."),
16+
min_should: s.unknown("Minimum number of should conditions that must match."),
17+
});
18+
19+
const pointSchema = s.object(
20+
"A Qdrant point to insert or update.",
21+
{
22+
id: pointIdSchema,
23+
vector: vectorSchema,
24+
payload: payloadSchema,
25+
},
26+
{ optional: ["payload"] },
27+
);
28+
29+
const recordSchema = s.looseRequiredObject(
30+
"A Qdrant point record.",
31+
{
32+
id: pointIdSchema,
33+
payload: s.nullable(payloadSchema),
34+
vector: s.nullable(vectorSchema),
35+
shard_key: s.unknown("The Qdrant shard key when present."),
36+
order_value: s.unknown("The Qdrant order value when present."),
37+
},
38+
{ optional: ["payload", "vector", "shard_key", "order_value"] },
39+
);
40+
41+
const scoredPointSchema = s.looseRequiredObject(
42+
"A Qdrant scored point.",
43+
{
44+
id: pointIdSchema,
45+
version: s.nonNegativeInteger("The point version."),
46+
score: s.number("The similarity score."),
47+
payload: s.nullable(payloadSchema),
48+
vector: s.nullable(vectorSchema),
49+
shard_key: s.unknown("The Qdrant shard key when present."),
50+
order_value: s.unknown("The Qdrant order value when present."),
51+
},
52+
{ optional: ["payload", "vector", "shard_key", "order_value"] },
53+
);
54+
55+
const collectionSchema = s.looseRequiredObject(
56+
"A Qdrant collection description.",
57+
{
58+
status: s.unknown("The collection status."),
59+
optimizer_status: s.unknown("The collection optimizer status."),
60+
indexed_vectors_count: s.nullable(s.nonNegativeInteger("The approximate indexed vector count.")),
61+
points_count: s.nullable(s.nonNegativeInteger("The approximate point count.")),
62+
segments_count: s.nonNegativeInteger("The number of collection segments."),
63+
config: s.looseObject("The collection configuration."),
64+
payload_schema: s.looseObject("The collection payload index schema."),
65+
warnings: s.array("Collection warnings.", s.looseObject("A collection warning.")),
66+
},
67+
{ optional: ["indexed_vectors_count", "points_count", "warnings"] },
68+
);
69+
70+
const filterInputFields = {
71+
filter: filterSchema,
72+
limit: s.integer("The maximum number of points to return.", { minimum: 1, maximum: 1000 }),
73+
withPayload: s.boolean("Whether to include point payloads."),
74+
withVector: s.boolean("Whether to include point vectors."),
75+
};
76+
77+
export const qdrantActions: ProviderActionDefinition[] = [
78+
defineProviderAction(service, {
79+
name: "list_collections",
80+
description: "List the Qdrant collections visible to the authenticated API key.",
81+
inputSchema: s.actionInput({}, [], "Input parameters for listing Qdrant collections."),
82+
outputSchema: s.actionOutput(
83+
{
84+
collections: s.array(
85+
"The visible Qdrant collections.",
86+
s.requiredObject("A Qdrant collection name.", { name: s.nonEmptyString("The collection name.") }),
87+
),
88+
},
89+
"The Qdrant collection list response.",
90+
),
91+
followUpActions: ["qdrant.get_collection", "qdrant.create_collection"],
92+
}),
93+
defineProviderAction(service, {
94+
name: "get_collection",
95+
description: "Retrieve configuration and status information for one Qdrant collection.",
96+
inputSchema: s.actionInput(
97+
{ collectionName: collectionNameSchema },
98+
["collectionName"],
99+
"Input parameters for retrieving a Qdrant collection.",
100+
),
101+
outputSchema: s.actionOutput({ collection: collectionSchema }, "The Qdrant collection description response."),
102+
followUpActions: ["qdrant.query_points", "qdrant.scroll_points", "qdrant.upsert_points"],
103+
}),
104+
defineProviderAction(service, {
105+
name: "create_collection",
106+
description: "Create a Qdrant Cloud collection with one unnamed dense vector configuration.",
107+
inputSchema: s.actionInput(
108+
{
109+
collectionName: collectionNameSchema,
110+
vectorSize: s.positiveInteger("The dense vector dimension."),
111+
distance: s.stringEnum("The distance function used by the collection.", [
112+
"Cosine",
113+
"Euclid",
114+
"Dot",
115+
"Manhattan",
116+
]),
117+
},
118+
["collectionName", "vectorSize", "distance"],
119+
"Input parameters for creating a dense-vector Qdrant collection.",
120+
),
121+
outputSchema: s.actionOutput({ created: s.boolean("Whether Qdrant created the collection.") }),
122+
followUpActions: ["qdrant.get_collection", "qdrant.upsert_points"],
123+
}),
124+
defineProviderAction(service, {
125+
name: "upsert_points",
126+
description: "Insert or replace dense-vector points in a Qdrant collection and wait for the write to commit.",
127+
inputSchema: s.actionInput(
128+
{
129+
collectionName: collectionNameSchema,
130+
points: s.array("The points to upsert.", pointSchema, { minItems: 1, maxItems: 1000 }),
131+
},
132+
["collectionName", "points"],
133+
"Input parameters for upserting Qdrant points.",
134+
),
135+
outputSchema: s.actionOutput(
136+
{
137+
operationId: s.nullable(s.nonNegativeInteger("The Qdrant operation ID when returned.")),
138+
status: s.stringEnum("The Qdrant write status.", ["acknowledged", "completed", "wait_timeout"]),
139+
},
140+
"The Qdrant upsert operation result.",
141+
),
142+
followUpActions: ["qdrant.get_point", "qdrant.query_points", "qdrant.scroll_points"],
143+
}),
144+
defineProviderAction(service, {
145+
name: "get_point",
146+
description: "Retrieve one point by numeric ID or UUID from a Qdrant collection.",
147+
inputSchema: s.actionInput(
148+
{ collectionName: collectionNameSchema, id: pointIdSchema },
149+
["collectionName", "id"],
150+
"Input parameters for retrieving a Qdrant point.",
151+
),
152+
outputSchema: s.actionOutput({ point: recordSchema }, "The Qdrant point record response."),
153+
}),
154+
defineProviderAction(service, {
155+
name: "query_points",
156+
description: "Search a dense-vector Qdrant collection with an optional payload filter.",
157+
inputSchema: s.actionInput(
158+
{
159+
collectionName: collectionNameSchema,
160+
vector: vectorSchema,
161+
...filterInputFields,
162+
offset: s.nonNegativeInteger("The number of matching points to skip."),
163+
scoreThreshold: s.number("The minimum score a result must have."),
164+
},
165+
["collectionName", "vector"],
166+
"Input parameters for querying Qdrant points.",
167+
),
168+
outputSchema: s.actionOutput(
169+
{ points: s.array("The scored points returned by Qdrant.", scoredPointSchema) },
170+
"The Qdrant query response.",
171+
),
172+
followUpActions: ["qdrant.get_point", "qdrant.scroll_points"],
173+
}),
174+
defineProviderAction(service, {
175+
name: "scroll_points",
176+
description: "Read one page of points from a Qdrant collection with an optional payload filter.",
177+
inputSchema: s.actionInput(
178+
{ collectionName: collectionNameSchema, offset: pointIdSchema, ...filterInputFields },
179+
["collectionName"],
180+
"Input parameters for scrolling through Qdrant points.",
181+
),
182+
outputSchema: s.actionOutput(
183+
{
184+
points: s.array("The point records returned by Qdrant.", recordSchema),
185+
nextOffset: s.nullable(pointIdSchema),
186+
complete: s.boolean("Whether there is no next page."),
187+
},
188+
"One page of Qdrant scroll results.",
189+
),
190+
followUpActions: ["qdrant.scroll_points", "qdrant.get_point"],
191+
}),
192+
];

src/providers/qdrant/definition.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { ProviderDefinition } from "../../core/types.ts";
2+
3+
import { qdrantActions } from "./actions.ts";
4+
5+
const service = "qdrant";
6+
7+
/**
8+
* Qdrant Cloud provider backed by the official REST API.
9+
*/
10+
export const provider: ProviderDefinition = {
11+
service,
12+
displayName: "Qdrant Cloud",
13+
description: "Create, inspect, write, and query dense-vector collections in Qdrant Cloud.",
14+
categories: ["Data", "Developer Tools"],
15+
authTypes: ["custom_credential"],
16+
auth: [
17+
{
18+
type: "custom_credential",
19+
fields: [
20+
{
21+
key: "clusterUrl",
22+
label: "Cluster URL",
23+
inputType: "text",
24+
required: true,
25+
secret: false,
26+
placeholder: "https://your-cluster.cloud.qdrant.io:6333",
27+
description:
28+
"The HTTPS REST URL for a Qdrant Cloud cluster. Only official cloud.qdrant.io endpoints on port 6333 are supported.",
29+
},
30+
{
31+
key: "apiKey",
32+
label: "API Key",
33+
inputType: "password",
34+
required: true,
35+
secret: true,
36+
placeholder: "Your Qdrant Cloud database API key",
37+
description: "A Qdrant Cloud database API key with access to the target collections.",
38+
},
39+
],
40+
},
41+
],
42+
homepageUrl: "https://qdrant.tech/cloud/",
43+
actions: qdrantActions,
44+
};

src/providers/qdrant/executors.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { CredentialValidators, ExecutionContext, ProviderExecutors } from "../../core/types.ts";
2+
3+
import { defineProviderExecutors, requireCustomCredential } from "../provider-runtime.ts";
4+
import { createQdrantContext, qdrantActionHandlers, validateQdrantCredential } from "./runtime.ts";
5+
6+
const service = "qdrant";
7+
8+
export const executors: ProviderExecutors = defineProviderExecutors({
9+
service,
10+
handlers: qdrantActionHandlers,
11+
async createContext(context: ExecutionContext, fetcher): Promise<ReturnType<typeof createQdrantContext>> {
12+
const credential = await requireCustomCredential(context, service);
13+
return createQdrantContext(credential.values, fetcher, context.signal);
14+
},
15+
});
16+
17+
export const credentialValidators: CredentialValidators = {
18+
customCredential(input, { fetcher, signal }) {
19+
return validateQdrantCredential(input.values, fetcher, signal);
20+
},
21+
};

0 commit comments

Comments
 (0)