• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

stacklok / toolhive-studio / 29571051485

17 Jul 2026 09:44AM UTC coverage: 71.713% (+0.5%) from 71.204%
29571051485

Pull #2475

github

samuv
fix(chat): harden Effect runtime adapters after external review

Make readiness and runtime retrieval atomic, type adapter programs against
ChatServices, and tighten domain-error narrowing plus agent/settings edge cases.

Co-authored-by: Cursor <cursoragent@cursor.com>
Pull Request #2475: refactor(chat): migrate main-process chat to Effect domain services

5476 of 8187 branches covered (66.89%)

871 of 1363 new or added lines in 42 files covered. (63.9%)

1 existing line in 1 file now uncovered.

8153 of 11369 relevant lines covered (71.71%)

137.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.33
/main/src/chat/runtime/errors.ts
1
import { Data } from 'effect'
2

3
/** Base shape for expected chat domain failures. */
4
export type ChatDomainError =
5
  | ChatUnavailableError
6
  | StorageError
7
  | ThreadNotFoundError
8
  | ThreadAlreadyExistsError
9
  | ProviderError
10
  | McpDiscoveryError
11
  | McpServerUnavailableError
12
  | StreamConflictError
13
  | ValidationError
14

15
export const CHAT_UNAVAILABLE_USER_MESSAGE =
16
  'Chat is temporarily unavailable. Please restart the app and try again.'
13✔
17

18
export class ChatUnavailableError extends Data.TaggedError(
19
  'ChatUnavailableError'
20
)<{
21
  readonly reason: string
22
  readonly userMessage: string
23
}> {}
24

25
export class StorageError extends Data.TaggedError('StorageError')<{
26
  readonly operation: string
27
  readonly cause?: unknown
28
  readonly userMessage: string
29
}> {}
30

31
export class ThreadNotFoundError extends Data.TaggedError(
32
  'ThreadNotFoundError'
33
)<{
34
  readonly threadId: string
35
  readonly userMessage: string
36
}> {}
37

38
export class ThreadAlreadyExistsError extends Data.TaggedError(
39
  'ThreadAlreadyExistsError'
40
)<{
41
  readonly threadId: string
42
  readonly userMessage: string
43
}> {}
44

45
export class ProviderError extends Data.TaggedError('ProviderError')<{
46
  readonly providerId: string
47
  readonly cause?: unknown
48
  readonly userMessage: string
49
}> {}
50

51
export class McpDiscoveryError extends Data.TaggedError('McpDiscoveryError')<{
52
  readonly cause?: unknown
53
  readonly userMessage: string
54
  readonly serverFailures: ReadonlyArray<{
55
    readonly serverName: string
56
    readonly reason: string
57
  }>
58
}> {}
59

60
export class McpServerUnavailableError extends Data.TaggedError(
61
  'McpServerUnavailableError'
62
)<{
63
  readonly userMessage: string
64
}> {}
65

66
export class StreamConflictError extends Data.TaggedError(
67
  'StreamConflictError'
68
)<{
69
  readonly chatId: string
70
  readonly userMessage: string
71
}> {}
72

73
export class ValidationError extends Data.TaggedError('ValidationError')<{
74
  readonly field?: string
75
  readonly userMessage: string
76
}> {}
77

78
export function toUserFacingProviderMessage(error: unknown): string {
79
  const message = getErrorMessage(error)
4✔
80
  if (/overloaded/i.test(message)) {
4✔
81
    return 'The AI service is currently overloaded. Please try again in a few moments.'
1✔
82
  }
83
  if (/rate limit/i.test(message)) {
3!
NEW
84
    return 'Rate limit exceeded. Please wait a moment before sending another message.'
×
85
  }
86
  if (/insufficient_quota|quota/i.test(message)) {
3!
NEW
87
    return 'API quota exceeded. Please check your API key billing status.'
×
88
  }
89
  if (/invalid_api_key|authentication/i.test(message)) {
3!
NEW
90
    return 'Invalid API key. Please check your API key configuration.'
×
91
  }
92
  return message || 'An error occurred.'
3✔
93
}
94

95
export function getDomainUserMessage(error: ChatDomainError): string {
96
  return error.userMessage
22✔
97
}
98

99
function getErrorMessage(error: unknown): string {
100
  if (error instanceof Error) return error.message
4✔
101
  if (typeof error === 'string') return error
2!
102
  if (error && typeof error === 'object' && 'message' in error) {
2✔
103
    const message = (error as { message: unknown }).message
1✔
104
    if (typeof message === 'string') return message
1!
105
  }
106
  return ''
1✔
107
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc