onyx-kb-mcp 1.0.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.
Files changed (45) hide show
  1. package/LICENSE +1 -0
  2. package/README.md +166 -0
  3. package/build/__tests__/config/config.test.d.ts +1 -0
  4. package/build/__tests__/config/config.test.js +56 -0
  5. package/build/__tests__/config/config.test.js.map +1 -0
  6. package/build/__tests__/index.test.d.ts +1 -0
  7. package/build/__tests__/index.test.js +25 -0
  8. package/build/__tests__/index.test.js.map +1 -0
  9. package/build/__tests__/integration/server.test.d.ts +1 -0
  10. package/build/__tests__/integration/server.test.js +73 -0
  11. package/build/__tests__/integration/server.test.js.map +1 -0
  12. package/build/__tests__/services/onyxApi.test.d.ts +1 -0
  13. package/build/__tests__/services/onyxApi.test.js +317 -0
  14. package/build/__tests__/services/onyxApi.test.js.map +1 -0
  15. package/build/__tests__/tools/tools.test.d.ts +1 -0
  16. package/build/__tests__/tools/tools.test.js +177 -0
  17. package/build/__tests__/tools/tools.test.js.map +1 -0
  18. package/build/config/index.d.ts +20 -0
  19. package/build/config/index.js +29 -0
  20. package/build/config/index.js.map +1 -0
  21. package/build/index.d.ts +2 -0
  22. package/build/index.js +24 -0
  23. package/build/index.js.map +1 -0
  24. package/build/server.d.ts +21 -0
  25. package/build/server.js +85 -0
  26. package/build/server.js.map +1 -0
  27. package/build/services/onyxApi.d.ts +82 -0
  28. package/build/services/onyxApi.js +367 -0
  29. package/build/services/onyxApi.js.map +1 -0
  30. package/build/tools/chatTool.d.ts +25 -0
  31. package/build/tools/chatTool.js +84 -0
  32. package/build/tools/chatTool.js.map +1 -0
  33. package/build/tools/index.d.ts +7 -0
  34. package/build/tools/index.js +8 -0
  35. package/build/tools/index.js.map +1 -0
  36. package/build/tools/searchTool.d.ts +20 -0
  37. package/build/tools/searchTool.js +55 -0
  38. package/build/tools/searchTool.js.map +1 -0
  39. package/build/tools/summarizeTool.d.ts +20 -0
  40. package/build/tools/summarizeTool.js +106 -0
  41. package/build/tools/summarizeTool.js.map +1 -0
  42. package/build/types/index.d.ts +198 -0
  43. package/build/types/index.js +111 -0
  44. package/build/types/index.js.map +1 -0
  45. package/package.json +85 -0
package/LICENSE ADDED
@@ -0,0 +1 @@
1
+ MIT License
package/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # onyx-kb-mcp
2
+
3
+ A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that provides seamless integration with [Onyx](https://github.com/onyx-dot-app/onyx) AI knowledge bases. Enable any MCP-compatible client (Claude Desktop, Claude Code, Cline, etc.) to search, query, and summarize your organization's knowledge base.
4
+
5
+ [![npm version](https://badge.fury.io/js/onyx-kb-mcp.svg)](https://www.npmjs.com/package/onyx-kb-mcp)
6
+
7
+ ## Features
8
+
9
+ - **Semantic Search** (`search_arq`) - Search your Onyx document sets with configurable context retrieval
10
+ - **Q&A with RAG** (`ask_arq`) - Get comprehensive answers with sources using Onyx's LLM + RAG capabilities
11
+ - **Executive Summaries** (`summarize_arq`) - Generate concise bullet-point summaries on any topic
12
+
13
+ ## Quick Start
14
+
15
+ ### Prerequisites
16
+
17
+ - Node.js v16 or higher
18
+ - An Onyx instance with API access
19
+ - An Onyx API token
20
+
21
+ ### Installation
22
+
23
+ ```bash
24
+ npm install -g onyx-kb-mcp
25
+ ```
26
+
27
+ Or use directly with npx (no installation required):
28
+
29
+ ```bash
30
+ npx onyx-kb-mcp
31
+ ```
32
+
33
+ ## MCP Client Configuration
34
+
35
+ ### Claude Desktop
36
+
37
+ Add to your Claude Desktop config file (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
38
+
39
+ ```json
40
+ {
41
+ "mcpServers": {
42
+ "onyx": {
43
+ "command": "npx",
44
+ "args": ["-y", "onyx-kb-mcp"],
45
+ "env": {
46
+ "ONYX_API_TOKEN": "your-api-token-here",
47
+ "ONYX_API_URL": "http://localhost:8080/api"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### Claude Code
55
+
56
+ Add to your Claude Code MCP settings (`~/.claude/settings.json`):
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "onyx": {
62
+ "command": "npx",
63
+ "args": ["-y", "onyx-kb-mcp"],
64
+ "env": {
65
+ "ONYX_API_TOKEN": "your-api-token-here",
66
+ "ONYX_API_URL": "http://localhost:8080/api"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ ### Smithery
74
+
75
+ This server is compatible with [Smithery](https://smithery.ai/). See `smithery.yaml` for configuration details.
76
+
77
+ ## Environment Variables
78
+
79
+ | Variable | Required | Default | Description |
80
+ |----------|----------|---------|-------------|
81
+ | `ONYX_API_TOKEN` | Yes | - | Your Onyx API authentication token |
82
+ | `ONYX_API_URL` | No | `http://localhost:8080/api` | URL of your Onyx API endpoint |
83
+ | `DEFAULT_PERSONA_ID` | No | `15` | Default persona ID for chat interactions |
84
+ | `DEBUG` | No | `false` | Enable debug logging |
85
+
86
+ ## Available Tools
87
+
88
+ ### search_arq
89
+
90
+ Search your knowledge base for relevant documents with configurable context retrieval.
91
+
92
+ | Parameter | Type | Required | Default | Description |
93
+ |-----------|------|----------|---------|-------------|
94
+ | `query` | string | Yes | - | Search query |
95
+ | `documentSets` | string[] | No | all | Document sets to search |
96
+ | `maxResults` | integer | No | 10 | Maximum results (1-20) |
97
+ | `chunksAbove` | integer | No | 1 | Context chunks above match |
98
+ | `chunksBelow` | integer | No | 1 | Context chunks below match |
99
+ | `retrieveFullDocuments` | boolean | No | false | Return full documents |
100
+
101
+ ### ask_arq
102
+
103
+ Ask questions and receive comprehensive answers with source citations.
104
+
105
+ | Parameter | Type | Required | Default | Description |
106
+ |-----------|------|----------|---------|-------------|
107
+ | `query` | string | Yes | - | Question to ask |
108
+ | `personaId` | integer | No | 15 | Persona ID to use |
109
+ | `personaName` | string | No | - | Persona name (alternative to ID) |
110
+ | `documentSets` | string[] | No | all | Document sets to search |
111
+ | `chatSessionId` | string | No | - | Session ID for conversation continuity |
112
+ | `enableAutoDetectFilters` | boolean | No | true | Auto-detect search filters |
113
+
114
+ ### summarize_arq
115
+
116
+ Generate executive summaries with bullet points and source links.
117
+
118
+ | Parameter | Type | Required | Default | Description |
119
+ |-----------|------|----------|---------|-------------|
120
+ | `topic` | string | Yes | - | Topic to summarize |
121
+ | `maxPoints` | integer | No | 5 | Max bullet points (3-10) |
122
+
123
+ ## Usage Examples
124
+
125
+ Once configured, you can use natural language in your MCP client:
126
+
127
+ ```
128
+ search arq for authentication documentation
129
+
130
+ ask arq: how does our deployment process work?
131
+
132
+ ask arq using "Engineering Assistant": explain the API architecture
133
+
134
+ summarize arq onboarding process in 5 points
135
+ ```
136
+
137
+ ## Development
138
+
139
+ ```bash
140
+ # Clone the repository
141
+ git clone https://github.com/arqitech/onyx-kb-mcp.git
142
+ cd onyx-kb-mcp
143
+
144
+ # Install dependencies
145
+ npm install
146
+
147
+ # Build
148
+ npm run build
149
+
150
+ # Run in development mode
151
+ npm run dev
152
+
153
+ # Run tests
154
+ npm test
155
+
156
+ # Lint code
157
+ npm run lint
158
+ ```
159
+
160
+ ## License
161
+
162
+ MIT License - see [LICENSE](./LICENSE) for details.
163
+
164
+ ## Author
165
+
166
+ [Arqitech](https://arqitech.com)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Unit tests for the config module
3
+ */
4
+ import { loadConfig, SERVER_CONFIG, DEBUG } from '../../config/index.js';
5
+ // Save original environment
6
+ const originalEnv = process.env;
7
+ beforeEach(() => {
8
+ // Reset environment variables before each test
9
+ process.env = { ...originalEnv };
10
+ delete process.env.ONYX_API_TOKEN;
11
+ delete process.env.ONYX_API_URL;
12
+ delete process.env.DEBUG;
13
+ });
14
+ afterAll(() => {
15
+ // Restore original environment
16
+ process.env = originalEnv;
17
+ });
18
+ describe('Config Module', () => {
19
+ describe('loadConfig', () => {
20
+ it('should load default config when no environment variables are set', () => {
21
+ const config = loadConfig();
22
+ expect(config).toEqual({
23
+ apiUrl: 'http://localhost:8080/api',
24
+ apiToken: '',
25
+ });
26
+ });
27
+ it('should use environment variables when set', () => {
28
+ process.env.ONYX_API_TOKEN = 'test-token';
29
+ process.env.ONYX_API_URL = 'http://test-url.com/api';
30
+ const config = loadConfig();
31
+ expect(config).toEqual({
32
+ apiUrl: 'http://test-url.com/api',
33
+ apiToken: 'test-token',
34
+ });
35
+ });
36
+ });
37
+ describe('SERVER_CONFIG', () => {
38
+ it('should have the correct server configuration', () => {
39
+ expect(SERVER_CONFIG).toHaveProperty('name', 'onyx-mcp-server');
40
+ expect(SERVER_CONFIG).toHaveProperty('version', '1.0.0');
41
+ });
42
+ });
43
+ describe('DEBUG', () => {
44
+ it('should be false by default', () => {
45
+ expect(DEBUG).toBe(false);
46
+ });
47
+ it('should be true when DEBUG environment variable is set to "true"', () => {
48
+ // Set the DEBUG environment variable
49
+ process.env.DEBUG = 'true';
50
+ // Re-import the module to get the updated value
51
+ // Note: This is a simplified test since we can't easily reset modules in Jest ESM
52
+ expect(process.env.DEBUG).toBe('true');
53
+ });
54
+ });
55
+ });
56
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../../src/__tests__/config/config.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEzE,4BAA4B;AAC5B,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC;AAEhC,UAAU,CAAC,GAAG,EAAE;IACd,+CAA+C;IAC/C,OAAO,CAAC,GAAG,GAAG,EAAE,GAAG,WAAW,EAAE,CAAC;IACjC,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAChC,OAAO,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC;AAC3B,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,GAAG,EAAE;IACZ,+BAA+B;IAC/B,OAAO,CAAC,GAAG,GAAG,WAAW,CAAC;AAC5B,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;YAC1E,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;YAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,MAAM,EAAE,2BAA2B;gBACnC,QAAQ,EAAE,EAAE;aACb,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,GAAG,EAAE;YACnD,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,YAAY,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,yBAAyB,CAAC;YAErD,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;YAC5B,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;gBACrB,MAAM,EAAE,yBAAyB;gBACjC,QAAQ,EAAE,YAAY;aACvB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;YACtD,MAAM,CAAC,aAAa,CAAC,CAAC,cAAc,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;YAChE,MAAM,CAAC,aAAa,CAAC,CAAC,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAC3D,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,OAAO,EAAE,GAAG,EAAE;QACrB,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;YACpC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,iEAAiE,EAAE,GAAG,EAAE;YACzE,qCAAqC;YACrC,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,MAAM,CAAC;YAE3B,gDAAgD;YAChD,kFAAkF;YAClF,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Unit tests for the main entry point
3
+ */
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ // ES modules equivalent of __dirname
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ describe('Main Entry Point', () => {
11
+ it('should have a main function that starts the server', async () => {
12
+ // Simple test to verify the file exists and has the expected content
13
+ const indexPath = path.resolve(__dirname, '../index.ts');
14
+ const fileExists = fs.existsSync(indexPath);
15
+ expect(fileExists).toBe(true);
16
+ // Read the file content
17
+ const content = fs.readFileSync(indexPath, 'utf8');
18
+ // Verify it contains the expected functions and imports
19
+ expect(content).toContain('async function main()');
20
+ expect(content).toContain('import { StdioServerTransport }');
21
+ expect(content).toContain('import { OnyxMcpServer }');
22
+ expect(content).toContain('main().catch(console.error)');
23
+ });
24
+ });
25
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/__tests__/index.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,qCAAqC;AACrC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,qEAAqE;QACrE,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACzD,MAAM,UAAU,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;QAC5C,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE9B,wBAAwB;QACxB,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAEnD,wDAAwD;QACxD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,uBAAuB,CAAC,CAAC;QACnD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;QAC7D,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;QACtD,MAAM,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Integration tests for the Onyx MCP Server
3
+ */
4
+ import { OnyxMcpServer } from '../../server.js';
5
+ import nock from 'nock';
6
+ describe('OnyxMcpServer Integration', () => {
7
+ let server;
8
+ beforeEach(() => {
9
+ // Set up environment variables
10
+ process.env.ONYX_API_TOKEN = 'test-token';
11
+ process.env.ONYX_API_URL = 'http://test-api.com/api';
12
+ // Mock all HTTP requests
13
+ nock.disableNetConnect();
14
+ // Create server and transport
15
+ server = new OnyxMcpServer();
16
+ });
17
+ afterEach(() => {
18
+ nock.cleanAll();
19
+ nock.enableNetConnect();
20
+ delete process.env.ONYX_API_TOKEN;
21
+ delete process.env.ONYX_API_URL;
22
+ });
23
+ it('should initialize and run the server', async () => {
24
+ // Run the server with the mock transport
25
+ // We're just testing that the server can be created without errors
26
+ expect(server).toBeDefined();
27
+ // Verify that the server has the expected properties
28
+ expect(server).toHaveProperty('server');
29
+ });
30
+ it('should handle list tools request', async () => {
31
+ // Run the server with the mock transport
32
+ // We're just testing that the server can be created without errors
33
+ expect(server).toBeDefined();
34
+ // Verify that the server has the expected properties
35
+ expect(server).toHaveProperty('server');
36
+ });
37
+ it('should handle call tool request for search_onyx', async () => {
38
+ // Set up API mocks
39
+ nock('http://test-api.com')
40
+ .post('/api/admin/search')
41
+ .reply(200, { documents: [] });
42
+ // Run the server
43
+ // We're just testing that the server can be created without errors
44
+ expect(server).toBeDefined();
45
+ // Verify that the server has the expected properties
46
+ expect(server).toHaveProperty('server');
47
+ });
48
+ it('should handle call tool request for chat_with_onyx', async () => {
49
+ // Set up API mocks
50
+ nock('http://test-api.com')
51
+ .post('/api/chat/create-chat-session')
52
+ .reply(200, { chat_session_id: 'test-session-id' });
53
+ nock('http://test-api.com')
54
+ .post('/api/chat/send-message')
55
+ .reply(200, JSON.stringify({
56
+ answer: 'Test answer',
57
+ documents: []
58
+ }));
59
+ // Run the server
60
+ // We're just testing that the server can be created without errors
61
+ expect(server).toBeDefined();
62
+ // Verify that the server has the expected properties
63
+ expect(server).toHaveProperty('server');
64
+ });
65
+ it('should handle call tool request for unknown tool', async () => {
66
+ // Run the server
67
+ // We're just testing that the server can be created without errors
68
+ expect(server).toBeDefined();
69
+ // Verify that the server has the expected properties
70
+ expect(server).toHaveProperty('server');
71
+ });
72
+ });
73
+ //# sourceMappingURL=server.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../../src/__tests__/integration/server.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,317 @@
1
+ /**
2
+ * Unit tests for the OnyxApiService
3
+ */
4
+ import nock from 'nock';
5
+ import { OnyxApiService } from '../../services/onyxApi.js';
6
+ // Mock config for testing
7
+ const mockConfig = {
8
+ apiUrl: 'http://test-api.com/api',
9
+ apiToken: 'test-token',
10
+ };
11
+ describe('OnyxApiService', () => {
12
+ let onyxApiService;
13
+ beforeEach(() => {
14
+ // Create service instance
15
+ onyxApiService = new OnyxApiService(mockConfig);
16
+ // Disable real HTTP requests
17
+ nock.disableNetConnect();
18
+ });
19
+ afterEach(() => {
20
+ // Clean up nock after each test
21
+ nock.cleanAll();
22
+ nock.enableNetConnect();
23
+ });
24
+ describe('searchOnyx', () => {
25
+ it('should make a POST request to the search endpoint with correct parameters', async () => {
26
+ // Mock the search API response
27
+ const mockResponse = {
28
+ documents: [
29
+ {
30
+ document_id: 'doc1',
31
+ chunk_ind: 1,
32
+ semantic_identifier: 'Test Document',
33
+ blurb: 'Test blurb',
34
+ source_type: 'test',
35
+ score: 0.95,
36
+ },
37
+ ],
38
+ };
39
+ // Set up the mock
40
+ nock('http://test-api.com')
41
+ .post('/api/admin/search')
42
+ .reply(200, mockResponse);
43
+ // Call the method
44
+ const result = await onyxApiService.searchOnyx('test query', [], 1, 2);
45
+ // Verify the result
46
+ expect(result).toEqual(mockResponse.documents);
47
+ });
48
+ it('should include document sets in the request when provided', async () => {
49
+ // Mock the search API response
50
+ const mockResponse = {
51
+ documents: [
52
+ {
53
+ document_id: 'doc1',
54
+ chunk_ind: 1,
55
+ semantic_identifier: 'Test Document',
56
+ blurb: 'Test blurb',
57
+ source_type: 'test',
58
+ score: 0.95,
59
+ },
60
+ ],
61
+ };
62
+ // Set up the mock
63
+ nock('http://test-api.com')
64
+ .post('/api/admin/search')
65
+ .reply(200, mockResponse);
66
+ // Call the method with document sets
67
+ const result = await onyxApiService.searchOnyx('test query', ['Test Set']);
68
+ // Verify the result
69
+ expect(result).toEqual(mockResponse.documents);
70
+ });
71
+ it('should throw an error when the API request fails', async () => {
72
+ // Set up the mock to return an error
73
+ nock('http://test-api.com')
74
+ .post('/api/admin/search')
75
+ .reply(500, { error: 'Internal server error' });
76
+ // Call the method and expect it to throw
77
+ await expect(onyxApiService.searchOnyx('test query')).rejects.toThrow('Failed to search Onyx');
78
+ });
79
+ });
80
+ describe('fetchDocumentChunk', () => {
81
+ it('should make a GET request to the chunk-info endpoint with correct parameters', async () => {
82
+ // Mock the chunk-info API response
83
+ const mockResponse = {
84
+ content: 'Test chunk content',
85
+ num_tokens: 10,
86
+ };
87
+ // Set up the mock
88
+ nock('http://test-api.com')
89
+ .get('/api/document/chunk-info')
90
+ .query({
91
+ document_id: 'doc1',
92
+ chunk_id: 1,
93
+ })
94
+ .reply(200, mockResponse);
95
+ // Call the method
96
+ const result = await onyxApiService.fetchDocumentChunk('doc1', 1);
97
+ // Verify the result
98
+ expect(result).toEqual(mockResponse.content);
99
+ });
100
+ it('should return empty string when the API request fails', async () => {
101
+ // Set up the mock to return an error
102
+ nock('http://test-api.com')
103
+ .get('/api/document/chunk-info')
104
+ .query({
105
+ document_id: 'doc1',
106
+ chunk_id: 1,
107
+ })
108
+ .reply(500, { error: 'Internal server error' });
109
+ // Call the method
110
+ const result = await onyxApiService.fetchDocumentChunk('doc1', 1);
111
+ // Verify the result
112
+ expect(result).toEqual('');
113
+ });
114
+ });
115
+ describe('fetchDocumentContent', () => {
116
+ it('should fetch document size info and then all chunks', async () => {
117
+ // Mock the document-size-info API response
118
+ const mockSizeResponse = {
119
+ num_chunks: 2,
120
+ total_tokens: 20
121
+ };
122
+ // Mock the chunk-info API responses
123
+ const mockChunk1Response = {
124
+ content: 'Chunk 1 content',
125
+ num_tokens: 10
126
+ };
127
+ const mockChunk2Response = {
128
+ content: 'Chunk 2 content',
129
+ num_tokens: 10
130
+ };
131
+ // Set up the mocks
132
+ nock('http://test-api.com')
133
+ .get('/api/document/document-size-info')
134
+ .query({
135
+ document_id: 'doc1'
136
+ })
137
+ .reply(200, mockSizeResponse);
138
+ nock('http://test-api.com')
139
+ .get('/api/document/chunk-info')
140
+ .query({
141
+ document_id: 'doc1',
142
+ chunk_id: 0
143
+ })
144
+ .reply(200, mockChunk1Response);
145
+ nock('http://test-api.com')
146
+ .get('/api/document/chunk-info')
147
+ .query({
148
+ document_id: 'doc1',
149
+ chunk_id: 1
150
+ })
151
+ .reply(200, mockChunk2Response);
152
+ // Call the method
153
+ const result = await onyxApiService.fetchDocumentContent('doc1');
154
+ // Verify the result
155
+ expect(result).toContain(mockChunk1Response.content);
156
+ expect(result).toContain(mockChunk2Response.content);
157
+ });
158
+ it('should return empty string when the API request fails', async () => {
159
+ // Set up the mock to return an error
160
+ nock('http://test-api.com')
161
+ .get('/api/document/document-size-info')
162
+ .query({
163
+ document_id: 'doc1'
164
+ })
165
+ .reply(500, { error: 'Internal server error' });
166
+ // Call the method
167
+ const result = await onyxApiService.fetchDocumentContent('doc1');
168
+ // Verify the result
169
+ expect(result).toEqual('');
170
+ });
171
+ });
172
+ describe('createChatSession', () => {
173
+ it('should make a POST request to create a chat session', async () => {
174
+ // Mock the create-chat-session API response
175
+ const mockResponse = {
176
+ chat_session_id: 'test-session-id'
177
+ };
178
+ // Set up the mock
179
+ nock('http://test-api.com')
180
+ .post('/api/chat/create-chat-session')
181
+ .reply(200, mockResponse);
182
+ // Call the method
183
+ const result = await onyxApiService.createChatSession(15);
184
+ // Verify the result
185
+ expect(result).toEqual(mockResponse.chat_session_id);
186
+ });
187
+ it('should throw an error when the API request fails', async () => {
188
+ // Set up the mock to return an error
189
+ nock('http://test-api.com')
190
+ .post('/api/chat/create-chat-session')
191
+ .reply(500, { error: 'Internal server error' });
192
+ // Call the method and expect it to throw
193
+ await expect(onyxApiService.createChatSession(15)).rejects.toThrow('Failed to create chat session');
194
+ });
195
+ });
196
+ describe('sendChatMessage', () => {
197
+ it('should make a POST request to send a chat message and parse JSON response', async () => {
198
+ // Mock the send-message API response (single JSON)
199
+ const mockResponse = JSON.stringify({
200
+ answer: 'Test answer',
201
+ documents: [
202
+ { document_id: 'doc1', semantic_identifier: 'Test Document 1' },
203
+ { document_id: 'doc2', semantic_identifier: 'Test Document 2' }
204
+ ]
205
+ });
206
+ // Set up the mock
207
+ nock('http://test-api.com')
208
+ .post('/api/chat/send-message')
209
+ .reply(200, mockResponse);
210
+ // Call the method
211
+ const result = await onyxApiService.sendChatMessage('test-session-id', 'test query');
212
+ // Verify the result
213
+ expect(result.answer).toEqual('Test answer');
214
+ expect(result.documents).toHaveLength(2);
215
+ expect(result.documents[0].document_id).toEqual('doc1');
216
+ });
217
+ it('should handle JSON lines format response', async () => {
218
+ // Mock the send-message API response (JSON lines)
219
+ const mockResponse = '{"answer_piece":"Part 1 of answer"}\n' +
220
+ '{"answer_piece":"Part 2 of answer"}\n' +
221
+ '{"top_documents":[{"document_id":"doc1","semantic_identifier":"Test Document 1"}]}';
222
+ // Set up the mock
223
+ nock('http://test-api.com')
224
+ .post('/api/chat/send-message')
225
+ .reply(200, mockResponse);
226
+ // Call the method
227
+ const result = await onyxApiService.sendChatMessage('test-session-id', 'test query');
228
+ // Verify the result
229
+ expect(result.answer).toEqual('Part 1 of answerPart 2 of answer');
230
+ expect(result.documents).toHaveLength(1);
231
+ expect(result.documents[0].document_id).toEqual('doc1');
232
+ });
233
+ it('should throw an error when the API request fails', async () => {
234
+ // Set up the mock to return an error
235
+ nock('http://test-api.com')
236
+ .post('/api/chat/send-message')
237
+ .reply(500, { error: 'Internal server error' });
238
+ // Call the method and expect it to throw
239
+ await expect(onyxApiService.sendChatMessage('test-session-id', 'test query')).rejects.toThrow('Error chatting with Onyx');
240
+ });
241
+ });
242
+ describe('buildContext', () => {
243
+ it('should build a formatted context string from search results and contents', () => {
244
+ // Test data
245
+ const results = [
246
+ {
247
+ document_id: 'doc1',
248
+ chunk_ind: 1,
249
+ semantic_identifier: 'Test Document 1',
250
+ score: 0.95,
251
+ link: 'http://example.com/doc1',
252
+ blurb: 'Test blurb 1',
253
+ source_type: 'test'
254
+ },
255
+ {
256
+ document_id: 'doc2',
257
+ chunk_ind: 2,
258
+ semantic_identifier: 'Test Document 2',
259
+ score: 0.85,
260
+ link: undefined,
261
+ blurb: 'Test blurb 2',
262
+ source_type: 'test'
263
+ }
264
+ ];
265
+ const contents = [
266
+ 'Content for document 1',
267
+ 'Content for document 2'
268
+ ];
269
+ // Call the method
270
+ const result = onyxApiService.buildContext(results, contents);
271
+ // Verify the result
272
+ expect(result).toContain('# Test Document 1');
273
+ expect(result).toContain('Source: http://example.com/doc1');
274
+ expect(result).toContain('Relevance Score: 0.95');
275
+ expect(result).toContain('Content for document 1');
276
+ expect(result).toContain('# Test Document 2');
277
+ expect(result).toContain('Source: doc2');
278
+ expect(result).toContain('Relevance Score: 0.85');
279
+ expect(result).toContain('Content for document 2');
280
+ });
281
+ it('should skip results with no content', () => {
282
+ // Test data
283
+ const results = [
284
+ {
285
+ document_id: 'doc1',
286
+ chunk_ind: 1,
287
+ semantic_identifier: 'Test Document 1',
288
+ score: 0.95,
289
+ link: 'http://example.com/doc1',
290
+ blurb: 'Test blurb 1',
291
+ source_type: 'test'
292
+ },
293
+ {
294
+ document_id: 'doc2',
295
+ chunk_ind: 2,
296
+ semantic_identifier: 'Test Document 2',
297
+ score: 0.85,
298
+ link: 'http://example.com/doc2',
299
+ blurb: 'Test blurb 2',
300
+ source_type: 'test'
301
+ }
302
+ ];
303
+ const contents = [
304
+ 'Content for document 1',
305
+ '' // Empty content for second document
306
+ ];
307
+ // Call the method
308
+ const result = onyxApiService.buildContext(results, contents);
309
+ // Verify the result
310
+ expect(result).toContain('# Test Document 1');
311
+ expect(result).toContain('Content for document 1');
312
+ // Should not contain document 2 info
313
+ expect(result).not.toContain('# Test Document 2');
314
+ });
315
+ });
316
+ });
317
+ //# sourceMappingURL=onyxApi.test.js.map