@slates-integrations/anthropic 0.2.0-rc.5

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.
@@ -0,0 +1,632 @@
1
+ import { createAxios } from 'slates';
2
+ import { Buffer } from 'node:buffer';
3
+ import type { AxiosInstance } from 'axios';
4
+ import { anthropicApiError } from './errors';
5
+
6
+ let FILES_API_BETA = 'files-api-2025-04-14';
7
+
8
+ let betaHeaders = (headers?: string[]) =>
9
+ headers && headers.length > 0 ? { 'anthropic-beta': headers.join(',') } : undefined;
10
+
11
+ let appendParam = (
12
+ params: URLSearchParams,
13
+ key: string,
14
+ value: string | number | boolean | undefined
15
+ ) => {
16
+ if (value !== undefined) params.append(key, String(value));
17
+ };
18
+
19
+ let appendArrayParam = (
20
+ params: URLSearchParams,
21
+ key: string,
22
+ values: string[] | undefined
23
+ ) => {
24
+ for (let value of values ?? []) {
25
+ params.append(key, value);
26
+ }
27
+ };
28
+
29
+ export interface BatchResult {
30
+ batchId: string;
31
+ type?: string;
32
+ processingStatus?: string;
33
+ requestCounts?: {
34
+ processing: number;
35
+ succeeded: number;
36
+ errored: number;
37
+ canceled: number;
38
+ expired: number;
39
+ };
40
+ createdAt?: string;
41
+ updatedAt?: string;
42
+ expiresAt?: string;
43
+ resultsUrl?: string;
44
+ }
45
+
46
+ export interface AnthropicFileResult {
47
+ fileId: string;
48
+ type?: string;
49
+ filename?: string;
50
+ mimeType?: string;
51
+ sizeBytes?: number;
52
+ createdAt?: string;
53
+ downloadable?: boolean;
54
+ }
55
+
56
+ export interface AnthropicFileContent {
57
+ fileId: string;
58
+ contentBase64: string;
59
+ contentType?: string;
60
+ sizeBytes: number;
61
+ }
62
+
63
+ export interface AnthropicReportResult {
64
+ data: Array<Record<string, unknown>>;
65
+ hasMore: boolean;
66
+ nextPage?: string | null;
67
+ }
68
+
69
+ export class AnthropicClient {
70
+ private axios: AxiosInstance;
71
+
72
+ constructor(private config: { token: string; apiVersion: string }) {
73
+ this.axios = createAxios({
74
+ baseURL: 'https://api.anthropic.com',
75
+ headers: {
76
+ 'x-api-key': config.token,
77
+ 'anthropic-version': config.apiVersion,
78
+ 'content-type': 'application/json'
79
+ }
80
+ });
81
+
82
+ this.axios.interceptors.response.use(
83
+ response => response,
84
+ error => Promise.reject(anthropicApiError(error))
85
+ );
86
+ }
87
+
88
+ // ---- Messages API ----
89
+
90
+ async createMessage(params: {
91
+ model: string;
92
+ maxTokens: number;
93
+ messages: Array<{
94
+ role: 'user' | 'assistant';
95
+ content: string | Array<Record<string, unknown>>;
96
+ }>;
97
+ system?: string;
98
+ temperature?: number;
99
+ topK?: number;
100
+ topP?: number;
101
+ stopSequences?: string[];
102
+ tools?: Array<Record<string, unknown>>;
103
+ toolChoice?: Record<string, unknown>;
104
+ thinking?: Record<string, unknown>;
105
+ metadata?: Record<string, unknown>;
106
+ mcpServers?: Array<Record<string, unknown>>;
107
+ serviceTier?: string;
108
+ betaHeaders?: string[];
109
+ }): Promise<Record<string, unknown>> {
110
+ let body: Record<string, unknown> = {
111
+ model: params.model,
112
+ max_tokens: params.maxTokens,
113
+ messages: params.messages
114
+ };
115
+
116
+ if (params.system !== undefined) body.system = params.system;
117
+ if (params.temperature !== undefined) body.temperature = params.temperature;
118
+ if (params.topK !== undefined) body.top_k = params.topK;
119
+ if (params.topP !== undefined) body.top_p = params.topP;
120
+ if (params.stopSequences !== undefined) body.stop_sequences = params.stopSequences;
121
+ if (params.tools !== undefined) body.tools = params.tools;
122
+ if (params.toolChoice !== undefined) body.tool_choice = params.toolChoice;
123
+ if (params.thinking !== undefined) body.thinking = params.thinking;
124
+ if (params.metadata !== undefined) body.metadata = params.metadata;
125
+ if (params.mcpServers !== undefined) body.mcp_servers = params.mcpServers;
126
+ if (params.serviceTier !== undefined) body.service_tier = params.serviceTier;
127
+
128
+ let response = await this.axios.post('/v1/messages', body, {
129
+ headers: betaHeaders(params.betaHeaders)
130
+ });
131
+ return response.data;
132
+ }
133
+
134
+ // ---- Token Counting ----
135
+
136
+ async countTokens(params: {
137
+ model: string;
138
+ messages: Array<{
139
+ role: 'user' | 'assistant';
140
+ content: string | Array<Record<string, unknown>>;
141
+ }>;
142
+ system?: string;
143
+ tools?: Array<Record<string, unknown>>;
144
+ thinking?: Record<string, unknown>;
145
+ betaHeaders?: string[];
146
+ }): Promise<{ inputTokens: number }> {
147
+ let body: Record<string, unknown> = {
148
+ model: params.model,
149
+ messages: params.messages
150
+ };
151
+
152
+ if (params.system !== undefined) body.system = params.system;
153
+ if (params.tools !== undefined) body.tools = params.tools;
154
+ if (params.thinking !== undefined) body.thinking = params.thinking;
155
+
156
+ let response = await this.axios.post('/v1/messages/count_tokens', body, {
157
+ headers: betaHeaders(params.betaHeaders)
158
+ });
159
+ return { inputTokens: response.data.input_tokens };
160
+ }
161
+
162
+ // ---- Models API ----
163
+
164
+ async listModels(params?: { limit?: number; afterId?: string; beforeId?: string }): Promise<{
165
+ models: Array<Record<string, unknown>>;
166
+ hasMore: boolean;
167
+ firstId?: string;
168
+ lastId?: string;
169
+ }> {
170
+ let queryParams: Record<string, string> = {};
171
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
172
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
173
+ if (params?.beforeId !== undefined) queryParams.before_id = params.beforeId;
174
+
175
+ let response = await this.axios.get('/v1/models', { params: queryParams });
176
+ return {
177
+ models: response.data.data,
178
+ hasMore: response.data.has_more,
179
+ firstId: response.data.first_id,
180
+ lastId: response.data.last_id
181
+ };
182
+ }
183
+
184
+ async getModel(modelId: string): Promise<Record<string, unknown>> {
185
+ let response = await this.axios.get(`/v1/models/${modelId}`);
186
+ return response.data;
187
+ }
188
+
189
+ // ---- Files API ----
190
+
191
+ async createFile(params: {
192
+ filename: string;
193
+ contentBase64: string;
194
+ mimeType?: string;
195
+ }): Promise<AnthropicFileResult> {
196
+ let fileBytes = Buffer.from(params.contentBase64, 'base64');
197
+ let formData = new FormData();
198
+ let blob = new Blob([fileBytes], {
199
+ type: params.mimeType ?? 'application/octet-stream'
200
+ });
201
+ formData.append('file', blob, params.filename);
202
+
203
+ let response = await this.axios.post('/v1/files', formData, {
204
+ headers: {
205
+ 'anthropic-beta': FILES_API_BETA,
206
+ 'Content-Type': 'multipart/form-data'
207
+ }
208
+ });
209
+ return this.normalizeFile(response.data);
210
+ }
211
+
212
+ async listFiles(params?: { limit?: number; afterId?: string; beforeId?: string }): Promise<{
213
+ files: Array<AnthropicFileResult>;
214
+ hasMore: boolean;
215
+ firstId?: string;
216
+ lastId?: string;
217
+ }> {
218
+ let queryParams: Record<string, string> = {};
219
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
220
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
221
+ if (params?.beforeId !== undefined) queryParams.before_id = params.beforeId;
222
+
223
+ let response = await this.axios.get('/v1/files', {
224
+ params: queryParams,
225
+ headers: { 'anthropic-beta': FILES_API_BETA }
226
+ });
227
+ return {
228
+ files: (response.data.data as Array<Record<string, unknown>>).map(file =>
229
+ this.normalizeFile(file)
230
+ ),
231
+ hasMore: response.data.has_more,
232
+ firstId: response.data.first_id,
233
+ lastId: response.data.last_id
234
+ };
235
+ }
236
+
237
+ async getFile(fileId: string): Promise<AnthropicFileResult> {
238
+ let response = await this.axios.get(`/v1/files/${fileId}`, {
239
+ headers: { 'anthropic-beta': FILES_API_BETA }
240
+ });
241
+ return this.normalizeFile(response.data);
242
+ }
243
+
244
+ async downloadFile(fileId: string): Promise<AnthropicFileContent> {
245
+ let response = await this.axios.get(`/v1/files/${fileId}/content`, {
246
+ responseType: 'arraybuffer',
247
+ headers: { 'anthropic-beta': FILES_API_BETA }
248
+ });
249
+ let content = Buffer.from(response.data as ArrayBuffer);
250
+ let contentType = response.headers['content-type'];
251
+ return {
252
+ fileId,
253
+ contentBase64: content.toString('base64'),
254
+ contentType: typeof contentType === 'string' ? contentType : undefined,
255
+ sizeBytes: content.byteLength
256
+ };
257
+ }
258
+
259
+ async deleteFile(fileId: string): Promise<{ fileId: string; type?: string }> {
260
+ let response = await this.axios.delete(`/v1/files/${fileId}`, {
261
+ headers: { 'anthropic-beta': FILES_API_BETA }
262
+ });
263
+ return {
264
+ fileId: response.data.id,
265
+ type: response.data.type
266
+ };
267
+ }
268
+
269
+ // ---- Message Batches API ----
270
+
271
+ async createMessageBatch(
272
+ requests: Array<{
273
+ customId: string;
274
+ params: Record<string, unknown>;
275
+ }>,
276
+ betaHeaderValues?: string[]
277
+ ): Promise<BatchResult> {
278
+ let body = {
279
+ requests: requests.map(r => ({
280
+ custom_id: r.customId,
281
+ params: r.params
282
+ }))
283
+ };
284
+
285
+ let response = await this.axios.post('/v1/messages/batches', body, {
286
+ headers: betaHeaders(betaHeaderValues)
287
+ });
288
+ return this.normalizeBatch(response.data);
289
+ }
290
+
291
+ async getMessageBatch(batchId: string): Promise<BatchResult> {
292
+ let response = await this.axios.get(`/v1/messages/batches/${batchId}`);
293
+ return this.normalizeBatch(response.data);
294
+ }
295
+
296
+ async listMessageBatches(params?: {
297
+ limit?: number;
298
+ afterId?: string;
299
+ beforeId?: string;
300
+ }): Promise<{ batches: Array<BatchResult>; hasMore: boolean }> {
301
+ let queryParams: Record<string, string> = {};
302
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
303
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
304
+ if (params?.beforeId !== undefined) queryParams.before_id = params.beforeId;
305
+
306
+ let response = await this.axios.get('/v1/messages/batches', { params: queryParams });
307
+ return {
308
+ batches: (response.data.data as Array<Record<string, unknown>>).map(
309
+ (b: Record<string, unknown>) => this.normalizeBatch(b)
310
+ ),
311
+ hasMore: response.data.has_more
312
+ };
313
+ }
314
+
315
+ async cancelMessageBatch(batchId: string): Promise<BatchResult> {
316
+ let response = await this.axios.post(`/v1/messages/batches/${batchId}/cancel`);
317
+ return this.normalizeBatch(response.data);
318
+ }
319
+
320
+ private normalizeBatch(data: Record<string, unknown>): BatchResult {
321
+ let counts = data.request_counts as
322
+ | {
323
+ processing: number;
324
+ succeeded: number;
325
+ errored: number;
326
+ canceled: number;
327
+ expired: number;
328
+ }
329
+ | undefined;
330
+ return {
331
+ batchId: data.id as string,
332
+ type: data.type as string | undefined,
333
+ processingStatus: data.processing_status as string | undefined,
334
+ requestCounts: counts,
335
+ createdAt: data.created_at as string | undefined,
336
+ updatedAt: data.updated_at as string | undefined,
337
+ expiresAt: data.expires_at as string | undefined,
338
+ resultsUrl: data.results_url as string | undefined
339
+ };
340
+ }
341
+
342
+ private normalizeFile(data: Record<string, unknown>): AnthropicFileResult {
343
+ return {
344
+ fileId: data.id as string,
345
+ type: data.type as string | undefined,
346
+ filename: data.filename as string | undefined,
347
+ mimeType: data.mime_type as string | undefined,
348
+ sizeBytes: data.size_bytes as number | undefined,
349
+ createdAt: data.created_at as string | undefined,
350
+ downloadable: data.downloadable as boolean | undefined
351
+ };
352
+ }
353
+
354
+ // ---- Admin API: Organization ----
355
+
356
+ async getOrganization(): Promise<Record<string, unknown>> {
357
+ let response = await this.axios.get('/v1/organizations/me');
358
+ return response.data;
359
+ }
360
+
361
+ // ---- Admin API: Members ----
362
+
363
+ async listMembers(params?: {
364
+ limit?: number;
365
+ afterId?: string;
366
+ }): Promise<{ members: Array<Record<string, unknown>>; hasMore: boolean }> {
367
+ let queryParams: Record<string, string> = {};
368
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
369
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
370
+
371
+ let response = await this.axios.get('/v1/organizations/users', { params: queryParams });
372
+ return {
373
+ members: response.data.data,
374
+ hasMore: response.data.has_more
375
+ };
376
+ }
377
+
378
+ async updateMember(userId: string, role: string): Promise<Record<string, unknown>> {
379
+ let response = await this.axios.post(`/v1/organizations/users/${userId}`, { role });
380
+ return response.data;
381
+ }
382
+
383
+ async removeMember(userId: string): Promise<void> {
384
+ await this.axios.delete(`/v1/organizations/users/${userId}`);
385
+ }
386
+
387
+ // ---- Admin API: Invites ----
388
+
389
+ async createInvite(email: string, role: string): Promise<Record<string, unknown>> {
390
+ let response = await this.axios.post('/v1/organizations/invites', { email, role });
391
+ return response.data;
392
+ }
393
+
394
+ async listInvites(params?: {
395
+ limit?: number;
396
+ afterId?: string;
397
+ }): Promise<{ invites: Array<Record<string, unknown>>; hasMore: boolean }> {
398
+ let queryParams: Record<string, string> = {};
399
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
400
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
401
+
402
+ let response = await this.axios.get('/v1/organizations/invites', { params: queryParams });
403
+ return {
404
+ invites: response.data.data,
405
+ hasMore: response.data.has_more
406
+ };
407
+ }
408
+
409
+ async getInvite(inviteId: string): Promise<Record<string, unknown>> {
410
+ let response = await this.axios.get(`/v1/organizations/invites/${inviteId}`);
411
+ return response.data;
412
+ }
413
+
414
+ async deleteInvite(inviteId: string): Promise<void> {
415
+ await this.axios.delete(`/v1/organizations/invites/${inviteId}`);
416
+ }
417
+
418
+ // ---- Admin API: Workspaces ----
419
+
420
+ async createWorkspace(
421
+ name: string,
422
+ params?: Record<string, unknown>
423
+ ): Promise<Record<string, unknown>> {
424
+ let response = await this.axios.post('/v1/organizations/workspaces', { name, ...params });
425
+ return response.data;
426
+ }
427
+
428
+ async listWorkspaces(params?: {
429
+ limit?: number;
430
+ afterId?: string;
431
+ includeArchived?: boolean;
432
+ }): Promise<{ workspaces: Array<Record<string, unknown>>; hasMore: boolean }> {
433
+ let queryParams: Record<string, string> = {};
434
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
435
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
436
+ if (params?.includeArchived !== undefined)
437
+ queryParams.include_archived = String(params.includeArchived);
438
+
439
+ let response = await this.axios.get('/v1/organizations/workspaces', {
440
+ params: queryParams
441
+ });
442
+ return {
443
+ workspaces: response.data.data,
444
+ hasMore: response.data.has_more
445
+ };
446
+ }
447
+
448
+ async getWorkspace(workspaceId: string): Promise<Record<string, unknown>> {
449
+ let response = await this.axios.get(`/v1/organizations/workspaces/${workspaceId}`);
450
+ return response.data;
451
+ }
452
+
453
+ async updateWorkspace(
454
+ workspaceId: string,
455
+ params: Record<string, unknown>
456
+ ): Promise<Record<string, unknown>> {
457
+ let response = await this.axios.post(
458
+ `/v1/organizations/workspaces/${workspaceId}`,
459
+ params
460
+ );
461
+ return response.data;
462
+ }
463
+
464
+ async archiveWorkspace(workspaceId: string): Promise<Record<string, unknown>> {
465
+ let response = await this.axios.post(`/v1/organizations/workspaces/${workspaceId}`, {
466
+ is_archived: true
467
+ });
468
+ return response.data;
469
+ }
470
+
471
+ // ---- Admin API: Workspace Members ----
472
+
473
+ async addWorkspaceMember(
474
+ workspaceId: string,
475
+ userId: string,
476
+ role: string
477
+ ): Promise<Record<string, unknown>> {
478
+ let response = await this.axios.post(
479
+ `/v1/organizations/workspaces/${workspaceId}/members`,
480
+ {
481
+ user_id: userId,
482
+ workspace_role: role
483
+ }
484
+ );
485
+ return response.data;
486
+ }
487
+
488
+ async listWorkspaceMembers(
489
+ workspaceId: string,
490
+ params?: {
491
+ limit?: number;
492
+ afterId?: string;
493
+ }
494
+ ): Promise<{ members: Array<Record<string, unknown>>; hasMore: boolean }> {
495
+ let queryParams: Record<string, string> = {};
496
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
497
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
498
+
499
+ let response = await this.axios.get(
500
+ `/v1/organizations/workspaces/${workspaceId}/members`,
501
+ { params: queryParams }
502
+ );
503
+ return {
504
+ members: response.data.data,
505
+ hasMore: response.data.has_more
506
+ };
507
+ }
508
+
509
+ async getWorkspaceMember(
510
+ workspaceId: string,
511
+ userId: string
512
+ ): Promise<Record<string, unknown>> {
513
+ let response = await this.axios.get(
514
+ `/v1/organizations/workspaces/${workspaceId}/members/${userId}`
515
+ );
516
+ return response.data;
517
+ }
518
+
519
+ async updateWorkspaceMember(
520
+ workspaceId: string,
521
+ userId: string,
522
+ role: string
523
+ ): Promise<Record<string, unknown>> {
524
+ let response = await this.axios.post(
525
+ `/v1/organizations/workspaces/${workspaceId}/members/${userId}`,
526
+ {
527
+ workspace_role: role
528
+ }
529
+ );
530
+ return response.data;
531
+ }
532
+
533
+ async removeWorkspaceMember(workspaceId: string, userId: string): Promise<void> {
534
+ await this.axios.delete(`/v1/organizations/workspaces/${workspaceId}/members/${userId}`);
535
+ }
536
+
537
+ // ---- Admin API: API Keys ----
538
+
539
+ async listApiKeys(params?: {
540
+ limit?: number;
541
+ afterId?: string;
542
+ status?: string;
543
+ workspaceId?: string;
544
+ }): Promise<{ apiKeys: Array<Record<string, unknown>>; hasMore: boolean }> {
545
+ let queryParams: Record<string, string> = {};
546
+ if (params?.limit !== undefined) queryParams.limit = String(params.limit);
547
+ if (params?.afterId !== undefined) queryParams.after_id = params.afterId;
548
+ if (params?.status !== undefined) queryParams.status = params.status;
549
+ if (params?.workspaceId !== undefined) queryParams.workspace_id = params.workspaceId;
550
+
551
+ let response = await this.axios.get('/v1/organizations/api_keys', { params: queryParams });
552
+ return {
553
+ apiKeys: response.data.data,
554
+ hasMore: response.data.has_more
555
+ };
556
+ }
557
+
558
+ async getApiKey(apiKeyId: string): Promise<Record<string, unknown>> {
559
+ let response = await this.axios.get(`/v1/organizations/api_keys/${apiKeyId}`);
560
+ return response.data;
561
+ }
562
+
563
+ async updateApiKey(
564
+ apiKeyId: string,
565
+ params: Record<string, unknown>
566
+ ): Promise<Record<string, unknown>> {
567
+ let response = await this.axios.post(`/v1/organizations/api_keys/${apiKeyId}`, params);
568
+ return response.data;
569
+ }
570
+
571
+ // ---- Admin API: Usage and Cost Reports ----
572
+
573
+ async getMessagesUsageReport(params: {
574
+ startingAt: string;
575
+ endingAt?: string;
576
+ bucketWidth?: '1m' | '1h' | '1d';
577
+ limit?: number;
578
+ page?: string;
579
+ groupBy?: string[];
580
+ apiKeyIds?: string[];
581
+ workspaceIds?: string[];
582
+ models?: string[];
583
+ serviceTiers?: string[];
584
+ contextWindows?: string[];
585
+ }): Promise<AnthropicReportResult> {
586
+ let queryParams = new URLSearchParams();
587
+ appendParam(queryParams, 'starting_at', params.startingAt);
588
+ appendParam(queryParams, 'ending_at', params.endingAt);
589
+ appendParam(queryParams, 'bucket_width', params.bucketWidth);
590
+ appendParam(queryParams, 'limit', params.limit);
591
+ appendParam(queryParams, 'page', params.page);
592
+ appendArrayParam(queryParams, 'group_by[]', params.groupBy);
593
+ appendArrayParam(queryParams, 'api_key_ids[]', params.apiKeyIds);
594
+ appendArrayParam(queryParams, 'workspace_ids[]', params.workspaceIds);
595
+ appendArrayParam(queryParams, 'models[]', params.models);
596
+ appendArrayParam(queryParams, 'service_tiers[]', params.serviceTiers);
597
+ appendArrayParam(queryParams, 'context_window[]', params.contextWindows);
598
+
599
+ let response = await this.axios.get('/v1/organizations/usage_report/messages', {
600
+ params: queryParams
601
+ });
602
+ return {
603
+ data: response.data.data,
604
+ hasMore: response.data.has_more,
605
+ nextPage: response.data.next_page
606
+ };
607
+ }
608
+
609
+ async getCostReport(params: {
610
+ startingAt: string;
611
+ endingAt?: string;
612
+ limit?: number;
613
+ page?: string;
614
+ groupBy?: string[];
615
+ }): Promise<AnthropicReportResult> {
616
+ let queryParams = new URLSearchParams();
617
+ appendParam(queryParams, 'starting_at', params.startingAt);
618
+ appendParam(queryParams, 'ending_at', params.endingAt);
619
+ appendParam(queryParams, 'limit', params.limit);
620
+ appendParam(queryParams, 'page', params.page);
621
+ appendArrayParam(queryParams, 'group_by[]', params.groupBy);
622
+
623
+ let response = await this.axios.get('/v1/organizations/cost_report', {
624
+ params: queryParams
625
+ });
626
+ return {
627
+ data: response.data.data,
628
+ hasMore: response.data.has_more,
629
+ nextPage: response.data.next_page
630
+ };
631
+ }
632
+ }
@@ -0,0 +1,74 @@
1
+ import { ServiceError, badRequestError } from '@lowerdeck/error';
2
+
3
+ type ErrorResponse = {
4
+ status?: number;
5
+ statusText?: string;
6
+ data?: unknown;
7
+ };
8
+
9
+ let isRecord = (value: unknown): value is Record<string, unknown> =>
10
+ typeof value === 'object' && value !== null;
11
+
12
+ let addMessage = (messages: string[], value: unknown) => {
13
+ if (typeof value !== 'string') return;
14
+ let trimmed = value.trim();
15
+ if (trimmed && !messages.includes(trimmed)) {
16
+ messages.push(trimmed);
17
+ }
18
+ };
19
+
20
+ let extractAnthropicMessage = (error: unknown) => {
21
+ let response = isRecord(error) ? (error.response as ErrorResponse | undefined) : undefined;
22
+ let data = response?.data;
23
+ let messages: string[] = [];
24
+
25
+ if (isRecord(data)) {
26
+ addMessage(messages, data.message);
27
+ addMessage(messages, data.error);
28
+
29
+ if (isRecord(data.error)) {
30
+ addMessage(messages, data.error.message);
31
+ addMessage(messages, data.error.type);
32
+ }
33
+ } else {
34
+ addMessage(messages, data);
35
+ }
36
+
37
+ if (messages.length > 0) {
38
+ return messages.join(' - ');
39
+ }
40
+
41
+ if (error instanceof Error && error.message) {
42
+ return error.message;
43
+ }
44
+
45
+ return 'Unknown error';
46
+ };
47
+
48
+ export let anthropicServiceError = (message: string) =>
49
+ new ServiceError(badRequestError({ message }));
50
+
51
+ export let anthropicApiError = (error: unknown, operation = 'request') => {
52
+ if (error instanceof ServiceError) {
53
+ return error;
54
+ }
55
+
56
+ let response = isRecord(error) ? (error.response as ErrorResponse | undefined) : undefined;
57
+ let status = response?.status;
58
+ let statusLabel =
59
+ status !== undefined
60
+ ? `HTTP ${status}${response?.statusText ? ` ${response.statusText}` : ''}: `
61
+ : '';
62
+
63
+ let serviceError = anthropicServiceError(
64
+ `Anthropic API ${operation} failed: ${statusLabel}${extractAnthropicMessage(error)}`
65
+ );
66
+ serviceError.data.reason = 'anthropic_api_error';
67
+ serviceError.data.upstreamStatus = status;
68
+
69
+ if (error instanceof Error) {
70
+ serviceError.setParent(error);
71
+ }
72
+
73
+ return serviceError;
74
+ };