mdcontext 0.1.0 → 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/config.json +9 -9
- package/.claude/settings.local.json +25 -0
- package/.github/workflows/claude-code-review.yml +44 -0
- package/.github/workflows/claude.yml +85 -0
- package/CONTRIBUTING.md +186 -0
- package/NOTES/NOTES +44 -0
- package/README.md +206 -3
- package/biome.json +1 -1
- 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 +85 -89
- 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 +718 -657
- 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 +1533 -1423
- 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.js +4072 -629
- package/dist/index.d.ts +420 -33
- package/dist/index.js +8 -15
- package/dist/mcp/server.js +103 -7
- 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 +44 -5
- package/docs/020-current-implementation.md +8 -8
- package/docs/021-DOGFOODING-FINDINGS.md +1 -1
- package/docs/CONFIG.md +1123 -0
- package/docs/ERRORS.md +383 -0
- package/docs/summarization.md +320 -0
- package/justfile +40 -0
- package/package.json +39 -33
- 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-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/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-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/research-quality-review.md +834 -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/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 +32 -37
- 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 +2 -2
- package/src/cli/cli.test.ts +230 -33
- package/src/cli/commands/config-cmd.ts +642 -0
- package/src/cli/commands/context.ts +97 -9
- package/src/cli/commands/duplicates.ts +122 -0
- package/src/cli/commands/embeddings.ts +529 -0
- package/src/cli/commands/index-cmd.ts +210 -30
- package/src/cli/commands/index.ts +3 -0
- package/src/cli/commands/search.ts +894 -64
- package/src/cli/commands/stats.ts +3 -0
- package/src/cli/commands/tree.ts +26 -5
- 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 +66 -0
- package/src/cli/help.ts +209 -7
- package/src/cli/main.ts +348 -58
- package/src/cli/options.ts +10 -0
- package/src/cli/shared-error-handling.ts +199 -0
- package/src/cli/utils.ts +150 -17
- 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/types.ts +6 -33
- 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 +2 -0
- package/src/embeddings/openai-provider.ts +332 -83
- 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 +780 -93
- package/src/embeddings/types.ts +293 -16
- package/src/embeddings/vector-store.ts +486 -77
- 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/indexer.ts +286 -48
- package/src/index/storage.ts +94 -30
- package/src/index/types.ts +40 -2
- package/src/index/watcher.ts +67 -9
- package/src/index.ts +22 -0
- package/src/integration/search-keyword.test.ts +678 -0
- package/src/mcp/server.ts +135 -6
- package/src/parser/parser.ts +18 -19
- package/src/parser/section-filter.test.ts +277 -0
- package/src/parser/section-filter.ts +125 -3
- 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/searcher.test.ts +99 -1
- package/src/search/searcher.ts +189 -67
- 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/summarizer.ts +104 -35
- package/src/types/huggingface-transformers.d.ts +66 -0
- package/tests/fixtures/cli/.mdcontext/active-provider.json +7 -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 +4 -4
- package/tests/fixtures/cli/.mdcontext/indexes/sections.json +14 -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/vitest.config.ts +1 -6
- package/AGENTS.md +0 -46
- package/tests/fixtures/cli/.mdcontext/vectors.bin +0 -0
- package/tests/fixtures/cli/.mdcontext/vectors.meta.json +0 -1264
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider-Specific Error Detection and Handling
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities to detect and classify errors from different
|
|
5
|
+
* embedding providers (Ollama, LM Studio, OpenRouter) and transform them into
|
|
6
|
+
* user-friendly error messages with actionable suggestions.
|
|
7
|
+
*
|
|
8
|
+
* ## Supported Providers
|
|
9
|
+
*
|
|
10
|
+
* - **Ollama**: Local daemon-based embedding (port 11434)
|
|
11
|
+
* - **LM Studio**: GUI-based local embedding (port 1234)
|
|
12
|
+
* - **OpenRouter**: API gateway for multiple providers
|
|
13
|
+
*
|
|
14
|
+
* ## Usage
|
|
15
|
+
*
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { detectProviderError, getProviderSuggestions } from './provider-errors.js'
|
|
18
|
+
*
|
|
19
|
+
* try {
|
|
20
|
+
* await client.embeddings.create(...)
|
|
21
|
+
* } catch (error) {
|
|
22
|
+
* const providerError = detectProviderError('ollama', error)
|
|
23
|
+
* if (providerError) {
|
|
24
|
+
* console.log(providerError.userMessage)
|
|
25
|
+
* console.log(getProviderSuggestions(providerError))
|
|
26
|
+
* }
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { EmbeddingProvider } from '../config/schema.js'
|
|
32
|
+
import { PROVIDER_PORTS } from './provider-constants.js'
|
|
33
|
+
|
|
34
|
+
// ============================================================================
|
|
35
|
+
// Provider Error Types
|
|
36
|
+
// ============================================================================
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Types of provider-specific errors
|
|
40
|
+
*/
|
|
41
|
+
export type ProviderErrorType =
|
|
42
|
+
// Connection errors (local providers)
|
|
43
|
+
| 'daemon-not-running'
|
|
44
|
+
| 'gui-not-running'
|
|
45
|
+
| 'connection-refused'
|
|
46
|
+
| 'connection-timeout'
|
|
47
|
+
// Model errors
|
|
48
|
+
| 'model-not-found'
|
|
49
|
+
| 'model-loading'
|
|
50
|
+
| 'model-not-ready'
|
|
51
|
+
// API errors (remote providers)
|
|
52
|
+
| 'invalid-api-key'
|
|
53
|
+
| 'rate-limited'
|
|
54
|
+
| 'quota-exceeded'
|
|
55
|
+
| 'model-unavailable'
|
|
56
|
+
// Generic
|
|
57
|
+
| 'network-error'
|
|
58
|
+
| 'unknown'
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Structured provider error with context for user-friendly display
|
|
62
|
+
*/
|
|
63
|
+
export interface ProviderError {
|
|
64
|
+
readonly type: ProviderErrorType
|
|
65
|
+
readonly provider: EmbeddingProvider
|
|
66
|
+
readonly message: string
|
|
67
|
+
readonly model?: string | undefined
|
|
68
|
+
readonly originalError: unknown
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Port Detection
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Detect which provider an error is from based on port number.
|
|
77
|
+
* Uses PROVIDER_PORTS from provider-constants.ts as single source of truth.
|
|
78
|
+
*/
|
|
79
|
+
const detectProviderFromPort = (
|
|
80
|
+
error: Error,
|
|
81
|
+
): EmbeddingProvider | undefined => {
|
|
82
|
+
const message = error.message
|
|
83
|
+
for (const [provider, port] of Object.entries(PROVIDER_PORTS)) {
|
|
84
|
+
if (port && message.includes(String(port))) {
|
|
85
|
+
return provider as EmbeddingProvider
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// Ollama Error Detection
|
|
93
|
+
// ============================================================================
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Detect Ollama-specific errors
|
|
97
|
+
*/
|
|
98
|
+
const detectOllamaError = (error: unknown): ProviderError | null => {
|
|
99
|
+
if (!(error instanceof Error)) return null
|
|
100
|
+
|
|
101
|
+
const message = error.message.toLowerCase()
|
|
102
|
+
|
|
103
|
+
// Connection refused - daemon not running
|
|
104
|
+
if (
|
|
105
|
+
message.includes('econnrefused') ||
|
|
106
|
+
message.includes('connect econnrefused') ||
|
|
107
|
+
message.includes('connection refused')
|
|
108
|
+
) {
|
|
109
|
+
// Check if it's Ollama's port
|
|
110
|
+
if (
|
|
111
|
+
error.message.includes('11434') ||
|
|
112
|
+
error.message.includes('localhost:11434')
|
|
113
|
+
) {
|
|
114
|
+
return {
|
|
115
|
+
type: 'daemon-not-running',
|
|
116
|
+
provider: 'ollama',
|
|
117
|
+
message: 'Ollama daemon is not running',
|
|
118
|
+
originalError: error,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Model not found
|
|
124
|
+
if (
|
|
125
|
+
message.includes('model') &&
|
|
126
|
+
(message.includes('not found') || message.includes('does not exist'))
|
|
127
|
+
) {
|
|
128
|
+
const model = extractModelName(error.message)
|
|
129
|
+
return {
|
|
130
|
+
type: 'model-not-found',
|
|
131
|
+
provider: 'ollama',
|
|
132
|
+
message: model
|
|
133
|
+
? `Model '${model}' is not installed in Ollama`
|
|
134
|
+
: 'Requested model is not installed in Ollama',
|
|
135
|
+
model,
|
|
136
|
+
originalError: error,
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Model loading/not ready
|
|
141
|
+
if (
|
|
142
|
+
message.includes('not ready') ||
|
|
143
|
+
message.includes('loading') ||
|
|
144
|
+
message.includes('initializing')
|
|
145
|
+
) {
|
|
146
|
+
const model = extractModelName(error.message)
|
|
147
|
+
return {
|
|
148
|
+
type: 'model-loading',
|
|
149
|
+
provider: 'ollama',
|
|
150
|
+
message: model
|
|
151
|
+
? `Model '${model}' is still loading`
|
|
152
|
+
: 'Model is still loading',
|
|
153
|
+
model,
|
|
154
|
+
originalError: error,
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ============================================================================
|
|
162
|
+
// LM Studio Error Detection
|
|
163
|
+
// ============================================================================
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Detect LM Studio-specific errors
|
|
167
|
+
*/
|
|
168
|
+
const detectLMStudioError = (error: unknown): ProviderError | null => {
|
|
169
|
+
if (!(error instanceof Error)) return null
|
|
170
|
+
|
|
171
|
+
const message = error.message.toLowerCase()
|
|
172
|
+
|
|
173
|
+
// Connection refused - GUI not running
|
|
174
|
+
if (
|
|
175
|
+
message.includes('econnrefused') ||
|
|
176
|
+
message.includes('connect econnrefused') ||
|
|
177
|
+
message.includes('connection refused')
|
|
178
|
+
) {
|
|
179
|
+
if (
|
|
180
|
+
error.message.includes('1234') ||
|
|
181
|
+
error.message.includes('localhost:1234')
|
|
182
|
+
) {
|
|
183
|
+
return {
|
|
184
|
+
type: 'gui-not-running',
|
|
185
|
+
provider: 'lm-studio',
|
|
186
|
+
message: 'LM Studio is not running or local server is not started',
|
|
187
|
+
originalError: error,
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Model not loaded
|
|
193
|
+
if (
|
|
194
|
+
message.includes('no model') ||
|
|
195
|
+
message.includes('model not loaded') ||
|
|
196
|
+
message.includes('select a model')
|
|
197
|
+
) {
|
|
198
|
+
return {
|
|
199
|
+
type: 'model-not-found',
|
|
200
|
+
provider: 'lm-studio',
|
|
201
|
+
message: 'No embedding model is loaded in LM Studio',
|
|
202
|
+
originalError: error,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ============================================================================
|
|
210
|
+
// OpenRouter Error Detection
|
|
211
|
+
// ============================================================================
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Detect OpenRouter-specific errors
|
|
215
|
+
*/
|
|
216
|
+
const detectOpenRouterError = (error: unknown): ProviderError | null => {
|
|
217
|
+
if (!(error instanceof Error)) return null
|
|
218
|
+
|
|
219
|
+
const message = error.message.toLowerCase()
|
|
220
|
+
|
|
221
|
+
// Invalid API key (401 Unauthorized)
|
|
222
|
+
if (
|
|
223
|
+
message.includes('401') ||
|
|
224
|
+
message.includes('unauthorized') ||
|
|
225
|
+
message.includes('invalid api key') ||
|
|
226
|
+
message.includes('invalid_api_key')
|
|
227
|
+
) {
|
|
228
|
+
return {
|
|
229
|
+
type: 'invalid-api-key',
|
|
230
|
+
provider: 'openrouter',
|
|
231
|
+
message: 'Invalid or missing OpenRouter API key',
|
|
232
|
+
originalError: error,
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Rate limiting (429)
|
|
237
|
+
if (message.includes('429') || message.includes('rate limit')) {
|
|
238
|
+
return {
|
|
239
|
+
type: 'rate-limited',
|
|
240
|
+
provider: 'openrouter',
|
|
241
|
+
message: 'OpenRouter rate limit reached',
|
|
242
|
+
originalError: error,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Quota exceeded
|
|
247
|
+
if (message.includes('quota') || message.includes('insufficient')) {
|
|
248
|
+
return {
|
|
249
|
+
type: 'quota-exceeded',
|
|
250
|
+
provider: 'openrouter',
|
|
251
|
+
message: 'OpenRouter quota exceeded',
|
|
252
|
+
originalError: error,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Model unavailable
|
|
257
|
+
if (
|
|
258
|
+
message.includes('model') &&
|
|
259
|
+
(message.includes('not available') ||
|
|
260
|
+
message.includes('not found') ||
|
|
261
|
+
message.includes('not supported'))
|
|
262
|
+
) {
|
|
263
|
+
const model = extractModelName(error.message)
|
|
264
|
+
return {
|
|
265
|
+
type: 'model-unavailable',
|
|
266
|
+
provider: 'openrouter',
|
|
267
|
+
message: model
|
|
268
|
+
? `Model '${model}' is not available via OpenRouter`
|
|
269
|
+
: 'Requested model is not available via OpenRouter',
|
|
270
|
+
model,
|
|
271
|
+
originalError: error,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return null
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ============================================================================
|
|
279
|
+
// Generic Network Error Detection
|
|
280
|
+
// ============================================================================
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Detect generic network errors
|
|
284
|
+
*/
|
|
285
|
+
const detectNetworkError = (
|
|
286
|
+
provider: EmbeddingProvider,
|
|
287
|
+
error: unknown,
|
|
288
|
+
): ProviderError | null => {
|
|
289
|
+
if (!(error instanceof Error)) return null
|
|
290
|
+
|
|
291
|
+
const message = error.message.toLowerCase()
|
|
292
|
+
|
|
293
|
+
// Connection errors
|
|
294
|
+
if (
|
|
295
|
+
message.includes('econnrefused') ||
|
|
296
|
+
message.includes('connection refused')
|
|
297
|
+
) {
|
|
298
|
+
return {
|
|
299
|
+
type: 'connection-refused',
|
|
300
|
+
provider,
|
|
301
|
+
message: `Cannot connect to ${provider} server`,
|
|
302
|
+
originalError: error,
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Timeout
|
|
307
|
+
if (
|
|
308
|
+
message.includes('timeout') ||
|
|
309
|
+
message.includes('etimedout') ||
|
|
310
|
+
message.includes('timed out')
|
|
311
|
+
) {
|
|
312
|
+
return {
|
|
313
|
+
type: 'connection-timeout',
|
|
314
|
+
provider,
|
|
315
|
+
message: `Connection to ${provider} timed out`,
|
|
316
|
+
originalError: error,
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// DNS/network errors
|
|
321
|
+
if (
|
|
322
|
+
message.includes('enotfound') ||
|
|
323
|
+
message.includes('dns') ||
|
|
324
|
+
message.includes('network')
|
|
325
|
+
) {
|
|
326
|
+
return {
|
|
327
|
+
type: 'network-error',
|
|
328
|
+
provider,
|
|
329
|
+
message: `Network error connecting to ${provider}`,
|
|
330
|
+
originalError: error,
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return null
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// ============================================================================
|
|
338
|
+
// Model Name Extraction
|
|
339
|
+
// ============================================================================
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Extract model name from error message using common patterns
|
|
343
|
+
*/
|
|
344
|
+
const extractModelName = (message: string): string | undefined => {
|
|
345
|
+
// Pattern: "model 'name'" or "model \"name\""
|
|
346
|
+
const quotedMatch = message.match(/model\s+['"]([^'"]+)['"]/i)
|
|
347
|
+
if (quotedMatch) return quotedMatch[1]
|
|
348
|
+
|
|
349
|
+
// Pattern: "model: name" or "model name"
|
|
350
|
+
const colonMatch = message.match(/model[:\s]+(\S+)/i)
|
|
351
|
+
if (colonMatch) return colonMatch[1]
|
|
352
|
+
|
|
353
|
+
return undefined
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ============================================================================
|
|
357
|
+
// Main Detection Function
|
|
358
|
+
// ============================================================================
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Detect and classify a provider-specific error.
|
|
362
|
+
*
|
|
363
|
+
* @param provider - The embedding provider being used
|
|
364
|
+
* @param error - The error to analyze
|
|
365
|
+
* @returns Structured ProviderError or null if not a recognized provider error
|
|
366
|
+
*/
|
|
367
|
+
export const detectProviderError = (
|
|
368
|
+
provider: EmbeddingProvider,
|
|
369
|
+
error: unknown,
|
|
370
|
+
): ProviderError | null => {
|
|
371
|
+
// Try provider-specific detection first
|
|
372
|
+
switch (provider) {
|
|
373
|
+
case 'ollama':
|
|
374
|
+
return detectOllamaError(error) ?? detectNetworkError(provider, error)
|
|
375
|
+
case 'lm-studio':
|
|
376
|
+
return detectLMStudioError(error) ?? detectNetworkError(provider, error)
|
|
377
|
+
case 'openrouter':
|
|
378
|
+
return detectOpenRouterError(error) ?? detectNetworkError(provider, error)
|
|
379
|
+
case 'openai':
|
|
380
|
+
// OpenAI errors are handled by the SDK, fall through to network check
|
|
381
|
+
return detectNetworkError(provider, error)
|
|
382
|
+
default:
|
|
383
|
+
return detectNetworkError(provider, error)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Auto-detect provider from error (for cases where provider context is lost)
|
|
389
|
+
*/
|
|
390
|
+
export const detectProviderFromError = (
|
|
391
|
+
error: unknown,
|
|
392
|
+
): EmbeddingProvider | undefined => {
|
|
393
|
+
if (error instanceof Error) {
|
|
394
|
+
return detectProviderFromPort(error)
|
|
395
|
+
}
|
|
396
|
+
return undefined
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ============================================================================
|
|
400
|
+
// Suggestions
|
|
401
|
+
// ============================================================================
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Recommended Ollama embedding models
|
|
405
|
+
*/
|
|
406
|
+
export const RECOMMENDED_OLLAMA_MODELS = [
|
|
407
|
+
{ name: 'nomic-embed-text', dims: 768, note: 'recommended, fast' },
|
|
408
|
+
{ name: 'mxbai-embed-large', dims: 1024, note: 'higher quality' },
|
|
409
|
+
{ name: 'bge-m3', dims: 1024, note: 'multilingual' },
|
|
410
|
+
] as const
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get actionable suggestions for a provider error
|
|
414
|
+
*/
|
|
415
|
+
export const getProviderSuggestions = (error: ProviderError): string[] => {
|
|
416
|
+
switch (error.type) {
|
|
417
|
+
// Ollama errors
|
|
418
|
+
case 'daemon-not-running':
|
|
419
|
+
return [
|
|
420
|
+
'Start the Ollama daemon: ollama serve',
|
|
421
|
+
'Install Ollama: https://ollama.com/download',
|
|
422
|
+
]
|
|
423
|
+
|
|
424
|
+
// LM Studio errors
|
|
425
|
+
case 'gui-not-running':
|
|
426
|
+
return [
|
|
427
|
+
'Open LM Studio application',
|
|
428
|
+
'Go to Developer tab and start the local server',
|
|
429
|
+
'Ensure an embedding model is loaded',
|
|
430
|
+
'Note: LM Studio requires GUI - consider Ollama for automation',
|
|
431
|
+
]
|
|
432
|
+
|
|
433
|
+
// Model errors
|
|
434
|
+
case 'model-not-found':
|
|
435
|
+
if (error.provider === 'ollama') {
|
|
436
|
+
const suggestions = [
|
|
437
|
+
error.model
|
|
438
|
+
? `Download the model: ollama pull ${error.model}`
|
|
439
|
+
: 'Download an embedding model',
|
|
440
|
+
'Recommended embedding models:',
|
|
441
|
+
]
|
|
442
|
+
for (const model of RECOMMENDED_OLLAMA_MODELS) {
|
|
443
|
+
suggestions.push(
|
|
444
|
+
` - ${model.name} (${model.dims} dims, ${model.note})`,
|
|
445
|
+
)
|
|
446
|
+
}
|
|
447
|
+
return suggestions
|
|
448
|
+
}
|
|
449
|
+
if (error.provider === 'lm-studio') {
|
|
450
|
+
return [
|
|
451
|
+
'Load an embedding model in LM Studio',
|
|
452
|
+
'Go to Models tab and download an embedding model',
|
|
453
|
+
'Then load it in the Home tab',
|
|
454
|
+
]
|
|
455
|
+
}
|
|
456
|
+
if (error.provider === 'openrouter') {
|
|
457
|
+
return [
|
|
458
|
+
'Check available models: https://openrouter.ai/models',
|
|
459
|
+
'Common embedding models: text-embedding-3-small, text-embedding-3-large',
|
|
460
|
+
]
|
|
461
|
+
}
|
|
462
|
+
return ['Check that the model name is correct']
|
|
463
|
+
|
|
464
|
+
case 'model-loading':
|
|
465
|
+
return [
|
|
466
|
+
'Wait for the model to finish loading',
|
|
467
|
+
error.model
|
|
468
|
+
? `Or pre-load it: ollama run ${error.model}`
|
|
469
|
+
: 'First request may be slow while model loads',
|
|
470
|
+
]
|
|
471
|
+
|
|
472
|
+
// OpenRouter API errors
|
|
473
|
+
case 'invalid-api-key':
|
|
474
|
+
return [
|
|
475
|
+
'Get an API key: https://openrouter.ai/keys',
|
|
476
|
+
'Set the key: export OPENROUTER_API_KEY=sk-or-...',
|
|
477
|
+
'Or set: export OPENAI_API_KEY=sk-or-...',
|
|
478
|
+
'Note: OpenRouter keys start with sk-or-',
|
|
479
|
+
]
|
|
480
|
+
|
|
481
|
+
case 'rate-limited':
|
|
482
|
+
return [
|
|
483
|
+
'Wait a moment and try again',
|
|
484
|
+
'OpenRouter shares rate limits across all users',
|
|
485
|
+
'Consider using Ollama for unlimited local inference',
|
|
486
|
+
'Or use OpenAI directly for higher rate limits',
|
|
487
|
+
]
|
|
488
|
+
|
|
489
|
+
case 'quota-exceeded':
|
|
490
|
+
return [
|
|
491
|
+
'Check your OpenRouter balance: https://openrouter.ai/credits',
|
|
492
|
+
'Add credits to continue using OpenRouter',
|
|
493
|
+
'Or switch to a free provider like Ollama',
|
|
494
|
+
]
|
|
495
|
+
|
|
496
|
+
case 'model-unavailable':
|
|
497
|
+
return [
|
|
498
|
+
'Check available models: https://openrouter.ai/models',
|
|
499
|
+
'Try: text-embedding-3-small or text-embedding-3-large',
|
|
500
|
+
]
|
|
501
|
+
|
|
502
|
+
// Connection errors
|
|
503
|
+
case 'connection-refused':
|
|
504
|
+
if (error.provider === 'ollama') {
|
|
505
|
+
return ['Start the Ollama daemon: ollama serve']
|
|
506
|
+
}
|
|
507
|
+
if (error.provider === 'lm-studio') {
|
|
508
|
+
return ['Open LM Studio and start the local server']
|
|
509
|
+
}
|
|
510
|
+
return ['Check that the server is running']
|
|
511
|
+
|
|
512
|
+
case 'connection-timeout':
|
|
513
|
+
return [
|
|
514
|
+
'Check your network connection',
|
|
515
|
+
'The server may be overloaded, try again later',
|
|
516
|
+
'Consider increasing timeout in config',
|
|
517
|
+
]
|
|
518
|
+
|
|
519
|
+
case 'network-error':
|
|
520
|
+
return [
|
|
521
|
+
'Check your internet connection',
|
|
522
|
+
'Check if the server is reachable',
|
|
523
|
+
'Try again later',
|
|
524
|
+
]
|
|
525
|
+
|
|
526
|
+
default:
|
|
527
|
+
return ['Check the error details above']
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Get a user-friendly error title for display
|
|
533
|
+
*/
|
|
534
|
+
export const getProviderErrorTitle = (error: ProviderError): string => {
|
|
535
|
+
switch (error.type) {
|
|
536
|
+
case 'daemon-not-running':
|
|
537
|
+
return 'Ollama is not running'
|
|
538
|
+
case 'gui-not-running':
|
|
539
|
+
return 'LM Studio is not running'
|
|
540
|
+
case 'model-not-found':
|
|
541
|
+
return error.model
|
|
542
|
+
? `Model '${error.model}' not found`
|
|
543
|
+
: 'Model not found'
|
|
544
|
+
case 'model-loading':
|
|
545
|
+
return error.model
|
|
546
|
+
? `Model '${error.model}' is still loading`
|
|
547
|
+
: 'Model is still loading'
|
|
548
|
+
case 'invalid-api-key':
|
|
549
|
+
return 'Invalid API key'
|
|
550
|
+
case 'rate-limited':
|
|
551
|
+
return 'Rate limit exceeded'
|
|
552
|
+
case 'quota-exceeded':
|
|
553
|
+
return 'Quota exceeded'
|
|
554
|
+
case 'model-unavailable':
|
|
555
|
+
return 'Model not available'
|
|
556
|
+
case 'connection-refused':
|
|
557
|
+
return `Cannot connect to ${error.provider}`
|
|
558
|
+
case 'connection-timeout':
|
|
559
|
+
return `Connection to ${error.provider} timed out`
|
|
560
|
+
case 'network-error':
|
|
561
|
+
return 'Network error'
|
|
562
|
+
default:
|
|
563
|
+
return 'Embedding error'
|
|
564
|
+
}
|
|
565
|
+
}
|