@kognitivedev/cloud-knowledge-base 0.2.29

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.
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCloudKnowledgeBaseSearchStep = createCloudKnowledgeBaseSearchStep;
4
+ exports.createCloudKnowledgeBaseAnswerStep = createCloudKnowledgeBaseAnswerStep;
5
+ const workflows_1 = require("@kognitivedev/workflows");
6
+ const search_1 = require("./search");
7
+ async function resolveStepValue(value, input, context) {
8
+ if (typeof value === "function") {
9
+ return value(input, { resourceId: context.resourceId, abortSignal: context.abortSignal });
10
+ }
11
+ return value;
12
+ }
13
+ async function searchForWorkflow(options, input, context) {
14
+ var _a, _b;
15
+ const inheritedContext = context;
16
+ const query = await options.getQuery(input, {
17
+ resourceId: inheritedContext.resourceId,
18
+ abortSignal: inheritedContext.abortSignal,
19
+ });
20
+ return (0, search_1.searchCloudKnowledgeBase)({
21
+ client: options.client,
22
+ baseUrl: (_a = options.baseUrl) !== null && _a !== void 0 ? _a : inheritedContext.baseUrl,
23
+ apiKey: (_b = options.apiKey) !== null && _b !== void 0 ? _b : inheritedContext.apiKey,
24
+ fetch: options.fetch,
25
+ timeout: options.timeout,
26
+ logLevel: options.logLevel,
27
+ pipelineId: options.pipelineId,
28
+ resolvePipelineId: options.resolvePipelineId,
29
+ query,
30
+ topK: options.topK === undefined ? undefined : await resolveStepValue(options.topK, input, context),
31
+ retrievalMode: options.retrievalMode === undefined
32
+ ? undefined
33
+ : await resolveStepValue(options.retrievalMode, input, context),
34
+ snippetMaxLength: options.snippetMaxLength,
35
+ resourceId: inheritedContext.resourceId,
36
+ abortSignal: inheritedContext.abortSignal,
37
+ });
38
+ }
39
+ function createCloudKnowledgeBaseSearchStep(options) {
40
+ return (0, workflows_1.createKnowledgeBaseSearchStep)({
41
+ id: options.id,
42
+ description: options.description,
43
+ search: async (input, context) => searchForWorkflow(options, input, context),
44
+ extractOutput: options.extractOutput,
45
+ });
46
+ }
47
+ function createCloudKnowledgeBaseAnswerStep(options) {
48
+ return (0, workflows_1.createKnowledgeBaseAnswerStep)({
49
+ id: options.id,
50
+ description: options.description,
51
+ agent: options.agent,
52
+ search: async (input, context) => searchForWorkflow(options, input, context),
53
+ buildMessages: options.buildMessages,
54
+ temperature: options.temperature,
55
+ maxOutputTokens: options.maxOutputTokens,
56
+ emptyResultAnswer: options.emptyResultAnswer,
57
+ extractOutput: options.extractOutput,
58
+ });
59
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@kognitivedev/cloud-knowledge-base",
3
+ "version": "0.2.29",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc -w --noCheck",
12
+ "prepublishOnly": "npm run build",
13
+ "test": "vitest run"
14
+ },
15
+ "dependencies": {
16
+ "@kognitivedev/agents": "^0.2.29",
17
+ "@kognitivedev/documents": "^0.2.29",
18
+ "@kognitivedev/shared": "^0.2.29",
19
+ "@kognitivedev/tools": "^0.2.29",
20
+ "@kognitivedev/workflows": "^0.2.29"
21
+ },
22
+ "peerDependencies": {
23
+ "zod": ">=3.23.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20.0.0",
27
+ "typescript": "^5.0.0",
28
+ "vitest": "^3.0.0",
29
+ "zod": "^3.23.8"
30
+ },
31
+ "description": "Managed cloud knowledge base SDK for Kognitive agents and workflows",
32
+ "keywords": [
33
+ "kognitive",
34
+ "knowledge-base",
35
+ "retrieval",
36
+ "agents",
37
+ "workflows",
38
+ "pipelines"
39
+ ],
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "https://github.com/kognitivedev/kognitive",
44
+ "directory": "packages/cloud-knowledge-base"
45
+ },
46
+ "homepage": "https://kognitive.dev"
47
+ }
@@ -0,0 +1,493 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { createWorkflow } from "@kognitivedev/workflows";
3
+ import type { KognitiveDocumentsClient, PipelineSearchResult } from "@kognitivedev/documents";
4
+ import {
5
+ createCloudKnowledgeBaseAnswerStep,
6
+ createCloudKnowledgeBaseContextAdapter,
7
+ createCloudKnowledgeBaseSearchStep,
8
+ createCloudKnowledgeBaseSearchTool,
9
+ searchCloudKnowledgeBase,
10
+ } from "../index";
11
+
12
+ function makePipelineResult(overrides: Partial<PipelineSearchResult> = {}): PipelineSearchResult {
13
+ return {
14
+ id: "artifact_1",
15
+ content: "Retention policy is 30 days for standard workspaces.",
16
+ score: 0.92,
17
+ metadata: {
18
+ filename: "policy.pdf",
19
+ mimeType: "application/pdf",
20
+ pageNumber: 3,
21
+ artifactType: "chunk",
22
+ parseDocumentId: "parse_1",
23
+ },
24
+ ...overrides,
25
+ };
26
+ }
27
+
28
+ describe("@kognitivedev/cloud-knowledge-base", () => {
29
+ it("normalizes search results and forwards auth/baseUrl via the documents client", async () => {
30
+ const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
31
+ expect(String(_input)).toBe("https://kb.example.com/api/cloud/pipelines/pipeline-1/search");
32
+ expect(init?.method).toBe("POST");
33
+ expect(new Headers(init?.headers).get("authorization")).toBe("Bearer secret-key");
34
+ return new Response(JSON.stringify({
35
+ results: [makePipelineResult()],
36
+ }), {
37
+ status: 200,
38
+ headers: { "content-type": "application/json" },
39
+ });
40
+ });
41
+
42
+ const result = await searchCloudKnowledgeBase({
43
+ baseUrl: "https://kb.example.com",
44
+ apiKey: "secret-key",
45
+ fetch: fetchMock,
46
+ pipelineId: "pipeline-1",
47
+ query: "retention policy",
48
+ resourceId: { userId: "user-1" },
49
+ });
50
+
51
+ expect(result).toEqual({
52
+ pipelineId: "pipeline-1",
53
+ query: "retention policy",
54
+ hits: [{
55
+ sourceId: "artifact_1",
56
+ content: "Retention policy is 30 days for standard workspaces.",
57
+ score: 0.92,
58
+ citation: {
59
+ sourceId: "artifact_1",
60
+ snippet: "Retention policy is 30 days for standard workspaces.",
61
+ filename: "policy.pdf",
62
+ mimeType: "application/pdf",
63
+ pageNumber: 3,
64
+ artifactType: "chunk",
65
+ parseDocumentId: "parse_1",
66
+ },
67
+ metadata: {
68
+ filename: "policy.pdf",
69
+ mimeType: "application/pdf",
70
+ pageNumber: 3,
71
+ artifactType: "chunk",
72
+ parseDocumentId: "parse_1",
73
+ },
74
+ }],
75
+ citations: [{
76
+ sourceId: "artifact_1",
77
+ snippet: "Retention policy is 30 days for standard workspaces.",
78
+ filename: "policy.pdf",
79
+ mimeType: "application/pdf",
80
+ pageNumber: 3,
81
+ artifactType: "chunk",
82
+ parseDocumentId: "parse_1",
83
+ }],
84
+ });
85
+ expect(fetchMock).toHaveBeenCalledTimes(1);
86
+ });
87
+
88
+ it("supports dynamic pipeline resolution with a provided documents client", async () => {
89
+ const client = {
90
+ pipelines: {
91
+ search: vi.fn(async () => [makePipelineResult({ id: "artifact_2" })]),
92
+ },
93
+ } as unknown as KognitiveDocumentsClient;
94
+ const resolvePipelineId = vi.fn(async ({ resourceId }) => `pipeline-for-${resourceId.userId}`);
95
+
96
+ const result = await searchCloudKnowledgeBase({
97
+ client,
98
+ resolvePipelineId,
99
+ query: "retention policy",
100
+ resourceId: { userId: "tenant-42" },
101
+ });
102
+
103
+ expect(resolvePipelineId).toHaveBeenCalledWith(expect.objectContaining({
104
+ resourceId: { userId: "tenant-42" },
105
+ }));
106
+ expect((client.pipelines.search as any)).toHaveBeenCalledWith("pipeline-for-tenant-42", expect.objectContaining({
107
+ query: "retention policy",
108
+ }));
109
+ expect(result.pipelineId).toBe("pipeline-for-tenant-42");
110
+ expect(result.hits[0]?.sourceId).toBe("artifact_2");
111
+ });
112
+
113
+ it("preserves spreadsheet and section citation metadata for grounded answers", async () => {
114
+ const client = {
115
+ pipelines: {
116
+ search: vi.fn(async () => [makePipelineResult({
117
+ metadata: {
118
+ filename: "Excel recetas oncoalicia 2024.xlsx",
119
+ artifactType: "spreadsheet_row",
120
+ sheetName: "Recetas",
121
+ rowNumber: 42,
122
+ cellRange: "A42:Z42",
123
+ sectionTitle: "Náuseas y vómitos",
124
+ },
125
+ })]),
126
+ },
127
+ } as unknown as KognitiveDocumentsClient;
128
+
129
+ const result = await searchCloudKnowledgeBase({
130
+ client,
131
+ pipelineId: "pipeline-1",
132
+ query: "nausea recipes",
133
+ resourceId: { userId: "user-1" },
134
+ });
135
+
136
+ expect(result.hits[0]?.citation).toMatchObject({
137
+ filename: "Excel recetas oncoalicia 2024.xlsx",
138
+ artifactType: "spreadsheet_row",
139
+ sheetName: "Recetas",
140
+ rowNumber: 42,
141
+ cellRange: "A42:Z42",
142
+ sectionTitle: "Náuseas y vómitos",
143
+ });
144
+ expect(result.citations[0]).toMatchObject({
145
+ sheetName: "Recetas",
146
+ rowNumber: 42,
147
+ cellRange: "A42:Z42",
148
+ });
149
+ });
150
+
151
+ it("maps transport failures to a pipeline-scoped error", async () => {
152
+ const fetchMock = vi.fn(async () => new Response(JSON.stringify({
153
+ error: "Pipeline unavailable",
154
+ }), {
155
+ status: 503,
156
+ headers: { "content-type": "application/json" },
157
+ }));
158
+
159
+ await expect(searchCloudKnowledgeBase({
160
+ baseUrl: "https://kb.example.com",
161
+ fetch: fetchMock,
162
+ pipelineId: "pipeline-1",
163
+ query: "retention policy",
164
+ resourceId: { userId: "user-1" },
165
+ })).rejects.toThrow('Cloud knowledge base search failed for pipeline "pipeline-1": Pipeline unavailable');
166
+ });
167
+
168
+ it("preserves abort propagation through the documents client transport", async () => {
169
+ const controller = new AbortController();
170
+ let requestSignal: AbortSignal | undefined;
171
+ const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
172
+ requestSignal = init?.signal;
173
+ return new Promise<Response>((resolve) => {
174
+ setTimeout(() => {
175
+ resolve(new Response(JSON.stringify({
176
+ results: [makePipelineResult()],
177
+ }), {
178
+ status: 200,
179
+ headers: { "content-type": "application/json" },
180
+ }));
181
+ }, 20);
182
+ });
183
+ });
184
+
185
+ const request = searchCloudKnowledgeBase({
186
+ baseUrl: "https://kb.example.com",
187
+ fetch: fetchMock,
188
+ pipelineId: "pipeline-1",
189
+ query: "retention policy",
190
+ resourceId: { userId: "user-1" },
191
+ abortSignal: controller.signal,
192
+ });
193
+ controller.abort(new Error("cancelled"));
194
+ await new Promise((resolve) => setTimeout(resolve, 10));
195
+
196
+ await expect(request).resolves.toEqual(expect.objectContaining({
197
+ pipelineId: "pipeline-1",
198
+ }));
199
+ expect(requestSignal).toBeInstanceOf(AbortSignal);
200
+ expect(requestSignal).not.toBe(controller.signal);
201
+ });
202
+
203
+ it("creates a search tool with bounded citation-rich model output", async () => {
204
+ const client = {
205
+ pipelines: {
206
+ search: vi.fn(async () => [
207
+ makePipelineResult({
208
+ content: "Retention policy is 30 days. ".repeat(20),
209
+ }),
210
+ makePipelineResult({
211
+ id: "artifact_2",
212
+ content: "Backups are retained for 90 days. ".repeat(10),
213
+ }),
214
+ ]),
215
+ },
216
+ } as unknown as KognitiveDocumentsClient;
217
+
218
+ const tool = createCloudKnowledgeBaseSearchTool({
219
+ client,
220
+ pipelineId: "pipeline-1",
221
+ modelSummaryMaxHits: 1,
222
+ modelSummaryMaxCharacters: 80,
223
+ });
224
+
225
+ const output = await tool.execute({
226
+ query: "retention policy",
227
+ }, {
228
+ resourceId: { userId: "user-1" },
229
+ abortSignal: new AbortController().signal,
230
+ emit: () => {},
231
+ });
232
+
233
+ const modelOutput = tool.toModelOutput?.(output);
234
+ expect(typeof modelOutput).toBe("string");
235
+ expect(modelOutput).toContain('Knowledge base search results for "retention policy"');
236
+ expect(modelOutput).toContain("citation=");
237
+ expect(modelOutput).toContain("Additional hits omitted");
238
+ });
239
+
240
+ it("creates a context adapter that injects system context and auto-registers the search tool", async () => {
241
+ const adapter = createCloudKnowledgeBaseContextAdapter({
242
+ pipelineId: "pipeline-1",
243
+ maxContextHits: 1,
244
+ maxContextCharacters: 120,
245
+ fetch: vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
246
+ expect(String(_input)).toBe("https://kb.example.com/api/cloud/pipelines/pipeline-1/search");
247
+ expect(new Headers(init?.headers).get("authorization")).toBe("Bearer inherited-key");
248
+ return new Response(JSON.stringify({
249
+ results: [makePipelineResult()],
250
+ }), {
251
+ status: 200,
252
+ headers: { "content-type": "application/json" },
253
+ });
254
+ }),
255
+ });
256
+
257
+ const result = await adapter.resolve({
258
+ resourceId: { userId: "user-1" },
259
+ messages: [{
260
+ role: "user",
261
+ content: "What is the retention policy?",
262
+ }] as any,
263
+ abortSignal: new AbortController().signal,
264
+ apiKey: "inherited-key",
265
+ baseUrl: "https://kb.example.com",
266
+ });
267
+
268
+ expect(result.systemBlock).toContain("<CloudKnowledgeBaseContext>");
269
+ expect(result.systemBlock).toContain("pipeline-1");
270
+ expect(result.tools?.[0]?.id).toBe("search-cloud-knowledge-base");
271
+
272
+ const toolResult = await result.tools?.[0]?.execute({
273
+ query: "retention policy",
274
+ }, {
275
+ resourceId: { userId: "user-1" },
276
+ abortSignal: new AbortController().signal,
277
+ emit: () => {},
278
+ } as any);
279
+ expect(toolResult).toEqual(expect.objectContaining({
280
+ pipelineId: "pipeline-1",
281
+ }));
282
+ });
283
+
284
+ it("prefers explicit adapter backend config over inherited host config", async () => {
285
+ const fetchMock = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
286
+ expect(String(_input)).toBe("https://explicit.example.com/api/cloud/pipelines/pipeline-1/search");
287
+ expect(new Headers(init?.headers).get("authorization")).toBe("Bearer explicit-key");
288
+ return new Response(JSON.stringify({
289
+ results: [makePipelineResult()],
290
+ }), {
291
+ status: 200,
292
+ headers: { "content-type": "application/json" },
293
+ });
294
+ });
295
+
296
+ const adapter = createCloudKnowledgeBaseContextAdapter({
297
+ pipelineId: "pipeline-1",
298
+ baseUrl: "https://explicit.example.com",
299
+ apiKey: "explicit-key",
300
+ fetch: fetchMock,
301
+ });
302
+
303
+ await adapter.resolve({
304
+ resourceId: { userId: "user-1" },
305
+ messages: [{ role: "user", content: "retention policy" }] as any,
306
+ abortSignal: new AbortController().signal,
307
+ apiKey: "inherited-key",
308
+ baseUrl: "https://inherited.example.com",
309
+ });
310
+
311
+ expect(fetchMock).toHaveBeenCalledTimes(1);
312
+ });
313
+
314
+ it("prefers an explicit adapter client over inherited backend config", async () => {
315
+ const client = {
316
+ pipelines: {
317
+ search: vi.fn(async () => [makePipelineResult()]),
318
+ },
319
+ } as unknown as KognitiveDocumentsClient;
320
+
321
+ const adapter = createCloudKnowledgeBaseContextAdapter({
322
+ client,
323
+ pipelineId: "pipeline-1",
324
+ });
325
+
326
+ await adapter.resolve({
327
+ resourceId: { userId: "user-1" },
328
+ messages: [{ role: "user", content: "retention policy" }] as any,
329
+ abortSignal: new AbortController().signal,
330
+ apiKey: "inherited-key",
331
+ baseUrl: "https://inherited.example.com",
332
+ });
333
+
334
+ expect((client.pipelines.search as any)).toHaveBeenCalledWith(
335
+ "pipeline-1",
336
+ expect.objectContaining({ query: "retention policy" }),
337
+ );
338
+ });
339
+
340
+ it("throws a clear error when neither explicit nor inherited backend config is available", async () => {
341
+ const adapter = createCloudKnowledgeBaseContextAdapter({
342
+ pipelineId: "pipeline-1",
343
+ });
344
+
345
+ await expect(adapter.resolve({
346
+ resourceId: { userId: "user-1" },
347
+ messages: [{ role: "user", content: "retention policy" }] as any,
348
+ abortSignal: new AbortController().signal,
349
+ })).rejects.toThrow(
350
+ "Cloud knowledge base backend configuration is missing. Provide a documents client or a baseUrl, or inherit them from the host agent/workflow.",
351
+ );
352
+ });
353
+
354
+ it("provides cloud knowledge base search and answer workflow wrappers", async () => {
355
+ const client = {
356
+ pipelines: {
357
+ search: vi.fn(async () => [makePipelineResult()]),
358
+ },
359
+ } as unknown as KognitiveDocumentsClient;
360
+ const agent = {
361
+ generateObject: vi.fn(async () => ({
362
+ text: "",
363
+ object: {
364
+ answer: "The retention policy is 30 days.",
365
+ citations: [{
366
+ sourceId: "artifact_1",
367
+ snippet: "Retention policy is 30 days for standard workspaces.",
368
+ filename: "policy.pdf",
369
+ }],
370
+ },
371
+ toolCalls: [],
372
+ })),
373
+ };
374
+
375
+ const searchWorkflow = createWorkflow<{ query: string }>({ name: "cloud-kb-search" })
376
+ .then(createCloudKnowledgeBaseSearchStep({
377
+ id: "kb-search",
378
+ client,
379
+ resolvePipelineId: ({ resourceId }) => `pipeline-${resourceId.userId}`,
380
+ getQuery: (input) => input.query,
381
+ }))
382
+ .build();
383
+
384
+ const answerWorkflow = createWorkflow<{ query: string }>({ name: "cloud-kb-answer" })
385
+ .then(createCloudKnowledgeBaseAnswerStep({
386
+ id: "kb-answer",
387
+ client,
388
+ pipelineId: "pipeline-1",
389
+ agent,
390
+ getQuery: (input) => input.query,
391
+ }))
392
+ .build();
393
+
394
+ const searchResult = await searchWorkflow.execute(
395
+ { query: "retention policy" },
396
+ { resourceId: { userId: "user-1" } },
397
+ );
398
+ const answerResult = await answerWorkflow.execute(
399
+ { query: "retention policy" },
400
+ { resourceId: { userId: "user-1" } },
401
+ );
402
+
403
+ expect(searchResult.output).toEqual(expect.objectContaining({
404
+ pipelineId: "pipeline-user-1",
405
+ citations: expect.arrayContaining([expect.objectContaining({ sourceId: "artifact_1" })]),
406
+ }));
407
+ expect(answerResult.output).toEqual({
408
+ answer: "The retention policy is 30 days.",
409
+ citations: [{
410
+ sourceId: "artifact_1",
411
+ snippet: "Retention policy is 30 days for standard workspaces.",
412
+ filename: "policy.pdf",
413
+ }],
414
+ });
415
+ });
416
+
417
+ it("inherits workflow backend config from execute options and lets explicit step config override it", async () => {
418
+ const inheritedFetch = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
419
+ expect(String(_input)).toBe("https://workflow.example.com/api/cloud/pipelines/pipeline-1/search");
420
+ expect(new Headers(init?.headers).get("authorization")).toBe("Bearer workflow-key");
421
+ return new Response(JSON.stringify({
422
+ results: [makePipelineResult()],
423
+ }), {
424
+ status: 200,
425
+ headers: { "content-type": "application/json" },
426
+ });
427
+ });
428
+ const explicitFetch = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
429
+ expect(String(_input)).toBe("https://explicit.example.com/api/cloud/pipelines/pipeline-2/search");
430
+ expect(new Headers(init?.headers).get("authorization")).toBe("Bearer explicit-key");
431
+ return new Response(JSON.stringify({
432
+ results: [makePipelineResult({ id: "artifact_2" })],
433
+ }), {
434
+ status: 200,
435
+ headers: { "content-type": "application/json" },
436
+ });
437
+ });
438
+
439
+ const inheritedStep = createCloudKnowledgeBaseSearchStep({
440
+ id: "kb-search",
441
+ pipelineId: "pipeline-1",
442
+ fetch: inheritedFetch,
443
+ getQuery: (input: { query: string }) => input.query,
444
+ });
445
+ const explicitStep = createCloudKnowledgeBaseSearchStep({
446
+ id: "kb-search",
447
+ pipelineId: "pipeline-2",
448
+ baseUrl: "https://explicit.example.com",
449
+ apiKey: "explicit-key",
450
+ fetch: explicitFetch,
451
+ getQuery: (input: { query: string }) => input.query,
452
+ });
453
+
454
+ const inheritedResult = await inheritedStep.execute(
455
+ { query: "retention policy" },
456
+ {
457
+ state: {},
458
+ resourceId: { userId: "user-1" },
459
+ abortSignal: new AbortController().signal,
460
+ suspend: async () => {
461
+ throw new Error("suspend should not be called");
462
+ },
463
+ emit: () => {},
464
+ apiKey: "workflow-key",
465
+ baseUrl: "https://workflow.example.com",
466
+ } as any,
467
+ );
468
+ const explicitResult = await explicitStep.execute(
469
+ { query: "retention policy" },
470
+ {
471
+ state: {},
472
+ resourceId: { userId: "user-1" },
473
+ abortSignal: new AbortController().signal,
474
+ suspend: async () => {
475
+ throw new Error("suspend should not be called");
476
+ },
477
+ emit: () => {},
478
+ apiKey: "workflow-key",
479
+ baseUrl: "https://workflow.example.com",
480
+ } as any,
481
+ );
482
+
483
+ expect(inheritedFetch).toHaveBeenCalledTimes(1);
484
+ expect(explicitFetch).toHaveBeenCalledTimes(1);
485
+ expect(inheritedResult).toEqual(expect.objectContaining({
486
+ pipelineId: "pipeline-1",
487
+ }));
488
+ expect(explicitResult).toEqual(expect.objectContaining({
489
+ pipelineId: "pipeline-2",
490
+ hits: [expect.objectContaining({ sourceId: "artifact_2" })],
491
+ }));
492
+ });
493
+ });
@@ -0,0 +1,65 @@
1
+ import type { CloudKnowledgeBaseContextAdapter, CloudKnowledgeBaseContextAdapterOptions } from "./types";
2
+ import { buildSystemBlock, extractLastUserText } from "./shared";
3
+ import { searchCloudKnowledgeBase } from "./search";
4
+ import { createCloudKnowledgeBaseSearchTool } from "./tool";
5
+
6
+ function createInheritedSearchTool(
7
+ options: CloudKnowledgeBaseContextAdapterOptions,
8
+ input: { apiKey?: string; baseUrl?: string },
9
+ ) {
10
+ if (options.autoRegisterSearchTool === false) {
11
+ return null;
12
+ }
13
+
14
+ return createCloudKnowledgeBaseSearchTool({
15
+ ...options,
16
+ ...options.searchTool,
17
+ client: options.searchTool?.client ?? options.client,
18
+ baseUrl: options.searchTool?.baseUrl ?? options.baseUrl ?? input.baseUrl,
19
+ apiKey: options.searchTool?.apiKey ?? options.apiKey ?? input.apiKey,
20
+ fetch: options.searchTool?.fetch ?? options.fetch,
21
+ timeout: options.searchTool?.timeout ?? options.timeout,
22
+ logLevel: options.searchTool?.logLevel ?? options.logLevel,
23
+ pipelineId: options.searchTool?.pipelineId ?? options.pipelineId,
24
+ resolvePipelineId: options.searchTool?.resolvePipelineId ?? options.resolvePipelineId,
25
+ });
26
+ }
27
+
28
+ export function createCloudKnowledgeBaseContextAdapter(
29
+ options: CloudKnowledgeBaseContextAdapterOptions,
30
+ ): CloudKnowledgeBaseContextAdapter {
31
+ return {
32
+ async resolve(input) {
33
+ const resolvedInput = input as typeof input & { apiKey?: string; baseUrl?: string };
34
+ const searchTool = createInheritedSearchTool(options, resolvedInput);
35
+ const query = (options.buildQuery
36
+ ? await options.buildQuery(resolvedInput)
37
+ : extractLastUserText(resolvedInput.messages))
38
+ .trim();
39
+
40
+ if (!query) {
41
+ return searchTool ? { tools: [searchTool] } : {};
42
+ }
43
+
44
+ const search = await searchCloudKnowledgeBase({
45
+ ...options,
46
+ query,
47
+ resourceId: resolvedInput.resourceId,
48
+ messages: resolvedInput.messages,
49
+ metadata: resolvedInput.metadata,
50
+ abortSignal: resolvedInput.abortSignal,
51
+ apiKey: options.apiKey ?? resolvedInput.apiKey,
52
+ baseUrl: options.baseUrl ?? resolvedInput.baseUrl,
53
+ });
54
+
55
+ return {
56
+ systemBlock: buildSystemBlock(search, {
57
+ maxHits: options.maxContextHits,
58
+ maxCharacters: options.maxContextCharacters,
59
+ emptyStateMessage: options.emptyStateMessage,
60
+ }),
61
+ ...(searchTool ? { tools: [searchTool] } : {}),
62
+ };
63
+ },
64
+ };
65
+ }