@llumiverse/core 1.2.0 → 1.4.0-dev.20260629.090753Z

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/lib/CompletionStream.d.ts +48 -0
  2. package/lib/CompletionStream.d.ts.map +1 -0
  3. package/lib/{esm/CompletionStream.js → CompletionStream.js} +142 -67
  4. package/lib/CompletionStream.js.map +1 -0
  5. package/lib/{types/Driver.d.ts → Driver.d.ts} +38 -5
  6. package/lib/Driver.d.ts.map +1 -0
  7. package/lib/{esm/Driver.js → Driver.js} +152 -107
  8. package/lib/Driver.js.map +1 -0
  9. package/lib/{types/async.d.ts → async.d.ts} +3 -3
  10. package/lib/async.d.ts.map +1 -0
  11. package/lib/{esm/async.js → async.js} +4 -4
  12. package/lib/async.js.map +1 -0
  13. package/lib/{types/conversation-utils.d.ts → conversation-utils.d.ts} +9 -0
  14. package/lib/conversation-utils.d.ts.map +1 -0
  15. package/lib/{esm/conversation-utils.js → conversation-utils.js} +78 -15
  16. package/lib/conversation-utils.js.map +1 -0
  17. package/lib/embeddings.d.ts +58 -0
  18. package/lib/embeddings.d.ts.map +1 -0
  19. package/lib/embeddings.js +131 -0
  20. package/lib/embeddings.js.map +1 -0
  21. package/lib/formatters/commons.d.ts +3 -0
  22. package/lib/formatters/commons.d.ts.map +1 -0
  23. package/lib/formatters/commons.js +4 -0
  24. package/lib/formatters/commons.js.map +1 -0
  25. package/lib/{types/formatters → formatters}/generic.d.ts +2 -2
  26. package/lib/formatters/generic.d.ts.map +1 -0
  27. package/lib/{esm/formatters → formatters}/generic.js +8 -8
  28. package/lib/formatters/generic.js.map +1 -0
  29. package/lib/formatters/index.d.ts +4 -0
  30. package/lib/formatters/index.d.ts.map +1 -0
  31. package/lib/formatters/index.js +4 -0
  32. package/lib/formatters/index.js.map +1 -0
  33. package/lib/{types/formatters → formatters}/nova.d.ts +4 -4
  34. package/lib/formatters/nova.d.ts.map +1 -0
  35. package/lib/{esm/formatters → formatters}/nova.js +19 -17
  36. package/lib/formatters/nova.js.map +1 -0
  37. package/lib/http-agent.d.ts +53 -0
  38. package/lib/http-agent.d.ts.map +1 -0
  39. package/lib/http-agent.js +141 -0
  40. package/lib/http-agent.js.map +1 -0
  41. package/lib/index.d.ts +8 -0
  42. package/lib/index.d.ts.map +1 -0
  43. package/lib/index.js +7 -0
  44. package/lib/index.js.map +1 -0
  45. package/lib/{types/json.d.ts → json.d.ts} +1 -1
  46. package/lib/json.d.ts.map +1 -0
  47. package/lib/{esm/json.js → json.js} +4 -3
  48. package/lib/json.js.map +1 -0
  49. package/lib/logger.d.ts +3 -0
  50. package/lib/logger.d.ts.map +1 -0
  51. package/lib/logger.js +42 -0
  52. package/lib/logger.js.map +1 -0
  53. package/lib/resolver.d.ts +2 -0
  54. package/lib/resolver.d.ts.map +1 -0
  55. package/lib/{esm/resolver.js → resolver.js} +7 -4
  56. package/lib/resolver.js.map +1 -0
  57. package/lib/stream.d.ts.map +1 -0
  58. package/lib/stream.js.map +1 -0
  59. package/lib/{types/validation.d.ts → validation.d.ts} +1 -1
  60. package/lib/validation.d.ts.map +1 -0
  61. package/lib/validation.js +100 -0
  62. package/lib/validation.js.map +1 -0
  63. package/package.json +29 -38
  64. package/src/CompletionStream.ts +208 -115
  65. package/src/Driver.error.test.ts +101 -72
  66. package/src/Driver.ts +208 -152
  67. package/src/async.ts +30 -30
  68. package/src/conversation-utils.ts +92 -16
  69. package/src/embeddings.ts +157 -0
  70. package/src/formatters/commons.ts +2 -2
  71. package/src/formatters/generic.ts +20 -17
  72. package/src/formatters/index.ts +3 -3
  73. package/src/formatters/nova.ts +53 -51
  74. package/src/http-agent.test.ts +281 -0
  75. package/src/http-agent.ts +177 -0
  76. package/src/index.ts +7 -5
  77. package/src/json.ts +6 -5
  78. package/src/logger.ts +42 -0
  79. package/src/resolver.ts +12 -9
  80. package/src/stream.ts +4 -5
  81. package/src/validation.ts +42 -29
  82. package/lib/cjs/CompletionStream.js +0 -293
  83. package/lib/cjs/CompletionStream.js.map +0 -1
  84. package/lib/cjs/Driver.js +0 -300
  85. package/lib/cjs/Driver.js.map +0 -1
  86. package/lib/cjs/async.js +0 -131
  87. package/lib/cjs/async.js.map +0 -1
  88. package/lib/cjs/conversation-utils.js +0 -579
  89. package/lib/cjs/conversation-utils.js.map +0 -1
  90. package/lib/cjs/formatters/commons.js +0 -7
  91. package/lib/cjs/formatters/commons.js.map +0 -1
  92. package/lib/cjs/formatters/generic.js +0 -59
  93. package/lib/cjs/formatters/generic.js.map +0 -1
  94. package/lib/cjs/formatters/index.js +0 -20
  95. package/lib/cjs/formatters/index.js.map +0 -1
  96. package/lib/cjs/formatters/nova.js +0 -99
  97. package/lib/cjs/formatters/nova.js.map +0 -1
  98. package/lib/cjs/index.js +0 -22
  99. package/lib/cjs/index.js.map +0 -1
  100. package/lib/cjs/json.js +0 -29
  101. package/lib/cjs/json.js.map +0 -1
  102. package/lib/cjs/package.json +0 -3
  103. package/lib/cjs/resolver.js +0 -45
  104. package/lib/cjs/resolver.js.map +0 -1
  105. package/lib/cjs/stream.js +0 -30
  106. package/lib/cjs/stream.js.map +0 -1
  107. package/lib/cjs/validation.js +0 -96
  108. package/lib/cjs/validation.js.map +0 -1
  109. package/lib/esm/CompletionStream.js.map +0 -1
  110. package/lib/esm/Driver.js.map +0 -1
  111. package/lib/esm/async.js.map +0 -1
  112. package/lib/esm/conversation-utils.js.map +0 -1
  113. package/lib/esm/formatters/commons.js +0 -4
  114. package/lib/esm/formatters/commons.js.map +0 -1
  115. package/lib/esm/formatters/generic.js.map +0 -1
  116. package/lib/esm/formatters/index.js +0 -4
  117. package/lib/esm/formatters/index.js.map +0 -1
  118. package/lib/esm/formatters/nova.js.map +0 -1
  119. package/lib/esm/index.js +0 -6
  120. package/lib/esm/index.js.map +0 -1
  121. package/lib/esm/json.js.map +0 -1
  122. package/lib/esm/resolver.js.map +0 -1
  123. package/lib/esm/stream.js.map +0 -1
  124. package/lib/esm/validation.js +0 -88
  125. package/lib/esm/validation.js.map +0 -1
  126. package/lib/types/CompletionStream.d.ts +0 -20
  127. package/lib/types/CompletionStream.d.ts.map +0 -1
  128. package/lib/types/Driver.d.ts.map +0 -1
  129. package/lib/types/async.d.ts.map +0 -1
  130. package/lib/types/conversation-utils.d.ts.map +0 -1
  131. package/lib/types/formatters/commons.d.ts +0 -3
  132. package/lib/types/formatters/commons.d.ts.map +0 -1
  133. package/lib/types/formatters/generic.d.ts.map +0 -1
  134. package/lib/types/formatters/index.d.ts +0 -4
  135. package/lib/types/formatters/index.d.ts.map +0 -1
  136. package/lib/types/formatters/nova.d.ts.map +0 -1
  137. package/lib/types/index.d.ts +0 -6
  138. package/lib/types/index.d.ts.map +0 -1
  139. package/lib/types/json.d.ts.map +0 -1
  140. package/lib/types/resolver.d.ts +0 -2
  141. package/lib/types/resolver.d.ts.map +0 -1
  142. package/lib/types/stream.d.ts.map +0 -1
  143. package/lib/types/validation.d.ts.map +0 -1
  144. /package/lib/{types/stream.d.ts → stream.d.ts} +0 -0
  145. /package/lib/{esm/stream.js → stream.js} +0 -0
