hone-ai 0.5.0 → 0.10.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/README.md +47 -2
- package/package.json +5 -2
- package/src/agent-client.integration.test.ts +57 -59
- package/src/agent-client.test.ts +27 -27
- package/src/agent-client.ts +109 -77
- package/src/agent.test.ts +16 -16
- package/src/agent.ts +103 -103
- package/src/agents-md-generator.test.ts +360 -0
- package/src/agents-md-generator.ts +900 -0
- package/src/config.test.ts +209 -224
- package/src/config.ts +84 -83
- package/src/errors.test.ts +211 -208
- package/src/errors.ts +107 -101
- package/src/index.integration.test.ts +327 -223
- package/src/index.ts +163 -100
- package/src/integration-test.ts +168 -137
- package/src/logger.test.ts +67 -67
- package/src/logger.ts +8 -8
- package/src/prd-generator.integration.test.ts +50 -50
- package/src/prd-generator.test.ts +66 -25
- package/src/prd-generator.ts +280 -194
- package/src/prds.test.ts +60 -65
- package/src/prds.ts +64 -62
- package/src/prompt.test.ts +154 -155
- package/src/prompt.ts +63 -65
- package/src/run.ts +147 -147
- package/src/status.test.ts +80 -80
- package/src/status.ts +40 -42
- package/src/task-generator.test.ts +93 -66
- package/src/task-generator.ts +125 -112
package/src/errors.test.ts
CHANGED
|
@@ -1,281 +1,284 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, mock } from 'bun:test'
|
|
2
|
-
import {
|
|
3
|
-
formatError,
|
|
1
|
+
import { describe, expect, test, beforeEach, mock } from 'bun:test'
|
|
2
|
+
import {
|
|
3
|
+
formatError,
|
|
4
4
|
isNetworkError,
|
|
5
5
|
isRateLimitError,
|
|
6
6
|
isModelUnavailableError,
|
|
7
7
|
parseAgentError,
|
|
8
8
|
retryWithBackoff,
|
|
9
9
|
HoneError,
|
|
10
|
-
ErrorMessages
|
|
11
|
-
} from './errors'
|
|
10
|
+
ErrorMessages,
|
|
11
|
+
} from './errors'
|
|
12
12
|
|
|
13
13
|
describe('formatError', () => {
|
|
14
14
|
test('formats error with message only', () => {
|
|
15
|
-
const result = formatError('Something went wrong')
|
|
16
|
-
expect(result).toBe('✗ Something went wrong')
|
|
17
|
-
})
|
|
18
|
-
|
|
15
|
+
const result = formatError('Something went wrong')
|
|
16
|
+
expect(result).toBe('✗ Something went wrong')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
19
|
test('formats error with message and details', () => {
|
|
20
|
-
const result = formatError('Something went wrong', 'More details here')
|
|
21
|
-
expect(result).toBe('✗ Something went wrong\n\nMore details here')
|
|
22
|
-
})
|
|
23
|
-
})
|
|
20
|
+
const result = formatError('Something went wrong', 'More details here')
|
|
21
|
+
expect(result).toBe('✗ Something went wrong\n\nMore details here')
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
24
|
|
|
25
25
|
describe('isNetworkError', () => {
|
|
26
26
|
test('returns false for non-errors', () => {
|
|
27
|
-
expect(isNetworkError(null)).toBe(false)
|
|
28
|
-
expect(isNetworkError(undefined)).toBe(false)
|
|
29
|
-
expect(isNetworkError('string')).toBe(false)
|
|
30
|
-
expect(isNetworkError(123)).toBe(false)
|
|
31
|
-
})
|
|
32
|
-
|
|
27
|
+
expect(isNetworkError(null)).toBe(false)
|
|
28
|
+
expect(isNetworkError(undefined)).toBe(false)
|
|
29
|
+
expect(isNetworkError('string')).toBe(false)
|
|
30
|
+
expect(isNetworkError(123)).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
33
|
test('detects ECONNREFUSED', () => {
|
|
34
|
-
const error = new Error('connect ECONNREFUSED')
|
|
35
|
-
expect(isNetworkError(error)).toBe(true)
|
|
36
|
-
})
|
|
37
|
-
|
|
34
|
+
const error = new Error('connect ECONNREFUSED')
|
|
35
|
+
expect(isNetworkError(error)).toBe(true)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
38
|
test('detects ETIMEDOUT', () => {
|
|
39
|
-
const error = { code: 'ETIMEDOUT', message: 'timeout' }
|
|
40
|
-
expect(isNetworkError(error)).toBe(true)
|
|
41
|
-
})
|
|
42
|
-
|
|
39
|
+
const error = { code: 'ETIMEDOUT', message: 'timeout' }
|
|
40
|
+
expect(isNetworkError(error)).toBe(true)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
43
|
test('detects fetch failed', () => {
|
|
44
|
-
const error = new Error('fetch failed')
|
|
45
|
-
expect(isNetworkError(error)).toBe(true)
|
|
46
|
-
})
|
|
47
|
-
|
|
44
|
+
const error = new Error('fetch failed')
|
|
45
|
+
expect(isNetworkError(error)).toBe(true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
48
|
test('detects network in message', () => {
|
|
49
|
-
const error = new Error('Network request failed')
|
|
50
|
-
expect(isNetworkError(error)).toBe(true)
|
|
51
|
-
})
|
|
52
|
-
|
|
49
|
+
const error = new Error('Network request failed')
|
|
50
|
+
expect(isNetworkError(error)).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
53
|
test('returns false for non-network errors', () => {
|
|
54
|
-
const error = new Error('Validation failed')
|
|
55
|
-
expect(isNetworkError(error)).toBe(false)
|
|
56
|
-
})
|
|
57
|
-
})
|
|
54
|
+
const error = new Error('Validation failed')
|
|
55
|
+
expect(isNetworkError(error)).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
58
|
|
|
59
59
|
describe('retryWithBackoff', () => {
|
|
60
60
|
beforeEach(() => {
|
|
61
61
|
// Clear any timers
|
|
62
|
-
mock.restore()
|
|
63
|
-
})
|
|
64
|
-
|
|
62
|
+
mock.restore()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
65
|
test('succeeds on first try', async () => {
|
|
66
|
-
const fn = mock(() => Promise.resolve('success'))
|
|
67
|
-
const result = await retryWithBackoff(fn)
|
|
68
|
-
|
|
69
|
-
expect(result).toBe('success')
|
|
70
|
-
expect(fn).toHaveBeenCalledTimes(1)
|
|
71
|
-
})
|
|
72
|
-
|
|
66
|
+
const fn = mock(() => Promise.resolve('success'))
|
|
67
|
+
const result = await retryWithBackoff(fn)
|
|
68
|
+
|
|
69
|
+
expect(result).toBe('success')
|
|
70
|
+
expect(fn).toHaveBeenCalledTimes(1)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
73
|
test('retries on network error and succeeds', async () => {
|
|
74
|
-
let attempts = 0
|
|
74
|
+
let attempts = 0
|
|
75
75
|
const fn = mock(() => {
|
|
76
|
-
attempts
|
|
76
|
+
attempts++
|
|
77
77
|
if (attempts < 2) {
|
|
78
|
-
return Promise.reject(new Error('Network timeout'))
|
|
78
|
+
return Promise.reject(new Error('Network timeout'))
|
|
79
79
|
}
|
|
80
|
-
return Promise.resolve('success')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const result = await retryWithBackoff(fn, {
|
|
80
|
+
return Promise.resolve('success')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const result = await retryWithBackoff(fn, {
|
|
84
84
|
initialDelay: 10,
|
|
85
|
-
maxDelay: 50
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
expect(result).toBe('success')
|
|
89
|
-
expect(fn).toHaveBeenCalledTimes(2)
|
|
90
|
-
})
|
|
91
|
-
|
|
85
|
+
maxDelay: 50,
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
expect(result).toBe('success')
|
|
89
|
+
expect(fn).toHaveBeenCalledTimes(2)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
92
|
test('throws after max retries', async () => {
|
|
93
|
-
const fn = mock(() => Promise.reject(new Error('Network timeout')))
|
|
94
|
-
|
|
93
|
+
const fn = mock(() => Promise.reject(new Error('Network timeout')))
|
|
94
|
+
|
|
95
95
|
await expect(
|
|
96
|
-
retryWithBackoff(fn, {
|
|
96
|
+
retryWithBackoff(fn, {
|
|
97
97
|
maxRetries: 2,
|
|
98
98
|
initialDelay: 10,
|
|
99
|
-
maxDelay: 50
|
|
99
|
+
maxDelay: 50,
|
|
100
100
|
})
|
|
101
|
-
).rejects.toThrow('Network timeout')
|
|
102
|
-
|
|
103
|
-
expect(fn).toHaveBeenCalledTimes(3)
|
|
104
|
-
})
|
|
105
|
-
|
|
101
|
+
).rejects.toThrow('Network timeout')
|
|
102
|
+
|
|
103
|
+
expect(fn).toHaveBeenCalledTimes(3) // Initial + 2 retries
|
|
104
|
+
})
|
|
105
|
+
|
|
106
106
|
test('does not retry non-network errors', async () => {
|
|
107
|
-
const fn = mock(() => Promise.reject(new Error('Validation failed')))
|
|
108
|
-
|
|
107
|
+
const fn = mock(() => Promise.reject(new Error('Validation failed')))
|
|
108
|
+
|
|
109
109
|
await expect(
|
|
110
|
-
retryWithBackoff(fn, {
|
|
110
|
+
retryWithBackoff(fn, {
|
|
111
111
|
initialDelay: 10,
|
|
112
|
-
maxDelay: 50
|
|
112
|
+
maxDelay: 50,
|
|
113
113
|
})
|
|
114
|
-
).rejects.toThrow('Validation failed')
|
|
115
|
-
|
|
116
|
-
expect(fn).toHaveBeenCalledTimes(1)
|
|
117
|
-
})
|
|
118
|
-
|
|
114
|
+
).rejects.toThrow('Validation failed')
|
|
115
|
+
|
|
116
|
+
expect(fn).toHaveBeenCalledTimes(1) // Only initial attempt
|
|
117
|
+
})
|
|
118
|
+
|
|
119
119
|
test('respects custom shouldRetry predicate', async () => {
|
|
120
|
-
const fn = mock(() => Promise.reject(new Error('Custom error')))
|
|
121
|
-
|
|
122
|
-
const result = await retryWithBackoff(fn, {
|
|
120
|
+
const fn = mock(() => Promise.reject(new Error('Custom error')))
|
|
121
|
+
|
|
122
|
+
const result = await retryWithBackoff(fn, {
|
|
123
123
|
maxRetries: 1,
|
|
124
124
|
initialDelay: 10,
|
|
125
125
|
shouldRetry: (error: unknown) => {
|
|
126
|
-
return error instanceof Error && error.message === 'Custom error'
|
|
127
|
-
}
|
|
128
|
-
}).catch(() => 'caught')
|
|
129
|
-
|
|
130
|
-
expect(result).toBe('caught')
|
|
131
|
-
expect(fn).toHaveBeenCalledTimes(2)
|
|
132
|
-
})
|
|
133
|
-
})
|
|
126
|
+
return error instanceof Error && error.message === 'Custom error'
|
|
127
|
+
},
|
|
128
|
+
}).catch(() => 'caught')
|
|
129
|
+
|
|
130
|
+
expect(result).toBe('caught')
|
|
131
|
+
expect(fn).toHaveBeenCalledTimes(2) // Initial + 1 retry
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
134
|
|
|
135
135
|
describe('HoneError', () => {
|
|
136
136
|
test('creates error with message', () => {
|
|
137
|
-
const error = new HoneError('Test error')
|
|
138
|
-
expect(error.message).toBe('Test error')
|
|
139
|
-
expect(error.exitCode).toBe(1)
|
|
140
|
-
expect(error.name).toBe('HoneError')
|
|
141
|
-
})
|
|
142
|
-
|
|
137
|
+
const error = new HoneError('Test error')
|
|
138
|
+
expect(error.message).toBe('Test error')
|
|
139
|
+
expect(error.exitCode).toBe(1)
|
|
140
|
+
expect(error.name).toBe('HoneError')
|
|
141
|
+
})
|
|
142
|
+
|
|
143
143
|
test('creates error with custom exit code', () => {
|
|
144
|
-
const error = new HoneError('Test error', 2)
|
|
145
|
-
expect(error.exitCode).toBe(2)
|
|
146
|
-
})
|
|
147
|
-
})
|
|
144
|
+
const error = new HoneError('Test error', 2)
|
|
145
|
+
expect(error.exitCode).toBe(2)
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
148
|
|
|
149
149
|
describe('isRateLimitError', () => {
|
|
150
150
|
test('detects rate limit variations', () => {
|
|
151
|
-
expect(isRateLimitError('Rate limit exceeded')).toBe(true)
|
|
152
|
-
expect(isRateLimitError('rate_limit error')).toBe(true)
|
|
153
|
-
expect(isRateLimitError('429 Too Many Requests')).toBe(true)
|
|
154
|
-
expect(isRateLimitError('Quota exceeded')).toBe(true)
|
|
155
|
-
})
|
|
156
|
-
|
|
151
|
+
expect(isRateLimitError('Rate limit exceeded')).toBe(true)
|
|
152
|
+
expect(isRateLimitError('rate_limit error')).toBe(true)
|
|
153
|
+
expect(isRateLimitError('429 Too Many Requests')).toBe(true)
|
|
154
|
+
expect(isRateLimitError('Quota exceeded')).toBe(true)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
157
|
test('returns false for non-rate-limit errors', () => {
|
|
158
|
-
expect(isRateLimitError('Model not found')).toBe(false)
|
|
159
|
-
expect(isRateLimitError('Network error')).toBe(false)
|
|
160
|
-
})
|
|
161
|
-
})
|
|
158
|
+
expect(isRateLimitError('Model not found')).toBe(false)
|
|
159
|
+
expect(isRateLimitError('Network error')).toBe(false)
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
162
|
|
|
163
163
|
describe('isModelUnavailableError', () => {
|
|
164
164
|
test('detects model unavailability', () => {
|
|
165
|
-
expect(isModelUnavailableError('Model not found')).toBe(true)
|
|
166
|
-
expect(isModelUnavailableError('Invalid model name')).toBe(true)
|
|
167
|
-
expect(isModelUnavailableError('404 Not Found')).toBe(true)
|
|
168
|
-
expect(isModelUnavailableError('Unknown model')).toBe(true)
|
|
169
|
-
})
|
|
170
|
-
|
|
165
|
+
expect(isModelUnavailableError('Model not found')).toBe(true)
|
|
166
|
+
expect(isModelUnavailableError('Invalid model name')).toBe(true)
|
|
167
|
+
expect(isModelUnavailableError('404 Not Found')).toBe(true)
|
|
168
|
+
expect(isModelUnavailableError('Unknown model')).toBe(true)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
171
|
test('returns false for non-model errors', () => {
|
|
172
|
-
expect(isModelUnavailableError('Rate limit exceeded')).toBe(false)
|
|
173
|
-
expect(isModelUnavailableError('Network timeout')).toBe(false)
|
|
174
|
-
})
|
|
175
|
-
})
|
|
172
|
+
expect(isModelUnavailableError('Rate limit exceeded')).toBe(false)
|
|
173
|
+
expect(isModelUnavailableError('Network timeout')).toBe(false)
|
|
174
|
+
})
|
|
175
|
+
})
|
|
176
176
|
|
|
177
177
|
describe('parseAgentError', () => {
|
|
178
178
|
test('identifies network errors', () => {
|
|
179
|
-
const result = parseAgentError('ECONNREFUSED', 1)
|
|
180
|
-
expect(result.type).toBe('network')
|
|
181
|
-
expect(result.retryable).toBe(true)
|
|
182
|
-
})
|
|
183
|
-
|
|
179
|
+
const result = parseAgentError('ECONNREFUSED', 1)
|
|
180
|
+
expect(result.type).toBe('network')
|
|
181
|
+
expect(result.retryable).toBe(true)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
184
|
test('identifies rate limit errors', () => {
|
|
185
|
-
const result = parseAgentError('Rate limit exceeded', 1)
|
|
186
|
-
expect(result.type).toBe('rate_limit')
|
|
187
|
-
expect(result.retryable).toBe(false)
|
|
188
|
-
})
|
|
189
|
-
|
|
185
|
+
const result = parseAgentError('Rate limit exceeded', 1)
|
|
186
|
+
expect(result.type).toBe('rate_limit')
|
|
187
|
+
expect(result.retryable).toBe(false)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
190
|
test('extracts retry-after from rate limit errors', () => {
|
|
191
|
-
const result = parseAgentError('Rate limit exceeded. Retry after 60 seconds', 1)
|
|
192
|
-
expect(result.type).toBe('rate_limit')
|
|
193
|
-
expect(result.retryAfter).toBe(60)
|
|
194
|
-
})
|
|
195
|
-
|
|
191
|
+
const result = parseAgentError('Rate limit exceeded. Retry after 60 seconds', 1)
|
|
192
|
+
expect(result.type).toBe('rate_limit')
|
|
193
|
+
expect(result.retryAfter).toBe(60)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
196
|
test('identifies model unavailable errors', () => {
|
|
197
|
-
const result = parseAgentError('Model not found', 1)
|
|
198
|
-
expect(result.type).toBe('model_unavailable')
|
|
199
|
-
expect(result.retryable).toBe(false)
|
|
200
|
-
})
|
|
201
|
-
|
|
197
|
+
const result = parseAgentError('Model not found', 1)
|
|
198
|
+
expect(result.type).toBe('model_unavailable')
|
|
199
|
+
expect(result.retryable).toBe(false)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
202
|
test('identifies spawn failures', () => {
|
|
203
|
-
const result = parseAgentError('ENOENT', 1)
|
|
204
|
-
expect(result.type).toBe('spawn_failed')
|
|
205
|
-
expect(result.retryable).toBe(false)
|
|
206
|
-
})
|
|
207
|
-
|
|
203
|
+
const result = parseAgentError('ENOENT', 1)
|
|
204
|
+
expect(result.type).toBe('spawn_failed')
|
|
205
|
+
expect(result.retryable).toBe(false)
|
|
206
|
+
})
|
|
207
|
+
|
|
208
208
|
test('returns unknown for other errors', () => {
|
|
209
|
-
const result = parseAgentError('Some random error', 1)
|
|
210
|
-
expect(result.type).toBe('unknown')
|
|
211
|
-
expect(result.retryable).toBe(false)
|
|
212
|
-
})
|
|
213
|
-
})
|
|
209
|
+
const result = parseAgentError('Some random error', 1)
|
|
210
|
+
expect(result.type).toBe('unknown')
|
|
211
|
+
expect(result.retryable).toBe(false)
|
|
212
|
+
})
|
|
213
|
+
})
|
|
214
214
|
|
|
215
215
|
describe('ErrorMessages', () => {
|
|
216
216
|
test('MISSING_API_KEY has correct format', () => {
|
|
217
|
-
const { message, details } = ErrorMessages.MISSING_API_KEY
|
|
218
|
-
expect(message).toContain('ANTHROPIC_API_KEY')
|
|
219
|
-
expect(details).toContain('.env')
|
|
220
|
-
expect(details).toContain('https://console.anthropic.com/')
|
|
221
|
-
})
|
|
222
|
-
|
|
217
|
+
const { message, details } = ErrorMessages.MISSING_API_KEY
|
|
218
|
+
expect(message).toContain('ANTHROPIC_API_KEY')
|
|
219
|
+
expect(details).toContain('.env')
|
|
220
|
+
expect(details).toContain('https://console.anthropic.com/')
|
|
221
|
+
})
|
|
222
|
+
|
|
223
223
|
test('FILE_NOT_FOUND includes path', () => {
|
|
224
|
-
const { message, details } = ErrorMessages.FILE_NOT_FOUND('/path/to/file.yml')
|
|
225
|
-
expect(message).toContain('File not found')
|
|
226
|
-
expect(details).toContain('/path/to/file.yml')
|
|
227
|
-
})
|
|
228
|
-
|
|
224
|
+
const { message, details } = ErrorMessages.FILE_NOT_FOUND('/path/to/file.yml')
|
|
225
|
+
expect(message).toContain('File not found')
|
|
226
|
+
expect(details).toContain('/path/to/file.yml')
|
|
227
|
+
})
|
|
228
|
+
|
|
229
229
|
test('AGENT_NOT_FOUND provides install instructions for claude', () => {
|
|
230
|
-
const { message, details } = ErrorMessages.AGENT_NOT_FOUND('claude')
|
|
231
|
-
expect(message).toContain('claude')
|
|
232
|
-
expect(details).toContain('npm install')
|
|
233
|
-
})
|
|
234
|
-
|
|
230
|
+
const { message, details } = ErrorMessages.AGENT_NOT_FOUND('claude')
|
|
231
|
+
expect(message).toContain('claude')
|
|
232
|
+
expect(details).toContain('npm install')
|
|
233
|
+
})
|
|
234
|
+
|
|
235
235
|
test('AGENT_NOT_FOUND provides install instructions for opencode', () => {
|
|
236
|
-
const { message, details } = ErrorMessages.AGENT_NOT_FOUND('opencode')
|
|
237
|
-
expect(message).toContain('opencode')
|
|
238
|
-
expect(details).toContain('npm install')
|
|
239
|
-
})
|
|
240
|
-
|
|
236
|
+
const { message, details } = ErrorMessages.AGENT_NOT_FOUND('opencode')
|
|
237
|
+
expect(message).toContain('opencode')
|
|
238
|
+
expect(details).toContain('npm install')
|
|
239
|
+
})
|
|
240
|
+
|
|
241
241
|
test('GIT_NOT_INITIALIZED has init instructions', () => {
|
|
242
|
-
const { message, details } = ErrorMessages.GIT_NOT_INITIALIZED
|
|
243
|
-
expect(message).toContain('Git')
|
|
244
|
-
expect(details).toContain('git init')
|
|
245
|
-
})
|
|
246
|
-
|
|
242
|
+
const { message, details } = ErrorMessages.GIT_NOT_INITIALIZED
|
|
243
|
+
expect(message).toContain('Git')
|
|
244
|
+
expect(details).toContain('git init')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
247
|
test('AGENT_SPAWN_FAILED includes agent and error', () => {
|
|
248
|
-
const { message, details } = ErrorMessages.AGENT_SPAWN_FAILED('opencode', 'command not found')
|
|
249
|
-
expect(message).toContain('opencode')
|
|
250
|
-
expect(details).toContain('command not found')
|
|
251
|
-
expect(details).toContain('PATH')
|
|
252
|
-
})
|
|
253
|
-
|
|
248
|
+
const { message, details } = ErrorMessages.AGENT_SPAWN_FAILED('opencode', 'command not found')
|
|
249
|
+
expect(message).toContain('opencode')
|
|
250
|
+
expect(details).toContain('command not found')
|
|
251
|
+
expect(details).toContain('PATH')
|
|
252
|
+
})
|
|
253
|
+
|
|
254
254
|
test('MODEL_UNAVAILABLE includes model and agent', () => {
|
|
255
|
-
const { message, details } = ErrorMessages.MODEL_UNAVAILABLE(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
expect(
|
|
260
|
-
|
|
261
|
-
|
|
255
|
+
const { message, details } = ErrorMessages.MODEL_UNAVAILABLE(
|
|
256
|
+
'claude-sonnet-4-invalid',
|
|
257
|
+
'opencode'
|
|
258
|
+
)
|
|
259
|
+
expect(message).toContain('Model not available')
|
|
260
|
+
expect(details).toContain('claude-sonnet-4-invalid')
|
|
261
|
+
expect(details).toContain('opencode')
|
|
262
|
+
expect(details).toContain('--help')
|
|
263
|
+
})
|
|
264
|
+
|
|
262
265
|
test('RATE_LIMIT_ERROR without retry-after', () => {
|
|
263
|
-
const { message, details } = ErrorMessages.RATE_LIMIT_ERROR('opencode')
|
|
264
|
-
expect(message).toContain('Rate limit')
|
|
265
|
-
expect(details).toContain('opencode')
|
|
266
|
-
expect(details).toContain('wait')
|
|
267
|
-
})
|
|
268
|
-
|
|
266
|
+
const { message, details } = ErrorMessages.RATE_LIMIT_ERROR('opencode')
|
|
267
|
+
expect(message).toContain('Rate limit')
|
|
268
|
+
expect(details).toContain('opencode')
|
|
269
|
+
expect(details).toContain('wait')
|
|
270
|
+
})
|
|
271
|
+
|
|
269
272
|
test('RATE_LIMIT_ERROR with retry-after', () => {
|
|
270
|
-
const { message, details } = ErrorMessages.RATE_LIMIT_ERROR('claude', 120)
|
|
271
|
-
expect(message).toContain('Rate limit')
|
|
272
|
-
expect(details).toContain('120 seconds')
|
|
273
|
-
})
|
|
274
|
-
|
|
273
|
+
const { message, details } = ErrorMessages.RATE_LIMIT_ERROR('claude', 120)
|
|
274
|
+
expect(message).toContain('Rate limit')
|
|
275
|
+
expect(details).toContain('120 seconds')
|
|
276
|
+
})
|
|
277
|
+
|
|
275
278
|
test('AGENT_ERROR includes details', () => {
|
|
276
|
-
const { message, details } = ErrorMessages.AGENT_ERROR('opencode', 2, 'Invalid input')
|
|
277
|
-
expect(message).toContain('opencode')
|
|
278
|
-
expect(details).toContain('code 2')
|
|
279
|
-
expect(details).toContain('Invalid input')
|
|
280
|
-
})
|
|
281
|
-
})
|
|
279
|
+
const { message, details } = ErrorMessages.AGENT_ERROR('opencode', 2, 'Invalid input')
|
|
280
|
+
expect(message).toContain('opencode')
|
|
281
|
+
expect(details).toContain('code 2')
|
|
282
|
+
expect(details).toContain('Invalid input')
|
|
283
|
+
})
|
|
284
|
+
})
|