mdcontext 0.0.1 → 0.2.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.
- package/.changeset/README.md +28 -0
- package/.changeset/config.json +11 -0
- package/.claude/settings.local.json +25 -0
- package/.github/workflows/ci.yml +83 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +85 -0
- package/.github/workflows/release.yml +113 -0
- package/.tldrignore +112 -0
- package/BACKLOG.md +338 -0
- package/CONTRIBUTING.md +186 -0
- package/NOTES/NOTES +44 -0
- package/README.md +434 -11
- package/biome.json +36 -0
- package/cspell.config.yaml +14 -0
- package/dist/chunk-23UPXDNL.js +3044 -0
- package/dist/chunk-2W7MO2DL.js +1366 -0
- package/dist/chunk-3NUAZGMA.js +1689 -0
- package/dist/chunk-7TOWB2XB.js +366 -0
- package/dist/chunk-7XOTOADQ.js +3065 -0
- package/dist/chunk-AH2PDM2K.js +3042 -0
- package/dist/chunk-BNXWSZ63.js +3742 -0
- package/dist/chunk-BTL5DJVU.js +3222 -0
- package/dist/chunk-HDHYG7E4.js +104 -0
- package/dist/chunk-HLR4KZBP.js +3234 -0
- package/dist/chunk-IP3FRFEB.js +1045 -0
- package/dist/chunk-KHU56VDO.js +3042 -0
- package/dist/chunk-KRYIFLQR.js +88 -0
- package/dist/chunk-LBSDNLEM.js +287 -0
- package/dist/chunk-MNTQ7HCP.js +2643 -0
- package/dist/chunk-MUJELQQ6.js +1387 -0
- package/dist/chunk-MXJGMSLV.js +2199 -0
- package/dist/chunk-N6QJGC3Z.js +2636 -0
- package/dist/chunk-OBELGBPM.js +1713 -0
- package/dist/chunk-OT7R5XTA.js +3192 -0
- package/dist/chunk-P7X4RA2T.js +106 -0
- package/dist/chunk-PIDUQNC2.js +3185 -0
- package/dist/chunk-POGCDIH4.js +3187 -0
- package/dist/chunk-PSIEOQGZ.js +3043 -0
- package/dist/chunk-PVRT3IHA.js +3238 -0
- package/dist/chunk-QNN4TT23.js +1430 -0
- package/dist/chunk-RE3R45RJ.js +3042 -0
- package/dist/chunk-S7E6TFX6.js +803 -0
- package/dist/chunk-SG6GLU4U.js +1378 -0
- package/dist/chunk-SJCDV2ST.js +274 -0
- package/dist/chunk-SYE5XLF3.js +104 -0
- package/dist/chunk-T5VLYBZD.js +103 -0
- package/dist/chunk-TOQB7VWU.js +3238 -0
- package/dist/chunk-VFNMZ4ZQ.js +3228 -0
- package/dist/chunk-VVTGZNBT.js +1629 -0
- package/dist/chunk-W7Q4RFEV.js +104 -0
- package/dist/chunk-XTYYVRLO.js +3190 -0
- package/dist/chunk-Y6MDYVJD.js +3063 -0
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +5458 -0
- package/dist/index.d.ts +653 -0
- package/dist/index.js +79 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +472 -0
- package/dist/schema-BAWSG7KY.js +22 -0
- package/dist/schema-E3QUPL26.js +20 -0
- package/dist/schema-EHL7WUT6.js +20 -0
- package/docs/019-USAGE.md +625 -0
- package/docs/020-current-implementation.md +364 -0
- package/docs/021-DOGFOODING-FINDINGS.md +175 -0
- package/docs/BACKLOG.md +80 -0
- package/docs/CONFIG.md +1123 -0
- package/docs/DESIGN.md +439 -0
- package/docs/ERRORS.md +383 -0
- package/docs/PROJECT.md +88 -0
- package/docs/ROADMAP.md +407 -0
- package/docs/summarization.md +320 -0
- package/docs/test-links.md +9 -0
- package/justfile +40 -0
- package/package.json +74 -9
- package/pnpm-workspace.yaml +5 -0
- package/research/INDEX.md +315 -0
- package/research/code-review/README.md +90 -0
- package/research/code-review/cli-error-handling-review.md +979 -0
- package/research/code-review/code-review-validation-report.md +464 -0
- package/research/code-review/main-ts-review.md +1128 -0
- package/research/config-analysis/01-current-implementation.md +470 -0
- package/research/config-analysis/02-strategy-recommendation.md +428 -0
- package/research/config-analysis/03-task-candidates.md +715 -0
- package/research/config-analysis/033-research-configuration-management.md +828 -0
- package/research/config-analysis/034-research-effect-cli-config.md +1504 -0
- package/research/config-analysis/04-consolidated-task-candidates.md +277 -0
- package/research/config-docs/SUMMARY.md +357 -0
- package/research/config-docs/TEST-RESULTS.md +776 -0
- package/research/config-docs/TODO.md +542 -0
- package/research/config-docs/analysis.md +744 -0
- package/research/config-docs/fix-validation.md +502 -0
- package/research/config-docs/help-audit.md +264 -0
- package/research/config-docs/help-system-analysis.md +890 -0
- package/research/dogfood/consolidated-tool-evaluation.md +373 -0
- package/research/dogfood/strategy-a/a-synthesis.md +184 -0
- package/research/dogfood/strategy-a/a1-docs.md +226 -0
- package/research/dogfood/strategy-a/a2-amorphic.md +156 -0
- package/research/dogfood/strategy-a/a3-llm.md +164 -0
- package/research/dogfood/strategy-b/b-synthesis.md +228 -0
- package/research/dogfood/strategy-b/b1-architecture.md +207 -0
- package/research/dogfood/strategy-b/b2-gaps.md +258 -0
- package/research/dogfood/strategy-b/b3-workflows.md +250 -0
- package/research/dogfood/strategy-c/c-synthesis.md +451 -0
- package/research/dogfood/strategy-c/c1-explorer.md +192 -0
- package/research/dogfood/strategy-c/c2-diver-memory.md +145 -0
- package/research/dogfood/strategy-c/c3-diver-control.md +148 -0
- package/research/dogfood/strategy-c/c4-diver-failure.md +151 -0
- package/research/dogfood/strategy-c/c5-diver-execution.md +221 -0
- package/research/dogfood/strategy-c/c6-diver-org.md +221 -0
- package/research/effect-cli-error-handling.md +845 -0
- package/research/effect-errors-as-values.md +943 -0
- package/research/errors-task-analysis/00-consolidated-tasks.md +207 -0
- package/research/errors-task-analysis/cli-commands-analysis.md +909 -0
- package/research/errors-task-analysis/embeddings-analysis.md +709 -0
- package/research/errors-task-analysis/index-search-analysis.md +812 -0
- package/research/frontmatter/COMMENTS-ARE-SKIPPED.md +149 -0
- package/research/frontmatter/LLM-CODE-NAVIGATION.md +276 -0
- package/research/issue-review.md +603 -0
- package/research/llm-summarization/agent-cli-tools-2026.md +1082 -0
- package/research/llm-summarization/alternative-providers-2026.md +1428 -0
- package/research/llm-summarization/anthropic-2026.md +367 -0
- package/research/llm-summarization/claude-cli-integration.md +1706 -0
- package/research/llm-summarization/cli-integration-patterns.md +3155 -0
- package/research/llm-summarization/openai-2026.md +473 -0
- package/research/llm-summarization/openai-compatible-providers-2026.md +1022 -0
- package/research/llm-summarization/opencode-cli-integration.md +1552 -0
- package/research/llm-summarization/prompt-engineering-2026.md +1426 -0
- package/research/llm-summarization/prototype-results.md +56 -0
- package/research/llm-summarization/provider-switching-patterns-2026.md +2153 -0
- package/research/llm-summarization/typescript-llm-libraries-2026.md +2436 -0
- package/research/mdcontext-error-analysis.md +521 -0
- package/research/mdcontext-pudding/00-EXECUTIVE-SUMMARY.md +282 -0
- package/research/mdcontext-pudding/01-index-embed.md +956 -0
- package/research/mdcontext-pudding/02-search-COMMANDS.md +142 -0
- package/research/mdcontext-pudding/02-search-SUMMARY.md +146 -0
- package/research/mdcontext-pudding/02-search.md +970 -0
- package/research/mdcontext-pudding/03-context.md +779 -0
- package/research/mdcontext-pudding/04-navigation-and-analytics.md +803 -0
- package/research/mdcontext-pudding/04-tree.md +704 -0
- package/research/mdcontext-pudding/05-config.md +1038 -0
- package/research/mdcontext-pudding/06-links-summary.txt +87 -0
- package/research/mdcontext-pudding/06-links.md +679 -0
- package/research/mdcontext-pudding/07-stats.md +693 -0
- package/research/mdcontext-pudding/BUG-FIX-PLAN.md +388 -0
- package/research/mdcontext-pudding/P0-BUG-VALIDATION.md +167 -0
- package/research/mdcontext-pudding/README.md +168 -0
- package/research/mdcontext-pudding/TESTING-SUMMARY.md +128 -0
- package/research/npm_publish/011-npm-workflow-research-agent2.md +792 -0
- package/research/npm_publish/012-npm-workflow-research-agent1.md +530 -0
- package/research/npm_publish/013-npm-workflow-research-agent3.md +722 -0
- package/research/npm_publish/014-npm-workflow-synthesis.md +556 -0
- package/research/npm_publish/031-npm-workflow-task-analysis.md +134 -0
- package/research/research-quality-review.md +834 -0
- package/research/semantic-search/002-research-embedding-models.md +490 -0
- package/research/semantic-search/003-research-rag-alternatives.md +523 -0
- package/research/semantic-search/004-research-vector-search.md +841 -0
- package/research/semantic-search/032-research-semantic-search.md +427 -0
- package/research/semantic-search/embedding-text-analysis.md +156 -0
- package/research/semantic-search/multi-word-failure-reproduction.md +171 -0
- package/research/semantic-search/query-processing-analysis.md +207 -0
- package/research/semantic-search/root-cause-and-solution.md +114 -0
- package/research/semantic-search/threshold-validation-report.md +69 -0
- package/research/semantic-search/vector-search-analysis.md +63 -0
- package/research/task-management-2026/00-synthesis-recommendations.md +295 -0
- package/research/task-management-2026/01-ai-workflow-tools.md +416 -0
- package/research/task-management-2026/02-agent-framework-patterns.md +476 -0
- package/research/task-management-2026/03-lightweight-file-based.md +567 -0
- package/research/task-management-2026/04-established-tools-ai-features.md +541 -0
- package/research/task-management-2026/linear/01-core-features-workflow.md +771 -0
- package/research/task-management-2026/linear/02-api-integrations.md +930 -0
- package/research/task-management-2026/linear/03-ai-features.md +368 -0
- package/research/task-management-2026/linear/04-pricing-setup.md +205 -0
- package/research/task-management-2026/linear/05-usage-patterns-best-practices.md +605 -0
- package/research/test-path-issues.md +276 -0
- package/review/ALP-76/1-error-type-design.md +962 -0
- package/review/ALP-76/2-error-handling-patterns.md +906 -0
- package/review/ALP-76/3-error-presentation.md +624 -0
- package/review/ALP-76/4-test-coverage.md +625 -0
- package/review/ALP-76/5-migration-completeness.md +440 -0
- package/review/ALP-76/6-effect-best-practices.md +755 -0
- package/scripts/apply-branch-protection.sh +47 -0
- package/scripts/branch-protection-templates.json +79 -0
- package/scripts/prototype-summarization.ts +346 -0
- package/scripts/rebuild-hnswlib.js +58 -0
- package/scripts/setup-branch-protection.sh +64 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/active-provider.json +7 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.json +541 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/bm25.meta.json +5 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/config.json +8 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/documents.json +60 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/links.json +13 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/.mdcontext/indexes/sections.json +1197 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/configuration-management.md +99 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/distributed-systems.md +92 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/error-handling.md +78 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/failure-automation.md +55 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/job-context.md +69 -0
- package/src/__tests__/fixtures/semantic-search/multi-word-corpus/process-orchestration.md +99 -0
- package/src/cli/argv-preprocessor.test.ts +210 -0
- package/src/cli/argv-preprocessor.ts +202 -0
- package/src/cli/cli.test.ts +627 -0
- package/src/cli/commands/backlinks.ts +54 -0
- package/src/cli/commands/config-cmd.ts +642 -0
- package/src/cli/commands/context.ts +285 -0
- package/src/cli/commands/duplicates.ts +122 -0
- package/src/cli/commands/embeddings.ts +529 -0
- package/src/cli/commands/index-cmd.ts +480 -0
- package/src/cli/commands/index.ts +16 -0
- package/src/cli/commands/links.ts +52 -0
- package/src/cli/commands/search.ts +1281 -0
- package/src/cli/commands/stats.ts +149 -0
- package/src/cli/commands/tree.ts +128 -0
- package/src/cli/config-layer.ts +176 -0
- package/src/cli/error-handler.test.ts +235 -0
- package/src/cli/error-handler.ts +655 -0
- package/src/cli/flag-schemas.ts +341 -0
- package/src/cli/help.ts +588 -0
- package/src/cli/index.ts +9 -0
- package/src/cli/main.ts +435 -0
- package/src/cli/options.ts +41 -0
- package/src/cli/shared-error-handling.ts +199 -0
- package/src/cli/typo-suggester.test.ts +105 -0
- package/src/cli/typo-suggester.ts +130 -0
- package/src/cli/utils.ts +259 -0
- package/src/config/file-provider.test.ts +320 -0
- package/src/config/file-provider.ts +273 -0
- package/src/config/index.ts +72 -0
- package/src/config/integration.test.ts +667 -0
- package/src/config/precedence.test.ts +277 -0
- package/src/config/precedence.ts +451 -0
- package/src/config/schema.test.ts +414 -0
- package/src/config/schema.ts +603 -0
- package/src/config/service.test.ts +320 -0
- package/src/config/service.ts +243 -0
- package/src/config/testing.test.ts +264 -0
- package/src/config/testing.ts +110 -0
- package/src/core/index.ts +1 -0
- package/src/core/types.ts +113 -0
- package/src/duplicates/detector.test.ts +183 -0
- package/src/duplicates/detector.ts +414 -0
- package/src/duplicates/index.ts +18 -0
- package/src/embeddings/embedding-namespace.test.ts +300 -0
- package/src/embeddings/embedding-namespace.ts +947 -0
- package/src/embeddings/heading-boost.test.ts +222 -0
- package/src/embeddings/hnsw-build-options.test.ts +198 -0
- package/src/embeddings/hyde.test.ts +272 -0
- package/src/embeddings/hyde.ts +264 -0
- package/src/embeddings/index.ts +10 -0
- package/src/embeddings/openai-provider.ts +414 -0
- package/src/embeddings/pricing.json +22 -0
- package/src/embeddings/provider-constants.ts +204 -0
- package/src/embeddings/provider-errors.test.ts +967 -0
- package/src/embeddings/provider-errors.ts +565 -0
- package/src/embeddings/provider-factory.test.ts +240 -0
- package/src/embeddings/provider-factory.ts +225 -0
- package/src/embeddings/provider-integration.test.ts +788 -0
- package/src/embeddings/query-preprocessing.test.ts +187 -0
- package/src/embeddings/semantic-search-threshold.test.ts +508 -0
- package/src/embeddings/semantic-search.ts +1270 -0
- package/src/embeddings/types.ts +359 -0
- package/src/embeddings/vector-store.ts +708 -0
- package/src/embeddings/voyage-provider.ts +313 -0
- package/src/errors/errors.test.ts +845 -0
- package/src/errors/index.ts +533 -0
- package/src/index/ignore-patterns.test.ts +354 -0
- package/src/index/ignore-patterns.ts +305 -0
- package/src/index/index.ts +4 -0
- package/src/index/indexer.ts +684 -0
- package/src/index/storage.ts +260 -0
- package/src/index/types.ts +147 -0
- package/src/index/watcher.ts +189 -0
- package/src/index.ts +30 -0
- package/src/integration/search-keyword.test.ts +678 -0
- package/src/mcp/server.ts +612 -0
- package/src/parser/index.ts +1 -0
- package/src/parser/parser.test.ts +291 -0
- package/src/parser/parser.ts +394 -0
- package/src/parser/section-filter.test.ts +277 -0
- package/src/parser/section-filter.ts +392 -0
- package/src/search/__tests__/hybrid-search.test.ts +650 -0
- package/src/search/bm25-store.ts +366 -0
- package/src/search/cross-encoder.test.ts +253 -0
- package/src/search/cross-encoder.ts +406 -0
- package/src/search/fuzzy-search.test.ts +419 -0
- package/src/search/fuzzy-search.ts +273 -0
- package/src/search/hybrid-search.ts +448 -0
- package/src/search/path-matcher.test.ts +276 -0
- package/src/search/path-matcher.ts +33 -0
- package/src/search/query-parser.test.ts +260 -0
- package/src/search/query-parser.ts +319 -0
- package/src/search/searcher.test.ts +280 -0
- package/src/search/searcher.ts +724 -0
- package/src/search/wink-bm25.d.ts +30 -0
- package/src/summarization/cli-providers/claude.ts +202 -0
- package/src/summarization/cli-providers/detection.test.ts +273 -0
- package/src/summarization/cli-providers/detection.ts +118 -0
- package/src/summarization/cli-providers/index.ts +8 -0
- package/src/summarization/cost.test.ts +139 -0
- package/src/summarization/cost.ts +102 -0
- package/src/summarization/error-handler.test.ts +127 -0
- package/src/summarization/error-handler.ts +111 -0
- package/src/summarization/index.ts +102 -0
- package/src/summarization/pipeline.test.ts +498 -0
- package/src/summarization/pipeline.ts +231 -0
- package/src/summarization/prompts.test.ts +269 -0
- package/src/summarization/prompts.ts +133 -0
- package/src/summarization/provider-factory.test.ts +396 -0
- package/src/summarization/provider-factory.ts +178 -0
- package/src/summarization/types.ts +184 -0
- package/src/summarize/budget-bugs.test.ts +620 -0
- package/src/summarize/formatters.ts +419 -0
- package/src/summarize/index.ts +20 -0
- package/src/summarize/summarizer.test.ts +275 -0
- package/src/summarize/summarizer.ts +597 -0
- package/src/summarize/verify-bugs.test.ts +238 -0
- package/src/types/huggingface-transformers.d.ts +66 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/tokens.test.ts +142 -0
- package/src/utils/tokens.ts +186 -0
- package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -0
- package/tests/fixtures/cli/.mdcontext/config.json +8 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/embeddings/openai_text-embedding-3-small_512/vectors.meta.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/indexes/documents.json +33 -0
- package/tests/fixtures/cli/.mdcontext/indexes/links.json +12 -0
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +247 -0
- package/tests/fixtures/cli/README.md +9 -0
- package/tests/fixtures/cli/api-reference.md +11 -0
- package/tests/fixtures/cli/getting-started.md +11 -0
- package/tests/integration/embed-index.test.ts +712 -0
- package/tests/integration/search-context.test.ts +469 -0
- package/tests/integration/search-semantic.test.ts +522 -0
- package/tsconfig.json +26 -0
- package/vitest.config.ts +16 -0
- package/vitest.setup.ts +12 -0
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider Switching Integration Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for provider switching, configuration precedence, and cross-provider
|
|
5
|
+
* compatibility. These tests verify the full integration of embedding providers
|
|
6
|
+
* with the configuration system.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'node:fs'
|
|
10
|
+
import * as os from 'node:os'
|
|
11
|
+
import * as path from 'node:path'
|
|
12
|
+
import { Effect, Option } from 'effect'
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
14
|
+
import {
|
|
15
|
+
createConfigProvider,
|
|
16
|
+
createConfigProviderSync,
|
|
17
|
+
} from '../config/index.js'
|
|
18
|
+
import { MdContextConfig } from '../config/schema.js'
|
|
19
|
+
import {
|
|
20
|
+
createEmbeddingProviderDirect,
|
|
21
|
+
getProviderBaseURL,
|
|
22
|
+
PROVIDER_BASE_URLS,
|
|
23
|
+
} from './provider-factory.js'
|
|
24
|
+
import type { VectorIndex } from './types.js'
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Test Setup
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
describe('Provider Integration Tests', () => {
|
|
31
|
+
let tempDir: string
|
|
32
|
+
const savedEnv: Record<string, string | undefined> = {}
|
|
33
|
+
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mdcontext-provider-int-'))
|
|
36
|
+
|
|
37
|
+
// Save and clear relevant env vars
|
|
38
|
+
const envKeys = [
|
|
39
|
+
'MDCONTEXT_EMBEDDINGS_PROVIDER',
|
|
40
|
+
'MDCONTEXT_EMBEDDINGS_BASEURL',
|
|
41
|
+
'MDCONTEXT_EMBEDDINGS_MODEL',
|
|
42
|
+
'OPENAI_API_KEY',
|
|
43
|
+
'OPENROUTER_API_KEY',
|
|
44
|
+
]
|
|
45
|
+
for (const key of envKeys) {
|
|
46
|
+
savedEnv[key] = process.env[key]
|
|
47
|
+
delete process.env[key]
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
53
|
+
|
|
54
|
+
// Restore env vars
|
|
55
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
56
|
+
if (value !== undefined) {
|
|
57
|
+
process.env[key] = value
|
|
58
|
+
} else {
|
|
59
|
+
delete process.env[key]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
vi.restoreAllMocks()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// ==========================================================================
|
|
66
|
+
// Configuration Precedence Tests
|
|
67
|
+
// ==========================================================================
|
|
68
|
+
|
|
69
|
+
describe('Configuration Precedence (CLI > Env > File > Defaults)', () => {
|
|
70
|
+
it('uses default provider (openai) when nothing specified', async () => {
|
|
71
|
+
const provider = createConfigProviderSync({
|
|
72
|
+
skipConfigFile: true,
|
|
73
|
+
skipEnv: true,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const result = await Effect.runPromise(
|
|
77
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
expect(result.embeddings.provider).toBe('openai')
|
|
81
|
+
expect(result.embeddings.model).toBe('text-embedding-3-small')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
it('config file overrides defaults', async () => {
|
|
85
|
+
const fileConfig = {
|
|
86
|
+
embeddings: { provider: 'ollama', model: 'nomic-embed-text' },
|
|
87
|
+
}
|
|
88
|
+
fs.writeFileSync(
|
|
89
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
90
|
+
JSON.stringify(fileConfig),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const provider = await Effect.runPromise(
|
|
94
|
+
createConfigProvider({
|
|
95
|
+
workingDir: tempDir,
|
|
96
|
+
skipEnv: true,
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
const result = await Effect.runPromise(
|
|
101
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
105
|
+
expect(result.embeddings.model).toBe('nomic-embed-text')
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('environment variable overrides config file', async () => {
|
|
109
|
+
// Config file says openai
|
|
110
|
+
const fileConfig = {
|
|
111
|
+
embeddings: { provider: 'openai', model: 'text-embedding-3-small' },
|
|
112
|
+
}
|
|
113
|
+
fs.writeFileSync(
|
|
114
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
115
|
+
JSON.stringify(fileConfig),
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
// Env says ollama
|
|
119
|
+
process.env.MDCONTEXT_EMBEDDINGS_PROVIDER = 'ollama'
|
|
120
|
+
|
|
121
|
+
const provider = await Effect.runPromise(
|
|
122
|
+
createConfigProvider({
|
|
123
|
+
workingDir: tempDir,
|
|
124
|
+
skipEnv: false,
|
|
125
|
+
}),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
const result = await Effect.runPromise(
|
|
129
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('CLI flag overrides environment variable', async () => {
|
|
136
|
+
// Env says openai
|
|
137
|
+
process.env.MDCONTEXT_EMBEDDINGS_PROVIDER = 'openai'
|
|
138
|
+
|
|
139
|
+
// CLI says ollama
|
|
140
|
+
const provider = createConfigProviderSync({
|
|
141
|
+
skipConfigFile: true,
|
|
142
|
+
skipEnv: false,
|
|
143
|
+
cliOverrides: { embeddings: { provider: 'ollama' } },
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const result = await Effect.runPromise(
|
|
147
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
// CLI wins
|
|
151
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('CLI baseURL overrides provider default', async () => {
|
|
155
|
+
const customURL = 'http://custom:9999/v1'
|
|
156
|
+
|
|
157
|
+
// Note: CLI overrides use plain strings, which get flattened to config keys.
|
|
158
|
+
// The flattenConfig function converts any value to a string, so this works at runtime
|
|
159
|
+
// even though the type expects Option<string>. Using 'as never' for test simplicity.
|
|
160
|
+
const provider = createConfigProviderSync({
|
|
161
|
+
skipConfigFile: true,
|
|
162
|
+
skipEnv: true,
|
|
163
|
+
cliOverrides: {
|
|
164
|
+
embeddings: {
|
|
165
|
+
provider: 'ollama',
|
|
166
|
+
baseURL: customURL as never, // Type workaround for flattened config
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const result = await Effect.runPromise(
|
|
172
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
176
|
+
expect(Option.getOrNull(result.embeddings.baseURL)).toBe(customURL)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('complete precedence chain works correctly', async () => {
|
|
180
|
+
// Config file: provider=openai, model=text-embedding-3-large
|
|
181
|
+
const fileConfig = {
|
|
182
|
+
embeddings: {
|
|
183
|
+
provider: 'openai',
|
|
184
|
+
model: 'text-embedding-3-large',
|
|
185
|
+
batchSize: 50,
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
fs.writeFileSync(
|
|
189
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
190
|
+
JSON.stringify(fileConfig),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
// Env: provider=ollama
|
|
194
|
+
process.env.MDCONTEXT_EMBEDDINGS_PROVIDER = 'ollama'
|
|
195
|
+
|
|
196
|
+
// CLI: provider=openrouter
|
|
197
|
+
const provider = await Effect.runPromise(
|
|
198
|
+
createConfigProvider({
|
|
199
|
+
workingDir: tempDir,
|
|
200
|
+
skipEnv: false,
|
|
201
|
+
cliOverrides: { embeddings: { provider: 'openrouter' } },
|
|
202
|
+
}),
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
const result = await Effect.runPromise(
|
|
206
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
// CLI wins for provider
|
|
210
|
+
expect(result.embeddings.provider).toBe('openrouter')
|
|
211
|
+
// File config used for unoverridden values
|
|
212
|
+
expect(result.embeddings.model).toBe('text-embedding-3-large')
|
|
213
|
+
expect(result.embeddings.batchSize).toBe(50)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// ==========================================================================
|
|
218
|
+
// Provider BaseURL Resolution Tests
|
|
219
|
+
// ==========================================================================
|
|
220
|
+
|
|
221
|
+
describe('Provider BaseURL Resolution', () => {
|
|
222
|
+
it('returns undefined for openai (uses SDK default)', () => {
|
|
223
|
+
const result = getProviderBaseURL('openai', Option.none())
|
|
224
|
+
expect(result).toBeUndefined()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it('returns correct default for ollama', () => {
|
|
228
|
+
const result = getProviderBaseURL('ollama', Option.none())
|
|
229
|
+
expect(result).toBe('http://localhost:11434/v1')
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('returns correct default for lm-studio', () => {
|
|
233
|
+
const result = getProviderBaseURL('lm-studio', Option.none())
|
|
234
|
+
expect(result).toBe('http://localhost:1234/v1')
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
it('returns correct default for openrouter', () => {
|
|
238
|
+
const result = getProviderBaseURL('openrouter', Option.none())
|
|
239
|
+
expect(result).toBe('https://openrouter.ai/api/v1')
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
it('config baseURL overrides provider default', () => {
|
|
243
|
+
const customURL = 'http://custom-ollama:11434/v1'
|
|
244
|
+
const result = getProviderBaseURL('ollama', Option.some(customURL))
|
|
245
|
+
expect(result).toBe(customURL)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('config baseURL works for openai (custom proxy)', () => {
|
|
249
|
+
const proxyURL = 'https://openai-proxy.example.com/v1'
|
|
250
|
+
const result = getProviderBaseURL('openai', Option.some(proxyURL))
|
|
251
|
+
expect(result).toBe(proxyURL)
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// ==========================================================================
|
|
256
|
+
// Provider Factory Tests
|
|
257
|
+
// ==========================================================================
|
|
258
|
+
|
|
259
|
+
describe('Provider Factory', () => {
|
|
260
|
+
it('creates provider with ollama configuration', async () => {
|
|
261
|
+
const program = createEmbeddingProviderDirect({
|
|
262
|
+
provider: 'ollama',
|
|
263
|
+
model: 'nomic-embed-text',
|
|
264
|
+
apiKey: 'dummy-key', // Ollama doesn't require API key but we pass one for testing
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const provider = await Effect.runPromise(program)
|
|
268
|
+
|
|
269
|
+
expect(provider.name).toContain('ollama')
|
|
270
|
+
expect(provider.name).toContain('nomic-embed-text')
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
it('creates provider with lm-studio configuration', async () => {
|
|
274
|
+
const program = createEmbeddingProviderDirect({
|
|
275
|
+
provider: 'lm-studio',
|
|
276
|
+
apiKey: 'dummy-key',
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
const provider = await Effect.runPromise(program)
|
|
280
|
+
|
|
281
|
+
expect(provider.name).toContain('lm-studio')
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
it('creates provider with openrouter configuration', async () => {
|
|
285
|
+
const program = createEmbeddingProviderDirect({
|
|
286
|
+
provider: 'openrouter',
|
|
287
|
+
model: 'text-embedding-3-small',
|
|
288
|
+
apiKey: 'sk-or-test-key',
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
const provider = await Effect.runPromise(program)
|
|
292
|
+
|
|
293
|
+
expect(provider.name).toContain('openrouter')
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
it('creates provider with custom baseURL', async () => {
|
|
297
|
+
const customURL = 'https://custom-api.example.com/v1'
|
|
298
|
+
|
|
299
|
+
const program = createEmbeddingProviderDirect({
|
|
300
|
+
provider: 'openai',
|
|
301
|
+
baseURL: customURL,
|
|
302
|
+
apiKey: 'test-key',
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const provider = await Effect.runPromise(program)
|
|
306
|
+
|
|
307
|
+
expect(provider).toBeDefined()
|
|
308
|
+
expect(provider.name).toContain('openai')
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('accepts baseURL as Option.some', async () => {
|
|
312
|
+
const customURL = 'https://custom-api.example.com/v1'
|
|
313
|
+
|
|
314
|
+
const program = createEmbeddingProviderDirect({
|
|
315
|
+
provider: 'openai',
|
|
316
|
+
baseURL: Option.some(customURL),
|
|
317
|
+
apiKey: 'test-key',
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
const provider = await Effect.runPromise(program)
|
|
321
|
+
expect(provider).toBeDefined()
|
|
322
|
+
})
|
|
323
|
+
|
|
324
|
+
it('uses provider default when baseURL is Option.none', async () => {
|
|
325
|
+
const program = createEmbeddingProviderDirect({
|
|
326
|
+
provider: 'ollama',
|
|
327
|
+
baseURL: Option.none(),
|
|
328
|
+
apiKey: 'test-key',
|
|
329
|
+
})
|
|
330
|
+
|
|
331
|
+
const provider = await Effect.runPromise(program)
|
|
332
|
+
expect(provider.name).toContain('ollama')
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
// ==========================================================================
|
|
337
|
+
// Provider Metadata Tests
|
|
338
|
+
// ==========================================================================
|
|
339
|
+
|
|
340
|
+
describe('Provider Metadata in Index', () => {
|
|
341
|
+
it('VectorIndex type includes provider fields', () => {
|
|
342
|
+
const index: VectorIndex = {
|
|
343
|
+
version: 1,
|
|
344
|
+
provider: 'ollama',
|
|
345
|
+
providerModel: 'nomic-embed-text',
|
|
346
|
+
providerBaseURL: 'http://localhost:11434/v1',
|
|
347
|
+
dimensions: 768,
|
|
348
|
+
entries: {},
|
|
349
|
+
totalCost: 0,
|
|
350
|
+
totalTokens: 0,
|
|
351
|
+
createdAt: new Date().toISOString(),
|
|
352
|
+
updatedAt: new Date().toISOString(),
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
expect(index.provider).toBe('ollama')
|
|
356
|
+
expect(index.providerModel).toBe('nomic-embed-text')
|
|
357
|
+
expect(index.providerBaseURL).toBe('http://localhost:11434/v1')
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
it('VectorIndex supports optional provider fields', () => {
|
|
361
|
+
const index: VectorIndex = {
|
|
362
|
+
version: 1,
|
|
363
|
+
provider: 'openai',
|
|
364
|
+
dimensions: 512,
|
|
365
|
+
entries: {},
|
|
366
|
+
totalCost: 0.005,
|
|
367
|
+
totalTokens: 10000,
|
|
368
|
+
createdAt: new Date().toISOString(),
|
|
369
|
+
updatedAt: new Date().toISOString(),
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
expect(index.provider).toBe('openai')
|
|
373
|
+
expect(index.providerModel).toBeUndefined()
|
|
374
|
+
expect(index.providerBaseURL).toBeUndefined()
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
it('simulates reading index metadata for provider mismatch detection', () => {
|
|
378
|
+
// Simulate index created with Ollama
|
|
379
|
+
const indexMeta: VectorIndex = {
|
|
380
|
+
version: 1,
|
|
381
|
+
provider: 'ollama',
|
|
382
|
+
providerModel: 'nomic-embed-text',
|
|
383
|
+
dimensions: 768,
|
|
384
|
+
entries: {},
|
|
385
|
+
totalCost: 0,
|
|
386
|
+
totalTokens: 1000,
|
|
387
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
388
|
+
updatedAt: '2024-01-01T00:00:00Z',
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Function to check provider mismatch
|
|
392
|
+
const checkProviderMismatch = (
|
|
393
|
+
indexProvider: string,
|
|
394
|
+
queryProvider: string,
|
|
395
|
+
): boolean => {
|
|
396
|
+
return indexProvider !== queryProvider
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Querying with different provider should warn
|
|
400
|
+
expect(checkProviderMismatch(indexMeta.provider, 'openai')).toBe(true)
|
|
401
|
+
expect(checkProviderMismatch(indexMeta.provider, 'ollama')).toBe(false)
|
|
402
|
+
})
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
// ==========================================================================
|
|
406
|
+
// All Provider Types Test
|
|
407
|
+
// ==========================================================================
|
|
408
|
+
|
|
409
|
+
describe('All Provider Types', () => {
|
|
410
|
+
const providers = [
|
|
411
|
+
'openai',
|
|
412
|
+
'ollama',
|
|
413
|
+
'lm-studio',
|
|
414
|
+
'openrouter',
|
|
415
|
+
'voyage',
|
|
416
|
+
] as const
|
|
417
|
+
|
|
418
|
+
for (const providerType of providers) {
|
|
419
|
+
it(`${providerType} provider can be created with factory`, async () => {
|
|
420
|
+
const program = createEmbeddingProviderDirect({
|
|
421
|
+
provider: providerType,
|
|
422
|
+
apiKey: 'test-key',
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
const provider = await Effect.runPromise(program)
|
|
426
|
+
|
|
427
|
+
expect(provider).toBeDefined()
|
|
428
|
+
expect(provider.name).toContain(providerType)
|
|
429
|
+
expect(typeof provider.dimensions).toBe('number')
|
|
430
|
+
expect(typeof provider.embed).toBe('function')
|
|
431
|
+
})
|
|
432
|
+
|
|
433
|
+
it(`${providerType} has correct default baseURL`, () => {
|
|
434
|
+
const expectedURL = PROVIDER_BASE_URLS[providerType]
|
|
435
|
+
const actualURL = getProviderBaseURL(providerType, Option.none())
|
|
436
|
+
|
|
437
|
+
expect(actualURL).toBe(expectedURL)
|
|
438
|
+
})
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
// ==========================================================================
|
|
443
|
+
// Config File Provider Selection Tests
|
|
444
|
+
// ==========================================================================
|
|
445
|
+
|
|
446
|
+
describe('Config File Provider Selection', () => {
|
|
447
|
+
it('supports provider: "ollama" in config file', async () => {
|
|
448
|
+
const fileConfig = {
|
|
449
|
+
embeddings: { provider: 'ollama' },
|
|
450
|
+
}
|
|
451
|
+
fs.writeFileSync(
|
|
452
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
453
|
+
JSON.stringify(fileConfig),
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
const provider = await Effect.runPromise(
|
|
457
|
+
createConfigProvider({
|
|
458
|
+
workingDir: tempDir,
|
|
459
|
+
skipEnv: true,
|
|
460
|
+
}),
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
const result = await Effect.runPromise(
|
|
464
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
it('supports provider: "lm-studio" in config file', async () => {
|
|
471
|
+
const fileConfig = {
|
|
472
|
+
embeddings: { provider: 'lm-studio' },
|
|
473
|
+
}
|
|
474
|
+
fs.writeFileSync(
|
|
475
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
476
|
+
JSON.stringify(fileConfig),
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
const provider = await Effect.runPromise(
|
|
480
|
+
createConfigProvider({
|
|
481
|
+
workingDir: tempDir,
|
|
482
|
+
skipEnv: true,
|
|
483
|
+
}),
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
const result = await Effect.runPromise(
|
|
487
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
expect(result.embeddings.provider).toBe('lm-studio')
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
it('supports provider: "openrouter" in config file', async () => {
|
|
494
|
+
const fileConfig = {
|
|
495
|
+
embeddings: { provider: 'openrouter' },
|
|
496
|
+
}
|
|
497
|
+
fs.writeFileSync(
|
|
498
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
499
|
+
JSON.stringify(fileConfig),
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
const provider = await Effect.runPromise(
|
|
503
|
+
createConfigProvider({
|
|
504
|
+
workingDir: tempDir,
|
|
505
|
+
skipEnv: true,
|
|
506
|
+
}),
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
const result = await Effect.runPromise(
|
|
510
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
expect(result.embeddings.provider).toBe('openrouter')
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
it('supports custom baseURL in config file', async () => {
|
|
517
|
+
const customURL = 'http://custom:8080/v1'
|
|
518
|
+
const fileConfig = {
|
|
519
|
+
embeddings: {
|
|
520
|
+
provider: 'ollama',
|
|
521
|
+
baseURL: customURL,
|
|
522
|
+
},
|
|
523
|
+
}
|
|
524
|
+
fs.writeFileSync(
|
|
525
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
526
|
+
JSON.stringify(fileConfig),
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
const provider = await Effect.runPromise(
|
|
530
|
+
createConfigProvider({
|
|
531
|
+
workingDir: tempDir,
|
|
532
|
+
skipEnv: true,
|
|
533
|
+
}),
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
const result = await Effect.runPromise(
|
|
537
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
541
|
+
expect(Option.getOrNull(result.embeddings.baseURL)).toBe(customURL)
|
|
542
|
+
})
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
// ==========================================================================
|
|
546
|
+
// Environment Variable Tests
|
|
547
|
+
// ==========================================================================
|
|
548
|
+
|
|
549
|
+
describe('Environment Variable Provider Selection', () => {
|
|
550
|
+
it('MDCONTEXT_EMBEDDINGS_PROVIDER=ollama works', async () => {
|
|
551
|
+
process.env.MDCONTEXT_EMBEDDINGS_PROVIDER = 'ollama'
|
|
552
|
+
|
|
553
|
+
const provider = createConfigProviderSync({
|
|
554
|
+
skipConfigFile: true,
|
|
555
|
+
skipEnv: false,
|
|
556
|
+
})
|
|
557
|
+
|
|
558
|
+
const result = await Effect.runPromise(
|
|
559
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('MDCONTEXT_EMBEDDINGS_PROVIDER=lm-studio works', async () => {
|
|
566
|
+
process.env.MDCONTEXT_EMBEDDINGS_PROVIDER = 'lm-studio'
|
|
567
|
+
|
|
568
|
+
const provider = createConfigProviderSync({
|
|
569
|
+
skipConfigFile: true,
|
|
570
|
+
skipEnv: false,
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
const result = await Effect.runPromise(
|
|
574
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
expect(result.embeddings.provider).toBe('lm-studio')
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
it('MDCONTEXT_EMBEDDINGS_PROVIDER=openrouter works', async () => {
|
|
581
|
+
process.env.MDCONTEXT_EMBEDDINGS_PROVIDER = 'openrouter'
|
|
582
|
+
|
|
583
|
+
const provider = createConfigProviderSync({
|
|
584
|
+
skipConfigFile: true,
|
|
585
|
+
skipEnv: false,
|
|
586
|
+
})
|
|
587
|
+
|
|
588
|
+
const result = await Effect.runPromise(
|
|
589
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
expect(result.embeddings.provider).toBe('openrouter')
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
it('MDCONTEXT_EMBEDDINGS_MODEL works', async () => {
|
|
596
|
+
process.env.MDCONTEXT_EMBEDDINGS_PROVIDER = 'ollama'
|
|
597
|
+
process.env.MDCONTEXT_EMBEDDINGS_MODEL = 'mxbai-embed-large'
|
|
598
|
+
|
|
599
|
+
const provider = createConfigProviderSync({
|
|
600
|
+
skipConfigFile: true,
|
|
601
|
+
skipEnv: false,
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
const result = await Effect.runPromise(
|
|
605
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
609
|
+
expect(result.embeddings.model).toBe('mxbai-embed-large')
|
|
610
|
+
})
|
|
611
|
+
})
|
|
612
|
+
|
|
613
|
+
// ==========================================================================
|
|
614
|
+
// Provider Switching Scenarios
|
|
615
|
+
// ==========================================================================
|
|
616
|
+
|
|
617
|
+
describe('Provider Switching Scenarios', () => {
|
|
618
|
+
it('simulates switching from OpenAI to Ollama config', async () => {
|
|
619
|
+
// Start with OpenAI config
|
|
620
|
+
const openaiConfig = {
|
|
621
|
+
embeddings: {
|
|
622
|
+
provider: 'openai',
|
|
623
|
+
model: 'text-embedding-3-small',
|
|
624
|
+
},
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
fs.writeFileSync(
|
|
628
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
629
|
+
JSON.stringify(openaiConfig),
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
let provider = await Effect.runPromise(
|
|
633
|
+
createConfigProvider({ workingDir: tempDir, skipEnv: true }),
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
let result = await Effect.runPromise(
|
|
637
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
638
|
+
)
|
|
639
|
+
|
|
640
|
+
expect(result.embeddings.provider).toBe('openai')
|
|
641
|
+
|
|
642
|
+
// Switch to Ollama config
|
|
643
|
+
const ollamaConfig = {
|
|
644
|
+
embeddings: {
|
|
645
|
+
provider: 'ollama',
|
|
646
|
+
model: 'nomic-embed-text',
|
|
647
|
+
},
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
fs.writeFileSync(
|
|
651
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
652
|
+
JSON.stringify(ollamaConfig),
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
provider = await Effect.runPromise(
|
|
656
|
+
createConfigProvider({ workingDir: tempDir, skipEnv: true }),
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
result = await Effect.runPromise(
|
|
660
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
664
|
+
expect(result.embeddings.model).toBe('nomic-embed-text')
|
|
665
|
+
})
|
|
666
|
+
|
|
667
|
+
it('simulates temporary CLI override without changing config', async () => {
|
|
668
|
+
// Persistent config uses OpenAI
|
|
669
|
+
const fileConfig = {
|
|
670
|
+
embeddings: {
|
|
671
|
+
provider: 'openai',
|
|
672
|
+
model: 'text-embedding-3-small',
|
|
673
|
+
},
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
fs.writeFileSync(
|
|
677
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
678
|
+
JSON.stringify(fileConfig),
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
// One-off CLI override to use Ollama
|
|
682
|
+
const provider = await Effect.runPromise(
|
|
683
|
+
createConfigProvider({
|
|
684
|
+
workingDir: tempDir,
|
|
685
|
+
skipEnv: true,
|
|
686
|
+
cliOverrides: {
|
|
687
|
+
embeddings: {
|
|
688
|
+
provider: 'ollama',
|
|
689
|
+
model: 'nomic-embed-text',
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
}),
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
const result = await Effect.runPromise(
|
|
696
|
+
MdContextConfig.pipe(Effect.withConfigProvider(provider)),
|
|
697
|
+
)
|
|
698
|
+
|
|
699
|
+
// CLI override active
|
|
700
|
+
expect(result.embeddings.provider).toBe('ollama')
|
|
701
|
+
expect(result.embeddings.model).toBe('nomic-embed-text')
|
|
702
|
+
|
|
703
|
+
// Verify config file unchanged
|
|
704
|
+
const fileContent = fs.readFileSync(
|
|
705
|
+
path.join(tempDir, 'mdcontext.config.json'),
|
|
706
|
+
'utf-8',
|
|
707
|
+
)
|
|
708
|
+
const savedConfig = JSON.parse(fileContent)
|
|
709
|
+
expect(savedConfig.embeddings.provider).toBe('openai')
|
|
710
|
+
})
|
|
711
|
+
})
|
|
712
|
+
|
|
713
|
+
// ==========================================================================
|
|
714
|
+
// Cross-Provider Compatibility Tests
|
|
715
|
+
// ==========================================================================
|
|
716
|
+
|
|
717
|
+
describe('Cross-Provider Compatibility', () => {
|
|
718
|
+
it('detects provider mismatch between index and query config', () => {
|
|
719
|
+
// Simulate checking index metadata against query config
|
|
720
|
+
const indexMetadata: Pick<
|
|
721
|
+
VectorIndex,
|
|
722
|
+
'provider' | 'providerModel' | 'dimensions'
|
|
723
|
+
> = {
|
|
724
|
+
provider: 'ollama',
|
|
725
|
+
providerModel: 'nomic-embed-text',
|
|
726
|
+
dimensions: 768,
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
const queryConfig = {
|
|
730
|
+
provider: 'openai' as const,
|
|
731
|
+
model: 'text-embedding-3-small',
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Check for mismatch
|
|
735
|
+
const isMismatch = indexMetadata.provider !== queryConfig.provider
|
|
736
|
+
|
|
737
|
+
expect(isMismatch).toBe(true)
|
|
738
|
+
|
|
739
|
+
// Generate warning message
|
|
740
|
+
const warningMessage = `Index was created with ${indexMetadata.provider} (${indexMetadata.providerModel}), but querying with ${queryConfig.provider} (${queryConfig.model}). Results may be inconsistent. Consider re-indexing.`
|
|
741
|
+
|
|
742
|
+
expect(warningMessage).toContain('Index was created with ollama')
|
|
743
|
+
expect(warningMessage).toContain('querying with openai')
|
|
744
|
+
expect(warningMessage).toContain('re-indexing')
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
it('no warning when provider matches', () => {
|
|
748
|
+
const indexMetadata = {
|
|
749
|
+
provider: 'openai',
|
|
750
|
+
providerModel: 'text-embedding-3-small',
|
|
751
|
+
dimensions: 512,
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const queryConfig = {
|
|
755
|
+
provider: 'openai' as const,
|
|
756
|
+
model: 'text-embedding-3-small',
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const isMismatch = indexMetadata.provider !== queryConfig.provider
|
|
760
|
+
|
|
761
|
+
expect(isMismatch).toBe(false)
|
|
762
|
+
})
|
|
763
|
+
|
|
764
|
+
it('different models on same provider should still warn', () => {
|
|
765
|
+
const indexMetadata = {
|
|
766
|
+
provider: 'openai',
|
|
767
|
+
providerModel: 'text-embedding-3-small',
|
|
768
|
+
dimensions: 512,
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const queryConfig = {
|
|
772
|
+
provider: 'openai' as const,
|
|
773
|
+
model: 'text-embedding-3-large', // Different model
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Provider matches but model differs
|
|
777
|
+
const providerMatch = indexMetadata.provider === queryConfig.provider
|
|
778
|
+
const modelMatch = indexMetadata.providerModel === queryConfig.model
|
|
779
|
+
|
|
780
|
+
expect(providerMatch).toBe(true)
|
|
781
|
+
expect(modelMatch).toBe(false)
|
|
782
|
+
|
|
783
|
+
// Should still warn about model mismatch
|
|
784
|
+
const shouldWarn = !modelMatch
|
|
785
|
+
expect(shouldWarn).toBe(true)
|
|
786
|
+
})
|
|
787
|
+
})
|
|
788
|
+
})
|