@@ -1,16 +1,17 @@
1
1
  import {
2
- AIModel,
3
- Completion,
4
- CompletionChunkObject,
5
- DriverOptions,
6
- EmbeddingsOptions,
7
- EmbeddingsResult,
8
- ExecutionOptions,
9
- LlumiverseErrorContext,
10
- ModelSearchPayload,
2
+ type AIModel,
3
+ type Completion,
4
+ type CompletionChunkObject,
5
+ type DriverOptions,
6
+ type EmbeddingsOptions,
7
+ type EmbeddingsResult,
8
+ type ExecutionOptions,
11
9
  LlumiverseError,
10
+ type LlumiverseErrorContext,
11
+ type ModelSearchPayload,
12
12
  } from '@llumiverse/common';
13
13
  import { beforeEach, describe, expect, it } from 'vitest';
14
+ import { errorWith, getProp } from '../test/__helpers__/test-utils.js';
14
15
  import { AbstractDriver } from './Driver.js';
15
16
 
16
17
  // Simple test driver implementation
@@ -21,7 +22,10 @@ class TestDriver extends AbstractDriver<DriverOptions, string> {
21
22
  throw new Error('Not implemented');
22
23
  }
23
24
 
24
- async requestTextCompletionStream(_prompt: string, _options: ExecutionOptions): Promise<AsyncIterable<CompletionChunkObject>> {
25
+ async requestTextCompletionStream(
26
+ _prompt: string,
27
+ _options: ExecutionOptions,
28
+ ): Promise<AsyncIterable<CompletionChunkObject>> {
25
29
  throw new Error('Not implemented');
26
30
  }
27
31
 
@@ -36,6 +40,11 @@ class TestDriver extends AbstractDriver<DriverOptions, string> {
36
40
  async generateEmbeddings(_options: EmbeddingsOptions): Promise<EmbeddingsResult> {
37
41
  throw new Error('Not implemented');
38
42
  }
43
+
44
+ // Re-exposed for tests — base class declares this as protected
45
+ isRetryableError(statusCode: number | undefined, message: string): boolean | undefined {
46
+ return super.isRetryableError(statusCode, message);
47
+ }
39
48
  }
40
49
 
41
50
  describe('AbstractDriver Error Formatting', () => {
@@ -53,90 +62,118 @@ describe('AbstractDriver Error Formatting', () => {
53
62
  describe('isRetryableError', () => {
54
63
  describe('HTTP status codes', () => {
55
64
  it('should mark 429 as retryable (rate limit)', () => {
56
- expect(driver['isRetryableError'](429, 'Rate limit exceeded')).toBe(true);
65
+ expect(driver.isRetryableError(429, 'Rate limit exceeded')).toBe(true);
57
66
  });
58
67
 
59
68
  it('should mark 408 as retryable (timeout)', () => {
60
- expect(driver['isRetryableError'](408, 'Request timeout')).toBe(true);
69
+ expect(driver.isRetryableError(408, 'Request timeout')).toBe(true);
61
70
  });
62
71
 
63
72
  it('should mark 529 as retryable (overloaded)', () => {
64
- expect(driver['isRetryableError'](529, 'Service overloaded')).toBe(true);
73
+ expect(driver.isRetryableError(529, 'Service overloaded')).toBe(true);
65
74
  });
66
75
 
67
76
  it('should mark 5xx as retryable (server errors)', () => {
68
- expect(driver['isRetryableError'](500, 'Internal server error')).toBe(true);
69
- expect(driver['isRetryableError'](502, 'Bad gateway')).toBe(true);
70
- expect(driver['isRetryableError'](503, 'Service unavailable')).toBe(true);
71
- expect(driver['isRetryableError'](504, 'Gateway timeout')).toBe(true);
77
+ expect(driver.isRetryableError(500, 'Internal server error')).toBe(true);
78
+ expect(driver.isRetryableError(502, 'Bad gateway')).toBe(true);
79
+ expect(driver.isRetryableError(503, 'Service unavailable')).toBe(true);
80
+ expect(driver.isRetryableError(504, 'Gateway timeout')).toBe(true);
72
81
  });
73
82
 
74
83
  it('should mark 4xx as not retryable (except 429, 408)', () => {
75
- expect(driver['isRetryableError'](400, 'Bad request')).toBe(false);
76
- expect(driver['isRetryableError'](401, 'Unauthorized')).toBe(false);
77
- expect(driver['isRetryableError'](403, 'Forbidden')).toBe(false);
78
- expect(driver['isRetryableError'](404, 'Not found')).toBe(false);
84
+ expect(driver.isRetryableError(400, 'Bad request')).toBe(false);
85
+ expect(driver.isRetryableError(401, 'Unauthorized')).toBe(false);
86
+ expect(driver.isRetryableError(403, 'Forbidden')).toBe(false);
87
+ expect(driver.isRetryableError(404, 'Not found')).toBe(false);
88
+ });
89
+
90
+ it('should honor transient provider messages before non-retryable 4xx status codes', () => {
91
+ expect(
92
+ driver.isRetryableError(400, 'Unable to fetch URL. Status: URL_REJECTED-REJECTED_CLIENT_THROTTLED'),
93
+ ).toBe(true);
94
+ expect(
95
+ driver.isRetryableError(400, 'Unable to fetch URL. Status: URL_REJECTED-REJECTED_RATE_LIMITED'),
96
+ ).toBe(true);
97
+ expect(driver.isRetryableError(400, 'Request throttled by upstream host')).toBe(true);
98
+ expect(driver.isRetryableError(400, 'Please retry with a valid model id')).toBe(false);
99
+ });
100
+
101
+ it('should mark transport aborts and deadline-exceeded as retryable even under a 4xx status', () => {
102
+ expect(driver.isRetryableError(undefined, 'This operation was aborted')).toBe(true);
103
+ expect(driver.isRetryableError(499, 'This operation was aborted')).toBe(true);
104
+ expect(driver.isRetryableError(400, 'Deadline expired before operation could complete')).toBe(true);
105
+ expect(driver.isRetryableError(504, '[504] DEADLINE_EXCEEDED')).toBe(true);
106
+ // a genuine non-transient 4xx without an abort/deadline signal stays non-retryable
107
+ expect(driver.isRetryableError(400, 'Invalid argument')).toBe(false);
79
108
  });
80
109
 
81
110
  it('should mark 2xx and 3xx as not retryable', () => {
82
- expect(driver['isRetryableError'](200, 'OK')).toBe(false);
83
- expect(driver['isRetryableError'](301, 'Moved permanently')).toBe(false);
111
+ expect(driver.isRetryableError(200, 'OK')).toBe(false);
112
+ expect(driver.isRetryableError(301, 'Moved permanently')).toBe(false);
84
113
  });
85
114
  });
86
115
 
87
116
  describe('message-based detection', () => {
88
117
  it('should detect rate limit in message', () => {
89
- expect(driver['isRetryableError'](undefined, 'Rate limit exceeded')).toBe(true);
90
- expect(driver['isRetryableError'](undefined, 'You have hit the rate limit')).toBe(true);
91
- expect(driver['isRetryableError'](undefined, 'RATE_LIMIT_EXCEEDED')).toBe(true);
118
+ expect(driver.isRetryableError(undefined, 'Rate limit exceeded')).toBe(true);
119
+ expect(driver.isRetryableError(undefined, 'You have hit the rate limit')).toBe(true);
120
+ expect(driver.isRetryableError(undefined, 'RATE_LIMIT_EXCEEDED')).toBe(true);
92
121
  });
93
122
 
94
123
  it('should detect timeout in message', () => {
95
- expect(driver['isRetryableError'](undefined, 'Request timeout')).toBe(true);
96
- expect(driver['isRetryableError'](undefined, 'Connection timed out')).toBe(true);
97
- expect(driver['isRetryableError'](undefined, 'TIMEOUT_ERROR')).toBe(true);
124
+ expect(driver.isRetryableError(undefined, 'Request timeout')).toBe(true);
125
+ expect(driver.isRetryableError(undefined, 'Connection timed out')).toBe(true);
126
+ expect(driver.isRetryableError(undefined, 'TIMEOUT_ERROR')).toBe(true);
98
127
  });
99
128
 
100
129
  it('should detect retry in message', () => {
101
- expect(driver['isRetryableError'](undefined, 'Please retry later')).toBe(true);
102
- expect(driver['isRetryableError'](undefined, 'Retry the request')).toBe(true);
130
+ expect(driver.isRetryableError(undefined, 'Please retry later')).toBe(true);
131
+ expect(driver.isRetryableError(undefined, 'Retry the request')).toBe(true);
103
132
  });
104
133
 
105
134
  it('should detect overload in message', () => {
106
- expect(driver['isRetryableError'](undefined, 'Service overloaded')).toBe(true);
107
- expect(driver['isRetryableError'](undefined, 'Server is overload')).toBe(true);
108
- expect(driver['isRetryableError'](undefined, 'System overloaded')).toBe(true);
135
+ expect(driver.isRetryableError(undefined, 'Service overloaded')).toBe(true);
136
+ expect(driver.isRetryableError(undefined, 'Server is overload')).toBe(true);
137
+ expect(driver.isRetryableError(undefined, 'System overloaded')).toBe(true);
109
138
  });
110
139
 
111
140
  it('should detect resource exhausted in message', () => {
112
- expect(driver['isRetryableError'](undefined, 'Resource exhausted')).toBe(true);
113
- expect(driver['isRetryableError'](undefined, 'Resources exhausted')).toBe(true);
141
+ expect(driver.isRetryableError(undefined, 'Resource exhausted')).toBe(true);
142
+ expect(driver.isRetryableError(undefined, 'Resources exhausted')).toBe(true);
114
143
  });
115
144
 
116
145
  it('should detect throttle in message', () => {
117
- expect(driver['isRetryableError'](undefined, 'Request throttled')).toBe(true);
118
- expect(driver['isRetryableError'](undefined, 'Throttling exception')).toBe(true);
146
+ expect(driver.isRetryableError(undefined, 'Request throttled')).toBe(true);
147
+ expect(driver.isRetryableError(undefined, 'Throttling exception')).toBe(true);
119
148
  });
120
149
 
121
150
  it('should detect status codes in message', () => {
122
- expect(driver['isRetryableError'](undefined, 'Error 429: Too many requests')).toBe(true);
123
- expect(driver['isRetryableError'](undefined, 'HTTP 529 error')).toBe(true);
151
+ expect(driver.isRetryableError(undefined, 'Error 429: Too many requests')).toBe(true);
152
+ expect(driver.isRetryableError(undefined, 'HTTP 529 error')).toBe(true);
153
+ });
154
+
155
+ it('should classify invalid_grant credential issuer failures as non-retryable', () => {
156
+ expect(
157
+ driver.isRetryableError(
158
+ undefined,
159
+ "Error code invalid_grant: Error connecting to the given credential's issuer.",
160
+ ),
161
+ ).toBe(false);
124
162
  });
125
163
 
126
164
  it('should mark unknown messages as undefined (let consumer decide)', () => {
127
- expect(driver['isRetryableError'](undefined, 'Invalid API key')).toBeUndefined();
128
- expect(driver['isRetryableError'](undefined, 'Bad request')).toBeUndefined();
129
- expect(driver['isRetryableError'](undefined, 'Model not found')).toBeUndefined();
165
+ expect(driver.isRetryableError(undefined, 'Invalid API key')).toBeUndefined();
166
+ expect(driver.isRetryableError(undefined, 'Bad request')).toBeUndefined();
167
+ expect(driver.isRetryableError(undefined, 'Model not found')).toBeUndefined();
130
168
  });
131
169
  });
132
170
  });
133
171
 
134
172
  describe('formatLlumiverseError', () => {
135
173
  it('should format error with status code', () => {
136
- const originalError = new Error('Rate limit exceeded');
137
- (originalError as any).status = 429;
174
+ const originalError = errorWith('Rate limit exceeded', { status: 429 });
138
175
 
139
- const formatted = driver['formatLlumiverseError'](originalError, mockContext);
176
+ const formatted = driver.formatLlumiverseError(originalError, mockContext);
140
177
 
141
178
  expect(formatted).toBeInstanceOf(LlumiverseError);
142
179
  expect(formatted.code).toBe(429);
@@ -148,20 +185,18 @@ describe('AbstractDriver Error Formatting', () => {
148
185
  });
149
186
 
150
187
  it('should extract status from statusCode property', () => {
151
- const originalError = new Error('Server error');
152
- (originalError as any).statusCode = 500;
188
+ const originalError = errorWith('Server error', { statusCode: 500 });
153
189
 
154
- const formatted = driver['formatLlumiverseError'](originalError, mockContext);
190
+ const formatted = driver.formatLlumiverseError(originalError, mockContext);
155
191
 
156
192
  expect(formatted.code).toBe(500);
157
193
  expect(formatted.retryable).toBe(true);
158
194
  });
159
195
 
160
196
  it('should extract status from code property', () => {
161
- const originalError = new Error('Timeout');
162
- (originalError as any).code = 408;
197
+ const originalError = errorWith('Timeout', { code: 408 });
163
198
 
164
- const formatted = driver['formatLlumiverseError'](originalError, mockContext);
199
+ const formatted = driver.formatLlumiverseError(originalError, mockContext);
165
200
 
166
201
  expect(formatted.code).toBe(408);
167
202
  expect(formatted.retryable).toBe(true);
@@ -170,7 +205,7 @@ describe('AbstractDriver Error Formatting', () => {
170
205
  it('should use undefined when no status code found', () => {
171
206
  const originalError = new Error('Generic error');
172
207
 
173
- const formatted = driver['formatLlumiverseError'](originalError, mockContext);
208
+ const formatted = driver.formatLlumiverseError(originalError, mockContext);
174
209
 
175
210
  expect(formatted.code).toBeUndefined();
176
211
  expect(formatted.retryable).toBeUndefined(); // Unknown retryability
@@ -179,7 +214,7 @@ describe('AbstractDriver Error Formatting', () => {
179
214
  it('should handle non-Error objects', () => {
180
215
  const originalError = 'String error message';
181
216
 
182
- const formatted = driver['formatLlumiverseError'](originalError, mockContext);
217
+ const formatted = driver.formatLlumiverseError(originalError, mockContext);
183
218
 
184
219
  expect(formatted.message).toContain('String error message');
185
220
  expect(formatted.originalError).toBe(originalError);
@@ -188,46 +223,41 @@ describe('AbstractDriver Error Formatting', () => {
188
223
  it('should preserve provider in message', () => {
189
224
  const error = new Error('Test error');
190
225
 
191
- const formatted = driver['formatLlumiverseError'](error, mockContext);
226
+ const formatted = driver.formatLlumiverseError(error, mockContext);
192
227
 
193
228
  expect(formatted.message).toMatch(/^\[test-provider\]/);
194
229
  });
195
230
 
196
231
  it('should determine retryability based on status and message', () => {
197
232
  // Retryable by status
198
- const retryableError = new Error('Error');
199
- (retryableError as any).status = 429;
200
- const formatted1 = driver['formatLlumiverseError'](retryableError, mockContext);
233
+ const retryableError = errorWith('Error', { status: 429 });
234
+ const formatted1 = driver.formatLlumiverseError(retryableError, mockContext);
201
235
  expect(formatted1.retryable).toBe(true);
202
236
 
203
237
  // Not retryable by status
204
- const nonRetryableError = new Error('Error');
205
- (nonRetryableError as any).status = 400;
206
- const formatted2 = driver['formatLlumiverseError'](nonRetryableError, mockContext);
238
+ const nonRetryableError = errorWith('Error', { status: 400 });
239
+ const formatted2 = driver.formatLlumiverseError(nonRetryableError, mockContext);
207
240
  expect(formatted2.retryable).toBe(false);
208
241
 
209
242
  // Retryable by message
210
243
  const messageRetryable = new Error('Rate limit exceeded');
211
- const formatted3 = driver['formatLlumiverseError'](messageRetryable, mockContext);
244
+ const formatted3 = driver.formatLlumiverseError(messageRetryable, mockContext);
212
245
  expect(formatted3.retryable).toBe(true);
213
246
  });
214
247
  });
215
248
 
216
249
  describe('driver override capability', () => {
217
250
  class CustomDriver extends TestDriver {
218
- public formatLlumiverseError(
219
- error: unknown,
220
- context: LlumiverseErrorContext
221
- ): LlumiverseError {
251
+ public formatLlumiverseError(error: unknown, context: LlumiverseErrorContext): LlumiverseError {
222
252
  // Custom logic: check for specific error type
223
- if ((error as any).type === 'custom_retryable') {
253
+ if (getProp(error, 'type') === 'custom_retryable') {
224
254
  return new LlumiverseError(
225
255
  `[${this.provider}] Custom retryable error`,
226
256
  true,
227
257
  context,
228
258
  error,
229
259
  undefined,
230
- 'CUSTOM_ERROR'
260
+ 'CUSTOM_ERROR',
231
261
  );
232
262
  }
233
263
  // Fall back to default
@@ -239,7 +269,7 @@ describe('AbstractDriver Error Formatting', () => {
239
269
  const customDriver = new CustomDriver({});
240
270
  const customError = { type: 'custom_retryable', message: 'Custom error' };
241
271
 
242
- const formatted = customDriver['formatLlumiverseError'](customError, mockContext);
272
+ const formatted = customDriver.formatLlumiverseError(customError, mockContext);
243
273
 
244
274
  expect(formatted.name).toBe('CUSTOM_ERROR');
245
275
  expect(formatted.code).toBeUndefined();
@@ -249,10 +279,9 @@ describe('AbstractDriver Error Formatting', () => {
249
279
 
250
280
  it('should fall back to default for non-custom errors', () => {
251
281
  const customDriver = new CustomDriver({});
252
- const regularError = new Error('Regular error');
253
- (regularError as any).status = 500;
282
+ const regularError = errorWith('Regular error', { status: 500 });
254
283
 
255
- const formatted = customDriver['formatLlumiverseError'](regularError, mockContext);
284
+ const formatted = customDriver.formatLlumiverseError(regularError, mockContext);
256
285
 
257
286
  expect(formatted.code).toBe(500);
258
287
  expect(formatted.retryable).toBe(true);