@botbotgo/agent-harness 0.0.154 → 0.0.156

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.
@@ -3,6 +3,7 @@ import { mkdir } from "node:fs/promises";
3
3
  import { Document } from "@langchain/core/documents";
4
4
  import { createClient } from "@libsql/client";
5
5
  import { LibSQLVectorStore } from "@langchain/community/vectorstores/libsql";
6
+ import { QdrantClient } from "@qdrant/js-client-rest";
6
7
  import { compileVectorStore } from "../../workspace/resource-compilers.js";
7
8
  import { resolveRefId } from "../../workspace/support/workspace-ref-utils.js";
8
9
  import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./embedding-models.js";
@@ -52,6 +53,27 @@ async function ensureLibSqlSchema(db, table, column, dimensions) {
52
53
  ON ${safeTable}(libsql_vector_idx(${safeColumn}))
53
54
  `);
54
55
  }
56
+ function createQdrantClientForStore(vectorStore) {
57
+ return new QdrantClient({
58
+ ...(vectorStore.url ? { url: vectorStore.url } : {}),
59
+ ...(vectorStore.host ? { host: vectorStore.host } : {}),
60
+ ...(typeof vectorStore.port === "number" ? { port: vectorStore.port } : {}),
61
+ ...(vectorStore.authToken ? { apiKey: vectorStore.authToken } : {}),
62
+ checkCompatibility: false,
63
+ });
64
+ }
65
+ async function ensureQdrantCollection(client, collection, dimensions) {
66
+ const collections = await client.getCollections();
67
+ if (collections.collections.some((entry) => entry.name === collection)) {
68
+ return;
69
+ }
70
+ await client.createCollection(collection, {
71
+ vectors: {
72
+ size: dimensions,
73
+ distance: "Cosine",
74
+ },
75
+ });
76
+ }
55
77
  export function resolveCompiledVectorStoreRef(workspace, vectorStoreRef) {
56
78
  const resolvedId = vectorStoreRef ? resolveRefId(vectorStoreRef) : "default";
57
79
  const vectorStore = workspace.vectorStores.get(resolvedId);
@@ -75,6 +97,81 @@ export async function resolveCompiledVectorStore(workspace, vectorStore, options
75
97
  const embeddings = await resolveCompiledEmbeddingModel(embeddingModel, options.embeddingModelResolver);
76
98
  return createLlamaIndexVectorStore(workspace.workspaceRoot, vectorStore, embeddings);
77
99
  }
100
+ if (vectorStore.kind === "QdrantVectorStore") {
101
+ const embeddingModel = resolveCompiledEmbeddingModelRef(workspace, vectorStore.embeddingModelRef);
102
+ const embeddings = await resolveCompiledEmbeddingModel(embeddingModel, options.embeddingModelResolver);
103
+ const client = createQdrantClientForStore(vectorStore);
104
+ const collection = vectorStore.collection ?? "vectors";
105
+ const ensureCollectionForTexts = async (texts) => {
106
+ const seedText = texts.find((text) => text.trim().length > 0) ?? "seed";
107
+ const sample = await embeddings.embedQuery(seedText);
108
+ await ensureQdrantCollection(client, collection, sample.length);
109
+ };
110
+ return {
111
+ kind: vectorStore.kind,
112
+ embeddingModelRef: vectorStore.embeddingModelRef,
113
+ addDocuments: async (documents) => {
114
+ if (documents.length === 0) {
115
+ return [];
116
+ }
117
+ await ensureCollectionForTexts(documents.map((document) => document.pageContent));
118
+ const vectors = await embeddings.embedDocuments(documents.map((document) => document.pageContent));
119
+ const ids = documents.map((_, index) => `${Date.now()}-${index}-${Math.random().toString(36).slice(2, 10)}`);
120
+ await client.upsert(collection, {
121
+ wait: true,
122
+ points: documents.map((document, index) => ({
123
+ id: ids[index] ?? index,
124
+ vector: vectors[index] ?? [],
125
+ payload: {
126
+ pageContent: document.pageContent,
127
+ ...(document.metadata ? { metadata: document.metadata } : {}),
128
+ },
129
+ })),
130
+ });
131
+ return ids;
132
+ },
133
+ similaritySearch: async (query, limit) => {
134
+ const collections = await client.getCollections();
135
+ if (!collections.collections.some((entry) => entry.name === collection)) {
136
+ return [];
137
+ }
138
+ const queryVector = await embeddings.embedQuery(query);
139
+ const rows = await client.search(collection, {
140
+ vector: queryVector,
141
+ limit,
142
+ with_payload: true,
143
+ });
144
+ return rows.map((row) => {
145
+ const payload = (row.payload ?? {});
146
+ return {
147
+ pageContent: typeof payload.pageContent === "string" ? payload.pageContent : "",
148
+ metadata: typeof payload.metadata === "object" && payload.metadata && !Array.isArray(payload.metadata)
149
+ ? payload.metadata
150
+ : {},
151
+ score: typeof row.score === "number" ? row.score : undefined,
152
+ };
153
+ });
154
+ },
155
+ delete: async (params) => {
156
+ const collections = await client.getCollections();
157
+ if (!collections.collections.some((entry) => entry.name === collection)) {
158
+ return;
159
+ }
160
+ if (params.deleteAll) {
161
+ await client.deleteCollection(collection);
162
+ return;
163
+ }
164
+ const pointIds = (params.ids ?? []).filter((id) => typeof id === "string" || typeof id === "number");
165
+ if (pointIds.length === 0) {
166
+ return;
167
+ }
168
+ await client.delete(collection, {
169
+ wait: true,
170
+ points: pointIds,
171
+ });
172
+ },
173
+ };
174
+ }
78
175
  if (vectorStore.kind !== "LibSQLVectorStore") {
79
176
  throw new Error(`Vector store kind ${vectorStore.kind} is not supported by the built-in runtime.`);
80
177
  }
@@ -6,7 +6,7 @@ import { resolveIsolatedResourceModulePath } from "../resource/isolation.js";
6
6
  import { isExternalSourceLocator, resolveResourcePackageRoot } from "../resource/sources.js";
7
7
  import { discoverToolModuleDefinitions, isSupportedToolModulePath } from "../tool-modules.js";
8
8
  import { fileExists } from "../utils/fs.js";
9
- import { readNamedModelItems, readNamedYamlItems, readYamlItems, } from "./yaml-object-reader.js";
9
+ import { readNamedYamlItems, readYamlItems, } from "./yaml-object-reader.js";
10
10
  export { normalizeYamlItem, readYamlItems } from "./yaml-object-reader.js";
11
11
  const CONVENTIONAL_OBJECT_DIRECTORIES = ["tools"];
12
12
  const MODULE_AGENT_FILENAMES = ["agent.yaml", "agent.yml"];
@@ -39,16 +39,10 @@ function conventionalConfigRoot(root) {
39
39
  }
40
40
  function conventionalDirectoryRoots(root, relativeDir) {
41
41
  const resourceRoot = resolveResourcePackageRoot(root);
42
- const configRoot = conventionalConfigRoot(root);
43
- const candidates = relativeDir === "agents"
44
- ? [
45
- ...(configRoot ? [path.join(configRoot, "agents")] : []),
46
- path.join(root, "agents"),
47
- ]
48
- : [
49
- ...(resourceRoot ? [path.join(resourceRoot, relativeDir)] : []),
50
- path.join(root, relativeDir),
51
- ];
42
+ const candidates = [
43
+ ...(resourceRoot ? [path.join(resourceRoot, relativeDir)] : []),
44
+ path.join(root, relativeDir),
45
+ ];
52
46
  return Array.from(new Set(candidates));
53
47
  }
54
48
  export function conventionalPackageRoots(root, relativeDir) {
@@ -569,20 +563,6 @@ function mergeAgentRecord(records, item, sourcePath) {
569
563
  function mergeWorkspaceObjectRecord(records, workspaceObject, item, sourcePath) {
570
564
  mergeRawItemRecord(records, `${workspaceObject.kind}/${workspaceObject.id}`, item, sourcePath);
571
565
  }
572
- async function loadNamedModelsForRoot(configRoot, mergedObjects) {
573
- for (const { item, sourcePath } of await readNamedModelItems(configRoot)) {
574
- const workspaceObject = parseWorkspaceObject(item, sourcePath);
575
- if (!workspaceObject || workspaceObject.kind !== "model") {
576
- continue;
577
- }
578
- mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
579
- }
580
- }
581
- async function loadConfigAgentsForRoot(configRoot, mergedAgents) {
582
- for (const { item, sourcePath } of await readConfigAgentItems(configRoot)) {
583
- mergeAgentRecord(mergedAgents, item, sourcePath);
584
- }
585
- }
586
566
  async function loadModuleAgentsForRoot(root, mergedAgents) {
587
567
  const modulesRoot = moduleCollectionRoot(root, "agents");
588
568
  if (!(await fileExists(modulesRoot))) {
@@ -675,7 +655,7 @@ async function loadModuleObjectsForRoot(root, mergedObjects) {
675
655
  mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
676
656
  }
677
657
  }
678
- async function loadConfigObjectsForRoot(root, configRoot, mergedObjects) {
658
+ async function loadConfigYamlForRoot(root, configRoot, mergedAgents, mergedObjects) {
679
659
  if (!conventionalConfigRoot(root)) {
680
660
  return;
681
661
  }
@@ -684,7 +664,8 @@ async function loadConfigObjectsForRoot(root, configRoot, mergedObjects) {
684
664
  if (!workspaceObject) {
685
665
  continue;
686
666
  }
687
- if (isAgentKind(workspaceObject.kind) || workspaceObject.kind === "model") {
667
+ if (isAgentKind(workspaceObject.kind)) {
668
+ mergeAgentRecord(mergedAgents, item, sourcePath);
688
669
  continue;
689
670
  }
690
671
  mergeWorkspaceObjectRecord(mergedObjects, workspaceObject, item, sourcePath);
@@ -708,20 +689,6 @@ async function loadRootObjects(root, mergedObjects) {
708
689
  function isAgentKind(kind) {
709
690
  return kind === "agent";
710
691
  }
711
- async function readConfigAgentItems(configRoot) {
712
- const records = await readYamlItems(configRoot, undefined, { recursive: true });
713
- return records.filter(({ item, sourcePath }) => {
714
- const kind = typeof item.kind === "string" ? item.kind : undefined;
715
- if (!isAgentKind(kind)) {
716
- return false;
717
- }
718
- const relativePath = path.relative(configRoot, sourcePath);
719
- if (!relativePath || relativePath.startsWith("..")) {
720
- return false;
721
- }
722
- return !relativePath.includes(path.sep) || relativePath.startsWith(`agents${path.sep}`);
723
- });
724
- }
725
692
  export async function readToolModuleItems(root) {
726
693
  if (!(await fileExists(root))) {
727
694
  return [];
@@ -792,11 +759,9 @@ export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
792
759
  const roots = [frameworkWorkspaceRoot(), ...(options.overlayRoots ?? []), workspaceRoot];
793
760
  for (const root of roots) {
794
761
  const configRoot = conventionalConfigRoot(root) ?? root;
795
- await loadNamedModelsForRoot(configRoot, mergedObjects);
796
- await loadConfigAgentsForRoot(configRoot, mergedAgents);
762
+ await loadConfigYamlForRoot(root, configRoot, mergedAgents, mergedObjects);
797
763
  await loadModuleAgentsForRoot(root, mergedAgents);
798
764
  await loadConventionalObjectsForRoot(root, mergedObjects);
799
- await loadConfigObjectsForRoot(root, configRoot, mergedObjects);
800
765
  await loadModuleObjectsForRoot(root, mergedObjects);
801
766
  await loadRootObjects(root, mergedObjects);
802
767
  }
@@ -89,13 +89,21 @@ export function parseEmbeddingModelObject(object) {
89
89
  }
90
90
  export function parseVectorStoreObject(object) {
91
91
  const value = object.value;
92
+ const port = typeof value.port === "number" && Number.isInteger(value.port) ? value.port : undefined;
92
93
  return {
93
94
  id: object.id,
94
95
  kind: String(value.storeKind ?? value.kind ?? "LibSQLVectorStore").trim(),
95
96
  url: typeof value.url === "string" ? value.url : undefined,
97
+ host: typeof value.host === "string" ? value.host : undefined,
98
+ port,
96
99
  authToken: typeof value.authToken === "string" ? value.authToken : undefined,
97
100
  table: typeof value.table === "string" ? value.table : undefined,
98
101
  column: typeof value.column === "string" ? value.column : undefined,
102
+ collection: typeof value.collection === "string"
103
+ ? value.collection
104
+ : typeof value.collectionName === "string"
105
+ ? value.collectionName
106
+ : undefined,
99
107
  embeddingModelRef: typeof value.embeddingModelRef === "string"
100
108
  ? value.embeddingModelRef
101
109
  : typeof asObject(value.embeddingModel)?.ref === "string"
@@ -155,6 +163,14 @@ export function validateVectorStoreObject(vectorStore) {
155
163
  if ((vectorStore.kind === "LibSQLVectorStore" || vectorStore.kind === "LlamaIndexSimpleVectorStore") && !vectorStore.url) {
156
164
  throw new Error(`Vector store ${vectorStore.id} url must not be empty for ${vectorStore.kind}`);
157
165
  }
166
+ if (vectorStore.kind === "QdrantVectorStore") {
167
+ if (!vectorStore.url && !vectorStore.host) {
168
+ throw new Error(`Vector store ${vectorStore.id} must define url or host for QdrantVectorStore`);
169
+ }
170
+ if (!vectorStore.collection) {
171
+ throw new Error(`Vector store ${vectorStore.id} collection must not be empty for QdrantVectorStore`);
172
+ }
173
+ }
158
174
  }
159
175
  export function validateMcpServerObject(server) {
160
176
  if (!server.id.trim()) {
@@ -186,9 +202,12 @@ export function compileVectorStore(vectorStore) {
186
202
  id: vectorStore.id,
187
203
  kind: vectorStore.kind,
188
204
  url: vectorStore.url,
205
+ host: vectorStore.host,
206
+ port: vectorStore.port,
189
207
  authToken: vectorStore.authToken,
190
208
  table: vectorStore.table,
191
209
  column: vectorStore.column,
210
+ collection: vectorStore.collection,
192
211
  embeddingModelRef: vectorStore.embeddingModelRef,
193
212
  runtimeValue: vectorStore.kind,
194
213
  };
@@ -9,7 +9,3 @@ export declare function readNamedYamlItems(root: string, filenames: string[]): P
9
9
  item: Record<string, unknown>;
10
10
  sourcePath: string;
11
11
  }>>;
12
- export declare function readNamedModelItems(root: string): Promise<Array<{
13
- item: Record<string, unknown>;
14
- sourcePath: string;
15
- }>>;
@@ -2,7 +2,6 @@ import path from "node:path";
2
2
  import { readdir } from "node:fs/promises";
3
3
  import { parseAllDocuments } from "yaml";
4
4
  import { fileExists, listFilesRecursive, readYamlOrJson } from "../utils/fs.js";
5
- const MODEL_FILENAMES = ["models.yaml", "models.yml"];
6
5
  const ENV_PLACEHOLDER_PATTERN = /\$\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g;
7
6
  function asObject(value) {
8
7
  return typeof value === "object" && value ? value : undefined;
@@ -62,6 +61,8 @@ function normalizeKind(kind) {
62
61
  return "agent";
63
62
  case "FileStore":
64
63
  return "file-store";
64
+ case "SqliteStore":
65
+ return "sqlite-store";
65
66
  case "InMemoryStore":
66
67
  return "in-memory-store";
67
68
  case "RedisStore":
@@ -163,7 +164,10 @@ export async function readYamlItems(root, relativeDir, options = {}) {
163
164
  return [];
164
165
  }
165
166
  const files = options.recursive
166
- ? await listFilesRecursive(targetRoot, ".yaml")
167
+ ? Array.from(new Set([
168
+ ...(await listFilesRecursive(targetRoot, ".yaml")),
169
+ ...(await listFilesRecursive(targetRoot, ".yml")),
170
+ ])).sort()
167
171
  : (await readdir(targetRoot, { withFileTypes: true }))
168
172
  .filter((entry) => entry.isFile() && /\.ya?ml$/i.test(entry.name))
169
173
  .map((entry) => path.join(targetRoot, entry.name))
@@ -197,33 +201,3 @@ export async function readNamedYamlItems(root, filenames) {
197
201
  }
198
202
  return records;
199
203
  }
200
- export async function readNamedModelItems(root) {
201
- const filePaths = new Set();
202
- for (const filename of MODEL_FILENAMES) {
203
- const directPath = path.join(root, filename);
204
- if (await fileExists(directPath)) {
205
- filePaths.add(directPath);
206
- }
207
- }
208
- for (const extension of [".yaml", ".yml"]) {
209
- for (const filePath of await listFilesRecursive(root, extension)) {
210
- if (MODEL_FILENAMES.includes(path.basename(filePath))) {
211
- filePaths.add(filePath);
212
- }
213
- }
214
- }
215
- const records = [];
216
- for (const filePath of [...filePaths].sort()) {
217
- const parsedDocuments = parseAllDocuments(await readYamlOrJson(filePath));
218
- for (const parsedDocument of parsedDocuments) {
219
- const resolvedDocument = interpolateEnvPlaceholders(parsedDocument.toJSON(), filePath);
220
- for (const item of await objectItemsFromDocument(resolvedDocument, filePath)) {
221
- const normalized = normalizeYamlItem(item);
222
- if (normalized.kind === "model" && typeof normalized.id === "string" && normalized.id.trim()) {
223
- records.push({ item: normalized, sourcePath: filePath });
224
- }
225
- }
226
- }
227
- }
228
- return records;
229
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.154",
3
+ "version": "0.0.156",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",